Skip to content
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
35 changes: 19 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ See [`Sender`](#sender) for more details.

#### withBase()

The `withBase($baseUri, array $parameters = array())` method can be used to change the base URI used to
The `withBase($baseUri)` method can be used to change the base URI used to
resolve relative URIs to.

```php
Expand All @@ -139,7 +139,7 @@ actually returns a *new* [`Browser`](#browser) instance with the given base URI
Any requests to relative URIs will then be processed by first prepending the
base URI.
Please note that this merely prepends the base URI and does *not* resolve any
relative path references.
relative path references (like `../` etc.).
This is mostly useful for API calls where all endpoints (URIs) are located
under a common base URI scheme.

Expand Down Expand Up @@ -167,13 +167,26 @@ See also [`withBase()`](#withbase).

The `resolve($uri, array $parameters = array())` method can be used to resolve the given relative URI to
an absolute URI by appending it behind the configured base URI.
It also replaces URI template placerholders with the given `$parameters`
It also replaces URI template placeholders with the given `$parameters`
according to [RFC 6570](http://tools.ietf.org/html/rfc6570).
It returns a new [`Uri`](#uri) instace which can then be passed
It returns a new [`Uri`](#uri) instance which can then be passed
to the [HTTP methods](#methods).

URI template placeholders in the given URI string will be replaced according to
[RFC 6570](http://tools.ietf.org/html/rfc6570):

```php
echo $browser->resolve('http://example.com/{?first,second,third}', array(
'first' => 'a',
'third' => 'c'
));
// http://example.com/?first=a&third=c
```

If you pass in a relative URI string, then it will be resolved relative to the
configured base URI.
Please note that this merely prepends the base URI and does *not* resolve any
relative path references.
relative path references (like `../` etc.).
This is mostly useful for API calls where all endpoints (URIs) are located
under a common base URI:

Expand All @@ -184,16 +197,6 @@ echo $newBrowser->resolve('/example');
// http://api.example.com/v3/example
```

The given URI may also contain URI template placeholders:

```php
echo $browser->resolve('http://example.com/{?first,second,third}', array(
'first' => 'a',
'third' => 'c'
));
// http://example.com/?first=a&third=c
```

The URI template placeholders can also be combined with a base URI like this:

```php
Expand All @@ -202,7 +205,7 @@ echo $newBrowser->resolve('/fetch{/file}{?version,tag}', array(
'version' => 1.0,
'tag' => 'just testing'
));
// http://api.example.com/v3/fetch/file?version=1.0&tag=just%20testing
// http://api.example.com/v3/fetch/example?version=1.0&tag=just%20testing
```

This uses the excellent [rize/uri-template](https://github.com/rize/UriTemplate) library under the hood.
Expand Down
2 changes: 1 addition & 1 deletion examples/google.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
var_dump($result->getHeaders(), $result->getBody());
});

$client->get('http://google.com')->then(function (Response $response) {
$client->get('http://google.com/')->then(function (Response $response) {
var_dump($response->getHeaders(), $response->getBody());
});

Expand Down
52 changes: 38 additions & 14 deletions src/Browser.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ class Browser
private $baseUri = null;
private $options = array();

public function __construct(LoopInterface $loop, Sender $sender = null)
public function __construct(LoopInterface $loop, Sender $sender = null, UriTemplate $uriTemplate = null)
{
if ($sender === null) {
$sender = Sender::createFromLoop($loop);
}
if ($uriTemplate === null) {
$uriTemplate = new UriTemplate();
}
$this->sender = $sender;
$this->loop = $loop;
$this->uriTemplate = new UriTemplate();
$this->uriTemplate = $uriTemplate;
}

public function get($url, $headers = array())
Expand Down Expand Up @@ -75,42 +78,63 @@ public function send(Request $request)
}

/**
* Returns an absolute URI by processing the given relative URI
* Returns an absolute URI by processing the given relative URI, possibly using URI template syntax (RFC 6570)
*
* You can either pass in a relative or absolute URI, which may or may not
* contain any number of URI template placeholders.
*
* A relative URI can be given as a string value which may contain placeholders.
* An absolute URI can be given as a string value which may contain placeholders.
*
* You can also pass in an `Uri` instance. By definition of this library,
* an `Uri` instance is always absolute and can not contain any placeholders.
* As such, it is safe to pass the result of this method as input to this method.
*
* @param string|Uri $uri relative or absolute URI
* @param array $parameters parameters for URI template syntax (RFC 6570)
* @param array $parameters (optional) parameters for URI template placeholders (RFC 6570)
* @return Uri absolute URI
* @see self::withBase()
*/
public function resolve($uri, $parameters = array())
{
if ($this->baseUri !== null) {
$uri = $this->baseUri->expandBase($uri);
// not already an absolute `Uri` instance?
if (!($uri instanceof Uri)) {
// relative URIs should be prefixed with base URI
if ($this->baseUri !== null) {
$uri = $this->baseUri->expandBase($uri);
}

// replace all URI template placeholders (RFC 6570)
$uri = $this->uriTemplate->expand($uri, $parameters);

// ensure this is actually a valid, absolute URI instance
$uri = new Uri($uri);
}

$uri = $this->uriTemplate->expand($uri, $parameters);
// ensure we're actually below the base URI
if ($this->baseUri !== null) {
$this->baseUri->assertBaseOf($uri);
}

return new Uri($uri);
return $uri;
}

/**
* Creates a new Browser instance with the given absolute base URI, possibly using URI template syntax (RFC 6570)
* Creates a new Browser instance with the given absolute base URI
*
* This is mostly useful for use with the `resolve()` method.
* Any relative URI passed to `uri()` will simply be appended behind the given
* `$baseUrl`.
* Any relative URI passed to `resolve()` will simply be appended behind the given
* `$baseUri`.
*
* @param string|Uri $baseUri absolute base URI
* @param array $parameters (optional) default parameters to pass to URI template placeholders
* @return self
* @see self::url()
* @see self::withoutBase()
*/
public function withBase($baseUri, array $parameters = array())
public function withBase($baseUri)
{
$browser = clone $this;
$browser->baseUri = new Uri($baseUri);
$browser->uriTemplate = new UriTemplate('', $parameters);

return $browser;
}
Expand Down
46 changes: 32 additions & 14 deletions src/Message/Uri.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ public function __construct($uri)
throw new InvalidArgumentException('Not a valid absolute URI');
}

if (strpos($uri, '{') !== false) {
throw new \InvalidArgumentException('Contains placeholders');
}

$this->scheme = $parts['scheme'];
$this->host = $parts['host'];
$this->port = isset($parts['port']) ? $parts['port'] : null;
Expand Down Expand Up @@ -66,24 +70,29 @@ public function getQuery()
}

/**
* Reolves the given $uri by appending it behind $this base URI
* Resolves the given relative or absolute $uri by appending it behind $this base URI
*
* @param unknown $uri
* @return Uri
* @throws UnexpectedValueException
* The given $uri parameter can be either a relative or absolute URI string
* which can optionally contain URI template placeholders.
*
* As such, its value or the outcome of this method does not neccessarily
* have to represent a valid, absolute URI. Hence, it will be returned as
* a string value instead of an `Uri` instance.
*
* If the given $uri is a relative URI, it will simply be appended behind $this base URI.
*
* If the given $uri is an absolute URI, it will simply be returned,
* irrespective of the current base URI.
*
* @param string $uri
* @return string
* @internal
* @see Browser::resolve()
*/
public function expandBase($uri)
{
if ($uri instanceof self) {
return $this->assertBase($uri);
}

try {
return $this->assertBase(new self($uri));
} catch (\InvalidArgumentException $e) {
// not an absolute URI
if (strpos($uri, '://') !== false) {
return $uri;
}

$new = clone $this;
Expand All @@ -104,10 +113,19 @@ public function expandBase($uri)

$new->path .= $uri;

return $new;
return (string)$new;
}

private function assertBase(Uri $new)
/**
* Asserts that $this base URI is the base of the given $new Uri instance, i.e. the given $new URI is *below* this base URI
*
* @param Uri $new
* @throws \UnexpectedValueException
* @return Uri
* @internal
* @see Browser::resolve()
*/
public function assertBaseOf(Uri $new)
{
if ($new->scheme !== $this->scheme || $new->host !== $this->host || $new->port !== $this->port || strpos($new->path, $this->path) !== 0) {
throw new \UnexpectedValueException('Invalid base, "' . $new . '" does not appear to be below "' . $this . '"');
Expand Down
41 changes: 3 additions & 38 deletions tests/BrowserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,46 +123,11 @@ public function testResolveUriTemplateAbsolute()
$this->assertEquals('http://example.com/?q=test', $this->browser->resolve('http://example.com/{?q}', array('q' => 'test')));
}

public function testWithBaseUriTemplateParameters()
{
$browser = $this->browser->withBase('http://example.com/{version}/', array('version' => 1));

return $browser;
}

/**
* @depends testWithBaseUriTemplateParameters
* @param Browser $browser
*/
public function testResolveUriTemplateWithDefaultParameters(Browser $browser)
{
$this->assertEquals('http://example.com/1/', $browser->resolve(''));
}

/**
* @depends testWithBaseUriTemplateParameters
* @param Browser $browser
*/
public function testResolveUriTemplateOverwriteDefaultParameter(Browser $browser)
{
$this->assertEquals('http://example.com/2/', $browser->resolve('', array('version' => 2)));
}

/**
* @depends testWithBaseUriTemplateParameters
* @param Browser $browser
*/
public function testResolveUriTemplateUnsetQueryParameter(Browser $browser)
{
$this->assertEquals('http://example.com/1/test', $browser->resolve('/test{?q}'));
}

/**
* @depends testWithBaseUriTemplateParameters
* @param Browser $browser
* @expectedException InvalidArgumentException
*/
public function testResolveUriTemplateSetQueryParameter(Browser $browser)
public function testWithBaseUriTemplateParametersFails()
{
$this->assertEquals('http://example.com/1/test?q=hi', $browser->resolve('/test{?q}', array('q' => 'hi')));
$this->browser->withBase('http://example.com/{version}/');
}
}
59 changes: 37 additions & 22 deletions tests/Message/UriTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ public function testInvalidUri()
new Uri('invalid');
}

/**
* @expectedException InvalidArgumentException
*/
public function testInvalidPlaceholderUri()
{
new Uri('http://example.com/{version}');
}

public function testUriExpandBaseEndsWithoutSlash()
{
$base = new Uri('http://example.com/base');
Expand All @@ -53,42 +61,49 @@ public function testUriExpandBaseEndsWithoutSlash()
return $base;
}

public function testUriExpandBaseEndsWithSlash()
{
$base = new Uri('http://example.com/base/');

$this->assertEquals('http://example.com/base/', $base->expandBase(''));
$this->assertEquals('http://example.com/base/', $base->expandBase('/'));
$this->assertEquals('http://example.com/base/test', $base->expandBase('test'));
$this->assertEquals('http://example.com/base/test', $base->expandBase('/test'));

$this->assertEquals('http://example.com/base/?key=value', $base->expandBase('?key=value'));
$this->assertEquals('http://example.com/base/?key=value', $base->expandBase('/?key=value'));

$this->assertEquals('http://example.com/base/', $base->expandBase('http://example.com/base/'));
$this->assertEquals('http://example.com/base/another', $base->expandBase('http://example.com/base/another'));
}

public function testAssertBase()
{
$base = new Uri('http://example.com/base');

$base->assertBaseOf(new Uri('http://example.com/base'));
$base->assertBaseOf(new Uri('http://example.com/base/'));
$base->assertBaseOf(new Uri('http://example.com/base?test'));
}

public function provideOtherBaseUris()
{
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 domain instance' => array(new Uri('http://example.org/base'))
);
}

/**
* @param string|Uri $other
* @param Uri $base
* @param string $other
* @dataProvider provideOtherBaseUris
* @depends testUriExpandBaseEndsWithoutSlash
* @expectedException UnexpectedValueException
*/
public function testUriExpandBaseWithOtherBase($other, Uri $base)
public function testAssertNotBase($other)
{
$base->expandBase($other);
}

public function testUriExpandBaseEndsWithSlash()
{
$base = new Uri('http://example.com/base/');

$this->assertEquals('http://example.com/base/', $base->expandBase(''));
$this->assertEquals('http://example.com/base/', $base->expandBase('/'));
$this->assertEquals('http://example.com/base/test', $base->expandBase('test'));
$this->assertEquals('http://example.com/base/test', $base->expandBase('/test'));

$this->assertEquals('http://example.com/base/?key=value', $base->expandBase('?key=value'));
$this->assertEquals('http://example.com/base/?key=value', $base->expandBase('/?key=value'));
$base = new Uri('http://example.com/base');

$this->assertEquals('http://example.com/base/', $base->expandBase('http://example.com/base/'));
$this->assertEquals('http://example.com/base/another', $base->expandBase('http://example.com/base/another'));
$base->assertBaseOf(new Uri($other));
}
}