Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve withBase() method and deprecate withoutBase() method #173

Merged
merged 1 commit into from
Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 42 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ mess with most of the low-level details.
* [withFollowRedirects()](#withfollowredirects)
* [withRejectErrorResponse()](#withrejecterrorresponse)
* [withBase()](#withbase)
* [withoutBase()](#withoutbase)
* [withProtocolVersion()](#withprotocolversion)
* [~~withOptions()~~](#withoptions)
* [~~withoutBase()~~](#withoutbase)
* [ResponseInterface](#responseinterface)
* [RequestInterface](#requestinterface)
* [UriInterface](#uriinterface)
Expand Down Expand Up @@ -1103,41 +1103,42 @@ given setting applied.

#### withBase()

The `withBase(string|UriInterface $baseUri): Browser` method can be used to
The `withBase(string|null|UriInterface $baseUrl): Browser` method can be used to
change the base URL used to resolve relative URLs to.

```php
$newBrowser = $browser->withBase('http://api.example.com/v3');
```

Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method
actually returns a *new* [`Browser`](#browser) instance with the given base URL applied.

Any requests to relative URLs will then be processed by first prepending
the (absolute) base URL.
Please note that this merely prepends the base URL and does *not* resolve
any relative path references (like `../` etc.).
This is mostly useful for (RESTful) API calls where all endpoints (URLs)
are located under a common base URL scheme.
If you configure a base URL, any requests to relative URLs will be
processed by first prepending this absolute base URL. Note that this
merely prepends the base URL and does *not* resolve any relative path
references (like `../` etc.). This is mostly useful for (RESTful) API
calls where all endpoints (URLs) are located under a common base URL.

```php
$browser = $browser->withBase('http://api.example.com/v3');

// will request http://api.example.com/v3/example
$newBrowser->get('/example')->then(…);
$browser->get('/example')->then(…);
```

#### withoutBase()

The `withoutBase(): Browser` method can be used to
remove the base URL.
You can pass in a `null` base URL to return a new instance that does not
use a base URL:

```php
$newBrowser = $browser->withoutBase();
$browser = $browser->withBase(null);
```

Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withoutBase()` method
actually returns a *new* [`Browser`](#browser) instance without any base URL applied.
Accordingly, any requests using relative URLs to a browser that does not
use a base URL can not be completed and will be rejected without sending
a request.

See also [`withBase()`](#withbase).
This method will throw an `InvalidArgumentException` if the given
`$baseUrl` argument is not a valid URL.

Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method
actually returns a *new* [`Browser`](#browser) instance with the given base URL applied.

> Changelog: As of v2.9.0 this method accepts a `null` value to reset the
base URL. Earlier versions had to use the deprecated `withoutBase()`
method to reset the base URL.

#### withProtocolVersion()

Expand Down Expand Up @@ -1194,6 +1195,23 @@ Notice that the [`Browser`](#browser) is an immutable object, i.e. this
method actually returns a *new* [`Browser`](#browser) instance with the
options applied.

#### ~~withoutBase()~~

> Deprecated since v2.9.0, see [`withBase()`](#withbase) instead.

The deprecated `withoutBase(): Browser` method can be used to
remove the base URL.

```php
// deprecated: see withBase() instead
$newBrowser = $browser->withoutBase();
```

Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withoutBase()` method
actually returns a *new* [`Browser`](#browser) instance without any base URL applied.

See also [`withBase()`](#withbase).

### ResponseInterface

The `Psr\Http\Message\ResponseInterface` represents the incoming response received from the [`Browser`](#browser).
Expand Down
97 changes: 56 additions & 41 deletions src/Browser.php
Original file line number Diff line number Diff line change
Expand Up @@ -618,68 +618,61 @@ public function withRejectErrorResponse($obeySuccessCode)
/**
* Changes the base URL used to resolve relative URLs to.
*
* If you configure a base URL, any requests to relative URLs will be
* processed by first prepending this absolute base URL. Note that this
* merely prepends the base URL and does *not* resolve any relative path
* references (like `../` etc.). This is mostly useful for (RESTful) API
* calls where all endpoints (URLs) are located under a common base URL.
*
* ```php
* $newBrowser = $browser->withBase('http://api.example.com/v3');
* ```
* $browser = $browser->withBase('http://api.example.com/v3');
*
* Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method
* actually returns a *new* [`Browser`](#browser) instance with the given base URL applied.
* // will request http://api.example.com/v3/example
* $browser->get('/example')->then(…);
* ```
*
* Any requests to relative URLs will then be processed by first prepending
* the (absolute) base URL.
* Please note that this merely prepends the base URL and does *not* resolve
* any relative path references (like `../` etc.).
* This is mostly useful for (RESTful) API calls where all endpoints (URLs)
* are located under a common base URL scheme.
* You can pass in a `null` base URL to return a new instance that does not
* use a base URL:
*
* ```php
* // will request http://api.example.com/v3/example
* $newBrowser->get('/example')->then(…);
* $browser = $browser->withBase(null);
* ```
*
* By definition of this library, a given base URL MUST always absolute and
* can not contain any placeholders.
* Accordingly, any requests using relative URLs to a browser that does not
* use a base URL can not be completed and will be rejected without sending
* a request.
*
* @param string|UriInterface $baseUrl absolute base URL
* This method will throw an `InvalidArgumentException` if the given
* `$baseUrl` argument is not a valid URL.
*
* Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method
* actually returns a *new* [`Browser`](#browser) instance with the given base URL applied.
*
* > Changelog: As of v2.9.0 this method accepts a `null` value to reset the
* base URL. Earlier versions had to use the deprecated `withoutBase()`
* method to reset the base URL.
*
* @param string|null|UriInterface $baseUrl absolute base URL
* @return self
* @throws InvalidArgumentException if the given $baseUri is not a valid absolute URL
* @throws InvalidArgumentException if the given $baseUrl is not a valid absolute URL
* @see self::withoutBase()
*/
public function withBase($baseUrl)
{
$browser = clone $this;
$browser->baseUrl = $this->messageFactory->uri($baseUrl);
if ($baseUrl === null) {
$browser->baseUrl = null;
return $browser;
}

if ($browser->baseUrl->getScheme() === '' || $browser->baseUrl->getHost() === '') {
$browser->baseUrl = $this->messageFactory->uri($baseUrl);
if (!\in_array($browser->baseUrl->getScheme(), array('http', 'https')) || $browser->baseUrl->getHost() === '') {
throw new \InvalidArgumentException('Base URL must be absolute');
}

return $browser;
}

/**
* Removes the base URL.
*
* ```php
* $newBrowser = $browser->withoutBase();
* ```
*
* Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withoutBase()` method
* actually returns a *new* [`Browser`](#browser) instance without any base URL applied.
*
* See also [`withBase()`](#withbase).
*
* @return self
* @see self::withBase()
*/
public function withoutBase()
{
$browser = clone $this;
$browser->baseUrl = null;

return $browser;
}

/**
* Changes the HTTP protocol version that will be used for all subsequent requests.
*
Expand Down Expand Up @@ -760,6 +753,28 @@ public function withOptions(array $options)
return $browser;
}

/**
* [Deprecated] Removes the base URL.
*
* ```php
* // deprecated: see withBase() instead
* $newBrowser = $browser->withoutBase();
* ```
*
* Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withoutBase()` method
* actually returns a *new* [`Browser`](#browser) instance without any base URL applied.
*
* See also [`withBase()`](#withbase).
*
* @return self
* @deprecated 2.9.0 See self::withBase() instead.
* @see self::withBase()
*/
public function withoutBase()
{
return $this->withBase(null);
}

/**
* @param string $method
* @param string|UriInterface $url
Expand Down
7 changes: 1 addition & 6 deletions src/Message/MessageFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,15 @@ public function uriRelative(UriInterface $base, $uri)
*
* If the given $uri is a relative URI, it will simply be appended behind $base URI.
*
* If the given $uri is an absolute URI, it will simply be verified to
* be *below* the given $base URI.
* If the given $uri is an absolute URI, it will simply be returned as-is.
*
* @param UriInterface $uri
* @param UriInterface $base
* @return UriInterface
* @throws \UnexpectedValueException
*/
public function expandBase(UriInterface $uri, UriInterface $base)
{
if ($uri->getScheme() !== '') {
if (strpos((string)$uri, (string)$base) !== 0) {
throw new \UnexpectedValueException('Invalid base, "' . $uri . '" does not appear to be below "' . $base . '"');
}
return $uri;
}

Expand Down
63 changes: 41 additions & 22 deletions tests/BrowserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,31 @@ public function provideOtherUris()
'',
'http://example.com/%7Bversion%7D/'
),
'other domain' => array(
'http://example.com/base/',
'http://example.org/base/',
'http://example.org/base/'
),
'other scheme' => array(
'http://example.com/base/',
'https://example.com/base/',
'https://example.com/base/'
),
'other port' => array(
'http://example.com/base/',
'http://example.com:81/base/',
'http://example.com:81/base/'
),
'other path' => array(
'http://example.com/base/',
'http://example.com/other/',
'http://example.com/other/'
),
'other path due to missing slash' => array(
'http://example.com/base/',
'http://example.com/other',
'http://example.com/other'
),
);
}

Expand All @@ -310,35 +335,29 @@ public function testResolveUriWithBaseEndsWithoutSlash($base, $uri, $expectedAbs
$browser->get($uri);
}

public function provideOtherBaseUris()
public function testWithBaseUrlNotAbsoluteFails()
{
return array(
'other domain' => array('http://example.org/base/'),
'other scheme' => array('https://example.com/base/'),
'other port' => array('http://example.com:81/base/'),
'other path' => array('http://example.com/other/'),
'other path due to missing slash' => array('http://example.com/other'),
);
$this->setExpectedException('InvalidArgumentException');
$this->browser->withBase('hello');
}

/**
* @param string $other
* @dataProvider provideOtherBaseUris
*/
public function testRequestingUrlsNotBelowBaseWillRejectBeforeSending($other)
public function testWithBaseUrlInvalidSchemeFails()
{
$browser = $this->browser->withBase('http://example.com/base/');

$this->sender->expects($this->never())->method('send');

$this->setExpectedException('UnexpectedValueException');
Block\await($browser->get($other), $this->loop);
$this->setExpectedException('InvalidArgumentException');
$this->browser->withBase('ftp://example.com');
}

public function testWithBaseUriNotAbsoluteFails()
public function testWithoutBaseFollowedByGetRequestTriesToSendIncompleteRequestUrl()
{
$this->setExpectedException('InvalidArgumentException');
$this->browser->withBase('hello');
$this->browser = $this->browser->withBase('http://example.com')->withoutBase();

$that = $this;
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
$that->assertEquals('path', $request->getUri());
return true;
}))->willReturn(new Promise(function () { }));

$this->browser->get('path');
}

public function testWithProtocolVersionFollowedByGetRequestSendsRequestWithProtocolVersion()
Expand Down
24 changes: 24 additions & 0 deletions tests/FunctionalBrowserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,30 @@ public function testSimpleRequest()
Block\await($this->browser->get($this->base . 'get'), $this->loop);
}

public function testGetRequestWithRelativeAddressRejects()
{
$promise = $this->browser->get('delay');

$this->setExpectedException('InvalidArgumentException', 'Invalid request URL given');
Block\await($promise, $this->loop);
}

/**
* @doesNotPerformAssertions
*/
public function testGetRequestWithBaseAndRelativeAddressResolves()
{
Block\await($this->browser->withBase($this->base)->get('get'), $this->loop);
}

/**
* @doesNotPerformAssertions
*/
public function testGetRequestWithBaseAndFullAddressResolves()
{
Block\await($this->browser->withBase('http://example.com/')->get($this->base . 'get'), $this->loop);
}

public function testCancelGetRequestWillRejectRequest()
{
$promise = $this->browser->get($this->base . 'get');
Expand Down