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

Resolve relative URIs, add withBase() and resolve() #41

Merged
merged 1 commit into from
Aug 4, 2015
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
76 changes: 75 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,80 @@ actually returns a *new* [`Browser`](#browser) instance with the given [`Sender`

See [`Sender`](#sender) for more details.

#### withBase()

The `withBase($baseUri)` method can be used to change the base URI used to
resolve relative URIs 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 URI applied.

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.
This is mostly useful for API calls where all endpoints (URIs) are located
under a common base URI scheme.

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

See also [`resolve()`](#resolve).

#### withoutBase()

The `withoutBase()` method can be used to remove the base URI.

```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 URI applied.

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

#### resolve()

The `resolve($uri)` method can be used to resolve the given relative URI to
an absolute URI by appending it behind the configured base URI.
It returns a new [`Uri`](#uri) instace which can then be passed
to the [HTTP methods](#methods).

Please note that this merely prepends the base URI and does *not* resolve any
relative path references.
This is mostly useful for API calls where all endpoints (URIs) are located
under a common base URI:

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

echo $newBrowser->resolve('/example');
// http://api.example.com/v3/example
```

Trying to resolve anything that does not live under the same base URI will
result in an `UnexpectedValueException`:

```php
$newBrowser->resolve('http://www.example.com/');
// throws UnexpectedValueException
```

Similarily, if you do not have a base URI configured, passing a relative URI
will result in an `InvalidArgumentException`:

```php
$browser->resolve('/example');
// throws InvalidArgumentException
```

### Message

The `Message` is an abstract base class for the [`Response`](#response) and [`Request`](#request).
Expand All @@ -147,7 +221,7 @@ See its [class outline](src/Message/Request.php) for more details.

#### getUri()

The `getUri()` method can be used to get its [`Uri`](#sender) instance.
The `getUri()` method can be used to get its [`Uri`](#uri) instance.

### Uri

Expand Down
66 changes: 59 additions & 7 deletions src/Browser.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
use Clue\React\Buzz\Message\Body;
use Clue\React\Buzz\Message\Headers;
use Clue\React\Buzz\Io\Sender;
use Clue\React\Buzz\Message\Uri;

class Browser
{
private $sender;
private $loop;
private $baseUri = null;
private $options = array();

public function __construct(LoopInterface $loop, Sender $sender = null)
Expand All @@ -26,40 +28,40 @@ public function __construct(LoopInterface $loop, Sender $sender = null)

public function get($url, $headers = array())
{
return $this->send(new Request('GET', $url, $headers));
return $this->send(new Request('GET', $this->resolve($url), $headers));
}

public function post($url, $headers = array(), $content = '')
{
return $this->send(new Request('POST', $url, $headers, $content));
return $this->send(new Request('POST', $this->resolve($url), $headers, $content));
}

public function head($url, $headers = array())
{
return $this->send(new Request('HEAD', $url, $headers));
return $this->send(new Request('HEAD', $this->resolve($url), $headers));
}

public function patch($url, $headers = array(), $content = '')
{
return $this->send(new Request('PATCH', $url , $headers, $content));
return $this->send(new Request('PATCH', $this->resolve($url) , $headers, $content));
}

public function put($url, $headers = array(), $content = '')
{
return $this->send(new Request('PUT', $url, $headers, $content));
return $this->send(new Request('PUT', $this->resolve($url), $headers, $content));
}

public function delete($url, $headers = array(), $content = '')
{
return $this->send(new Request('DELETE', $url, $headers, $content));
return $this->send(new Request('DELETE', $this->resolve($url), $headers, $content));
}

public function submit($url, array $fields, $headers = array(), $method = 'POST')
{
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
$content = http_build_query($fields);

return $this->send(new Request($method, $url, $headers, $content));
return $this->send(new Request($method, $this->resolve($url), $headers, $content));
}

public function send(Request $request)
Expand All @@ -69,6 +71,56 @@ public function send(Request $request)
return $transaction->send();
}

/**
* Returns an absolute URI by processing the given relative URI
*
* @param string|Uri $uri relative or absolute URI
* @return Uri absolute URI
* @see self::withBase()
*/
public function resolve($uri)
{
if ($this->baseUri !== null) {
return $this->baseUri->expandBase($uri);
}

return new Uri($uri);
}

/**
* 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`.
*
* @param string|Uri $baseUri absolute base URI
* @return self
* @see self::url()
* @see self::withoutBase()
*/
public function withBase($baseUri)
{
$browser = clone $this;
$browser->baseUri = new Uri($baseUri);

return $browser;
}

/**
* Creates a new Browser instance *without* a base URL
*
* @return self
* @see self::withBase()
*/
public function withoutBase()
{
$browser = clone $this;
$browser->baseUri = null;

return $browser;
}

public function withOptions(array $options)
{
$browser = clone $this;
Expand Down
52 changes: 52 additions & 0 deletions src/Message/Uri.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Uri
{
private $scheme;
private $host;
private $port;
private $path;
private $query;

Expand Down Expand Up @@ -63,4 +64,55 @@ public function getQuery()
{
return $this->query;
}

/**
* Reolves the given $uri by appending it behind $this base URI
*
* @param unknown $uri
* @return Uri
* @throws UnexpectedValueException
* @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
}

$new = clone $this;

$pos = strpos($uri, '?');
if ($pos !== false) {
$new->query = substr($uri, $pos + 1);
$uri = substr($uri, 0, $pos);
}

if ($uri !== '' && substr($new->path, -1) !== '/') {
$new->path .= '/';
}

if (isset($uri[0]) && $uri[0] === '/') {
$uri = substr($uri, 1);
}

$new->path .= $uri;

return $new;
}

private function assertBase(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 . '"');
}

return $new;
}
}
86 changes: 85 additions & 1 deletion tests/BrowserTest.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

use Clue\React\Buzz\Browser;
use React\Promise\Deferred;
use Clue\React\Buzz\Message\Uri;

class BrowserTest extends TestCase
{
Expand All @@ -24,4 +24,88 @@ public function testWithSender()

$this->assertNotSame($this->browser, $browser);
}

public function testResolveAbsoluteReturnsSame()
{
$this->assertEquals('http://example.com/', $this->browser->resolve('http://example.com/'));
}

public function testResolveUriInstance()
{
$this->assertEquals('http://example.com/', $this->browser->resolve(new Uri('http://example.com/')));
}

/**
* @expectedException InvalidArgumentException
*/
public function testResolveRelativeWithoutBaseFails()
{
$this->browser->resolve('example');
}

public function testWithBase()
{
$browser = $this->browser->withBase('http://example.com/root');

$this->assertInstanceOf('Clue\React\Buzz\Browser', $browser);
$this->assertNotSame($this->browser, $browser);

return $browser;
}

/**
* @depends testWithBase
* @param Browser $browser
*/
public function testResolveRootWithBase(Browser $browser)
{
$this->assertEquals('http://example.com/root/test', $browser->resolve('/test'));
}

/**
* @depends testWithBase
* @param Browser $browser
*/
public function testResolveRelativeWithBase(Browser $browser)
{
$this->assertEquals('http://example.com/root/test', $browser->resolve('test'));
}

/**
* @depends testWithBase
* @param Browser $browser
* @expectedException UnexpectedValueException
*/
public function testResolveWithOtherBaseFails(Browser $browser)
{
$browser->resolve('http://www.example.org/other');
}

/**
* @depends testWithBase
* @param Browser $browser
*/
public function testResolveWithSameBaseInstance(Browser $browser)
{
$this->assertEquals('http://example.com/root/test', $browser->resolve(new Uri('http://example.com/root/test')));
}

/**
* @depends testWithBase
* @param Browser $browser
* @expectedException UnexpectedValueException
*/
public function testResolveWithOtherBaseInstanceFails(Browser $browser)
{
$browser->resolve(new Uri('http://example.org/other'));
}

/**
* @depends testWithBase
* @param Browser $browser
*/
public function testResolveEmptyReturnsBase(Browser $browser)
{
$this->assertEquals('http://example.com/root', $browser->resolve(''));
}
}
Loading