1
0
mirror of https://github.com/guzzle/guzzle.git synced 2025-01-17 21:38:16 +01:00

Adding support for URI templates

[Http] UriTemplate syntax supports {} or {{}} for backwards compatibility.
[Http] Guzzle::inject() now allows {} or {{}} injection for consistency.
[Http] BC: removing Guzzle\Http\Client::inject and now using Guzzle\Http\Client::expandTemplate.
[Http] Can use a custom syntax for URI templates (this might be useful for something like Solr because Solr actually uses {}).
[Http] Adding the ability to inject a custom UriTemplate into clients or just use the default UriTemplate
[Http] You can now use an array when creating a request via a client to specify a URI template and additional template variables.
[Service] Adding support for URI templates to service descriptions.
[Service] [BC] removing Guzzle\Service\Description\ApiCommand::getPath() and replacing it with getUri().
[Service] Use "uri" instead of "path" when creating service descriptions.  However, there is still backwards compatibility with the "path" attribute.
This commit is contained in:
Michael Dowling 2012-02-07 23:13:00 -06:00
parent 4685aed7ef
commit b9c04909c7
18 changed files with 793 additions and 116 deletions

View File

@ -31,6 +31,7 @@ Features
- Subject/Observer signal slot system for unobtrusively modifying request behavior
- Supports all of the features of libcurl including authentication, redirects, SSL, proxies, etc
- Web service client framework for building future-proof interfaces to web services
- Full support for [URI templates](http://tools.ietf.org/html/draft-gregorio-uritemplate-08)
HTTP basics
-----------
@ -118,6 +119,44 @@ try {
}
```
URI templates
-------------
Guzzle supports the entire URI templates RFC.
```php
<?php
$client = new Guzzle\Http\Client('http://www.myapi.com/api/v1', array(
'path' => '/path/to',
'a' => 'hi',
'data' => array(
'foo' => 'bar',
'mesa' => 'jarjar'
)
));
$request = $client->get('http://www.test.com{+path}{?a,data*}');
```
The generated request URL would become: ``http://www.test.com/path/to?a=hi&foo=bar&mesa=jarajar``
You can specify URI templates and an array of additional template variables to use when creating requests:
```php
<?php
$client = new Guzzle\Http\Client('http://test.com', array(
'a' => 'hi'
));
$request = $client->get(array('/{?a,b}', array(
'b' => 'there'
));
```
The resulting URL would become ``http://test.com?a=hi&b=there``
Testing Guzzle
--------------

View File

@ -91,12 +91,7 @@ class Guzzle
*/
public static function inject($input, Collection $config)
{
// Skip expensive regular expressions if it isn't needed
if (strpos($input, '{{') === false) {
return $input;
}
return preg_replace_callback('/{{\s*([A-Za-z_\-\.0-9]+)\s*}}/', function($matches) use ($config) {
return preg_replace_callback('/{{1,2}\s*([A-Za-z_\-\.0-9]+)\s*}{1,2}/', function($matches) use ($config) {
return $config->get(trim($matches[1]));
}, $input
);

View File

@ -7,6 +7,7 @@ use Guzzle\Common\AbstractHasDispatcher;
use Guzzle\Common\ExceptionCollection;
use Guzzle\Common\Collection;
use Guzzle\Http\Url;
use Guzzle\Http\UriTemplate;
use Guzzle\Http\EntityBody;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\RequestFactory;
@ -39,6 +40,11 @@ class Client extends AbstractHasDispatcher implements ClientInterface
*/
private $curlMulti;
/**
* @var UriTemplate URI template owned by the client
*/
private $uriTemplate;
/**
* {@inheritdoc}
*/
@ -107,41 +113,91 @@ class Client extends AbstractHasDispatcher implements ClientInterface
}
/**
* Inject configuration values into a formatted string with {{param}} as a
* parameter delimiter (replace param with the configuration value name)
* Expand a URI template using client configuration data
*
* @param string $string String to inject config values into
* @param string $template URI template to expand
* @param array $variables (optional) Additional variables to use in the expansion
*
* @return string
*/
public final function inject($string)
public function expandTemplate($template, array $variables = null)
{
return Guzzle::inject($string, $this->getConfig());
$expansionVars = $this->getConfig()->getAll();
if ($variables) {
$expansionVars = array_merge($expansionVars, $variables);
}
return $this->getUriTemplate()
->setTemplate($template)
->expand($expansionVars);
}
/**
* Set the URI template expander to use with the client
*
* @param UriTemplate $uriTemplate
*
* @return Client
*/
public function setUriTemplate(UriTemplate $uriTemplate)
{
$this->uriTemplate = $uriTemplate;
return $this;
}
/**
* Get the URI template expander used by the client. A default UriTemplate
* object will be created if one does not exist.
*
* @return UriTemplate
*/
public function getUriTemplate()
{
if (!$this->uriTemplate) {
$this->uriTemplate = new UriTemplate();
}
return $this->uriTemplate;
}
/**
* Create and return a new {@see RequestInterface} configured for the client
*
* @param string $method (optional) HTTP method. Defaults to GET
* @param string $uri (optional) Resource URI. Use an absolute path to
* override the base path of the client, or a relative path to append
* to the base path of the client. The URI can contain the
* querystring as well.
* @param string|array $uri (optional) Resource URI. Use an absolute path
* to override the base path of the client, or a relative path to
* append to the base path of the client. The URI can contain the
* querystring as well. Use an array to provide a URI template and
* additional variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
* @param string|resource|array|EntityBody $body (optional) Entity body of
* request (POST/PUT) or response (GET)
*
* @return RequestInterface
* @throws InvalidArgumentException if a URI array is passed that does not
* contain exactly two elements: the URI followed by template variables
*/
public function createRequest($method = RequestInterface::GET, $uri = null, $headers = null, $body = null)
{
if (!is_array($uri)) {
$templateVars = null;
} else {
if (count($uri) != 2 || !is_array($uri[1])) {
throw new \InvalidArgumentException('You must provide a URI'
. ' template followed by an array of template variables'
. ' when using an array for a URI template');
}
list($uri, $templateVars) = $uri;
}
if (!$uri) {
$url = $this->getBaseUrl();
} else if (strpos($uri, 'http') === 0) {
// Use absolute URLs as-is
$url = $this->inject($uri);
$url = $this->expandTemplate($uri, $templateVars);
} else {
$url = Url::factory($this->getBaseUrl())->combine($this->inject($uri));
$url = Url::factory($this->getBaseUrl())->combine($this->expandTemplate($uri, $templateVars));
}
return $this->prepareRequest(
@ -193,20 +249,20 @@ class Client extends AbstractHasDispatcher implements ClientInterface
}
/**
* Get the base service endpoint URL with configuration options injected
* into the configuration setting.
* Get the client's base URL as either an expanded or raw URI template
*
* @param bool $inject (optional) Set to FALSE to get the raw base URL
* @param bool $expand (optional) Set to FALSE to get the raw base URL
* without URI template expansion
*
* @return string|null
*/
public function getBaseUrl($inject = true)
public function getBaseUrl($expand = true)
{
return $inject ? $this->inject($this->baseUrl) : $this->baseUrl;
return $expand ? $this->expandTemplate($this->baseUrl) : $this->baseUrl;
}
/**
* Set the base service endpoint URL
* Set the base URL of the client
*
* @param string $url The base service endpoint URL of the webservice
*
@ -242,24 +298,28 @@ class Client extends AbstractHasDispatcher implements ClientInterface
/**
* Create a GET request for the client
*
* @param string $path (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to append
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
* @param string|resource|array|EntityBody $body (optional) Where to store
* the response entity body
*
* @return Request
*/
public final function get($path = null, $headers = null, $body = null)
public final function get($uri = null, $headers = null, $body = null)
{
return $this->createRequest('GET', $path, $headers, $body);
return $this->createRequest('GET', $uri, $headers, $body);
}
/**
* Create a HEAD request for the client
*
* @param string $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to append
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
*
* @return Request
@ -272,8 +332,10 @@ class Client extends AbstractHasDispatcher implements ClientInterface
/**
* Create a DELETE request for the client
*
* @param string $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to append
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
*
* @return Request
@ -286,8 +348,10 @@ class Client extends AbstractHasDispatcher implements ClientInterface
/**
* Create a PUT request for the client
*
* @param string $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to append
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
* @param string|resource|array|EntityBody $body Body to send in the request
*
@ -301,8 +365,10 @@ class Client extends AbstractHasDispatcher implements ClientInterface
/**
* Create a POST request for the client
*
* @param string $uri (optional) Resource URI of the request. Use an absolute path to
* override the base path, or a relative path to append it.
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
* @param array|Collection|string|EntityBody $postBody (optional) POST
* body. Can be a string, EntityBody, or associative array of POST
@ -319,8 +385,10 @@ class Client extends AbstractHasDispatcher implements ClientInterface
/**
* Create an OPTIONS request for the client
*
* @param string $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or relative path to append
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
*
* @return Request
*/

View File

@ -36,23 +36,39 @@ interface ClientInterface extends HasDispatcherInterface
function getConfig($key = false);
/**
* Inject configuration values into a formatted string with {{param}} as a
* parameter delimiter (replace param with the configuration value name)
* Set the URI template expander to use with the client
*
* @param string $string String to inject config values into
* @param UriTemplate $uriTemplate
*
* @return ClientInterface
*/
function setUriTemplate(UriTemplate $uriTemplate);
/**
* Get the URI template expander used by the client
*
* @return UriTemplate
*/
function getUriTemplate();
/**
* Expand a URI template using client configuration data
*
* @param string $template URI template to expand
* @param array $variables (optional) Additional variables to use in the expansion
*
* @return string
*/
function inject($string);
function expandTemplate($template, array $variables = null);
/**
* Create and return a new {@see RequestInterface} configured for the client
*
* @param string $method (optional) HTTP method. Defaults to GET
* @param string $uri (optional) Resource URI. Use an absolute path to
* override the base path of the client, or a relative path to append
* to the base path of the client. The URI can contain the
* querystring as well.
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
* @param string|resource|array|EntityBody $body (optional) Entity body of
* request (POST/PUT) or response (GET)
@ -76,18 +92,17 @@ interface ClientInterface extends HasDispatcherInterface
function prepareRequest(RequestInterface $request);
/**
* Get the base service endpoint URL with configuration options injected
* into the configuration setting.
* Get the client's base URL as either an expanded or raw URI template
*
* @param bool $inject (optional) Set to FALSE to get the raw base URL
* @param bool $expand (optional) Set to FALSE to get the raw base URL
* without URI template expansion
*
* @return string
* @throws RuntimeException if a base URL has not been set
*/
function getBaseUrl($inject = true);
function getBaseUrl($expand = true);
/**
* Set the base service endpoint URL
* Set the base URL of the client
*
* @param string $url The base service endpoint URL of the webservice
*
@ -110,8 +125,10 @@ interface ClientInterface extends HasDispatcherInterface
/**
* Create a GET request for the client
*
* @param string $path (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to append
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
* @param string|resource|array|EntityBody $body (optional) Where to store
* the response entity body
@ -123,8 +140,10 @@ interface ClientInterface extends HasDispatcherInterface
/**
* Create a HEAD request for the client
*
* @param string $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to append
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
*
* @return RequestInterface
@ -134,8 +153,10 @@ interface ClientInterface extends HasDispatcherInterface
/**
* Create a DELETE request for the client
*
* @param string $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to append
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
*
* @return RequestInterface
@ -145,8 +166,10 @@ interface ClientInterface extends HasDispatcherInterface
/**
* Create a PUT request for the client
*
* @param string $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to append
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
* @param string|resource|array|EntityBody $body Body to send in the request
*
@ -157,8 +180,10 @@ interface ClientInterface extends HasDispatcherInterface
/**
* Create a POST request for the client
*
* @param string $uri (optional) Resource URI of the request. Use an absolute path to
* override the base path, or a relative path to append it.
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
* @param array|Collection $headers (optional) HTTP headers
* @param array|Collection|string|EntityBody $postBody (optional) POST
* body. Can be a string, EntityBody, or associative array of POST
@ -172,8 +197,10 @@ interface ClientInterface extends HasDispatcherInterface
/**
* Create an OPTIONS request for the client
*
* @param string $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or relative path to append
* @param string|array $uri (optional) Resource URI of the request. Use an
* absolute path to override the base path, or a relative path to
* append. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
*
* @return RequestInterface
*/

View File

@ -0,0 +1,252 @@
<?php
namespace Guzzle\Http;
/**
* Expands URI templates using an array of variables
*
* @link http://tools.ietf.org/html/draft-gregorio-uritemplate-08
*/
class UriTemplate
{
private $template;
private $variables;
private $regex = '/\{{1,2}([^\}]+)\}{1,2}/';
private static $operators = array('+', '#', '.', '/', ';', '?', '&');
private static $delims = array(':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=');
private static $delimsPct = array('%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D');
/**
* @param string $template (optional) URI template to expand
*/
public function __construct($template = '')
{
$this->template = $template;
}
/**
* Get the URI template
*
* @return string
*/
public function getTemplate()
{
return $this->template;
}
/**
* Set the URI template
*
* @param string $template URI template to expand
*
* @return UriTemplate
*/
public function setTemplate($template)
{
$this->template = $template;
return $this;
}
/**
* Expand the URI template using the supplied variables
*
* @param array $variables Variables to use with the expansion
*
* @return string Returns the expanded template
*/
public function expand(array $variables)
{
$this->variables = $variables;
return preg_replace_callback($this->regex, array($this, 'expandMatch'), $this->template);
}
/**
* Set the regular expression used to identify URI templates
*
* @param string $regex Regular expression
*
* @return UriTemplate
*/
public function setRegex($regex)
{
$this->regex = $regex;
return $this;
}
/**
* Parse an expression into parts
*
* @param string $expression Expression to parse
*
* @return array Returns an associative array of parts
*/
private function parseExpression($expression)
{
// Check for URI operators
$operator = '';
if (in_array($expression[0], self::$operators)) {
$operator = $expression[0];
$expression = substr($expression, 1);
}
return array(
'operator' => $operator,
'values' => array_map(function($value) {
$value = trim($value);
$varspec = array();
$substrPos = strpos($value, ':');
if ($substrPos) {
$varspec['value'] = substr($value, 0, $substrPos);
$varspec['modifier'] = ':';
$varspec['position'] = (int) substr($value, $substrPos + 1);
} else if (substr($value, -1) == '*') {
$varspec['modifier'] = '*';
$varspec['value'] = substr($value, 0, -1);
} else {
$varspec['value'] = (string) $value;
$varspec['modifier'] = '';
}
return $varspec;
}, explode(',', $expression))
);
}
/**
* Process an expansion
*
* @param array $matches Matches met in the preg_replace_callback
*
* @return string Returns the replacement string
*/
private function expandMatch(array $matches)
{
$parsed = self::parseExpression($matches[1]);
$replacements = array();
$prefix = $parsed['operator'];
$joiner = $parsed['operator'];
$useQueryString = false;
if ($parsed['operator'] == '?') {
$joiner = '&';
$useQueryString = true;
} else if ($parsed['operator'] == '&') {
$useQueryString = true;
} else if ($parsed['operator'] == '#') {
$joiner = ',';
} else if ($parsed['operator'] == ';') {
$useQueryString = true;
} else if ($parsed['operator'] == '' || $parsed['operator'] == '+') {
$joiner = ',';
$prefix = '';
}
foreach ($parsed['values'] as $value) {
if (!array_key_exists($value['value'], $this->variables)) {
continue;
}
$variable = $this->variables[$value['value']];
$actuallyUseQueryString = $useQueryString;
$expanded = '';
if (is_array($variable)) {
$isAssoc = $this->isAssoc($variable);
$kvp = array();
foreach ($variable as $key => $var) {
if ($isAssoc) {
$key = rawurlencode($key);
}
$var = rawurlencode($var);
if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
$var = $this->decodeReserved($var);
}
if ($value['modifier'] == '*') {
if ($isAssoc) {
$var = $key . '=' . $var;
} else if ($key > 0 && $actuallyUseQueryString) {
$var = $value['value'] . '=' . $var;
}
}
$kvp[$key] = $var;
}
if ($value['modifier'] == '*') {
$expanded = implode($joiner, $kvp);
if ($isAssoc) {
// Don't prepend the value name when using the explode
// modifier with an associative array
$actuallyUseQueryString = false;
}
} else {
if ($isAssoc) {
// When an associative array is encountered and the
// explode modifier is not set, then the result must
// be a comma separated list of keys followed by their
// respective values.
foreach ($kvp as $k => &$v) {
$v = $k . ',' . $v;
}
}
$expanded = implode(',', $kvp);
}
} else {
if ($value['modifier'] == ':') {
$variable = substr($variable, 0, $value['position']);
}
$expanded = rawurlencode($variable);
if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
$expanded = $this->decodeReserved($expanded);
}
}
if ($actuallyUseQueryString) {
if (!$expanded && $joiner != '&') {
$expanded = $value['value'];
} else {
$expanded = $value['value'] . '=' . $expanded;
}
}
$replacements[] = $expanded;
}
$ret = implode($joiner, $replacements);
if ($ret && $prefix) {
return $prefix . $ret;
}
return $ret;
}
/**
* Determines if an array is associative
*
* @param array $array Array to check
*
* @return bool
*/
private function isAssoc(array $array)
{
return (bool) count(array_filter(array_keys($array), 'is_string'));
}
/**
* Removes percent encoding on reserved characters (used with + and # modifiers)
*
* @param string $string String to fix
*
* @return string
*/
private function decodeReserved($string)
{
return str_replace(self::$delimsPct, self::$delims, $string);
}
}

View File

@ -5,6 +5,7 @@ namespace Guzzle\Service\Command;
use Guzzle\Guzzle;
use Guzzle\Http\EntityBody;
use Guzzle\Http\Url;
use Guzzle\Http\UriTemplate;
use Guzzle\Service\Inspector;
/**
@ -27,28 +28,25 @@ class DynamicCommand extends AbstractCommand
*/
protected function build()
{
// Get the path values and use the client config settings
$pathValues = $this->getClient()->getConfig();
$foundPath = false;
foreach ($this->apiCommand->getParams() as $name => $arg) {
if ($arg->get('location') == 'path') {
$pathValues->set($name, $arg->get('prepend') . $this->get($name) . $arg->get('append'));
$foundPath = true;
}
}
// Build a custom URL if there are path values
if ($foundPath) {
$path = str_replace('//', '', Guzzle::inject($this->apiCommand->getPath(), $pathValues));
} else {
$path = $this->apiCommand->getPath();
}
if (!$path) {
if (!$this->apiCommand->getUri()) {
$url = $this->getClient()->getBaseUrl();
} else {
// Get the path values and use the client config settings
$variables = $this->getClient()->getConfig()->getAll();
foreach ($this->apiCommand->getParams() as $name => $arg) {
if (is_scalar($this->get($name))) {
$variables[$name] = $arg->get('prepend') . $this->get($name) . $arg->get('append');
}
}
// Expand the URI template using the URI values
$template = new UriTemplate($this->apiCommand->getUri());
$uri = $template->expand($variables);
// Merge the client's base URL with the URI template
$url = Url::factory($this->getClient()->getBaseUrl());
$url->combine($path);
$url->combine($uri);
$url = (string) $url;
}
@ -61,11 +59,7 @@ class DynamicCommand extends AbstractCommand
if ($this->get($name)) {
// Check that a location is set
$location = $arg->get('location') ?: 'query';
if ($location == 'path' || $location == 'data') {
continue;
}
$location = $arg->get('location');
if ($location) {

View File

@ -26,7 +26,7 @@ class ApiCommand
* string name Name of the command
* string doc Method documentation
* string method HTTP method of the command
* string path (optional) Path routing information of the command to include in the path
* string uri (optional) URI routing information of the command
* string class (optional) Concrete class that implements this command
* array params Associative array of parameters for the command with each
* parameter containing the following keys:
@ -51,7 +51,12 @@ class ApiCommand
$this->config['name'] = isset($config['name']) ? trim($config['name']) : '';
$this->config['doc'] = isset($config['doc']) ? trim($config['doc']) : '';
$this->config['method'] = isset($config['method']) ? trim($config['method']) : '';
$this->config['path'] = isset($config['path']) ? trim($config['path']) : '';
$this->config['uri'] = isset($config['uri']) ? trim($config['uri']) : '';
if (!$this->config['uri']) {
// Add backwards compatibility with the path attribute
$this->config['uri'] = isset($config['path']) ? trim($config['path']) : '';
}
$this->config['class'] = isset($config['class']) ? trim($config['class']) : 'Guzzle\\Service\\Command\\DynamicCommand';
if (isset($config['params']) && is_array($config['params'])) {
@ -136,13 +141,12 @@ class ApiCommand
}
/**
* Get the path routing information to append to the path of the generated
* request
* Get the URI that will be merged into the generated request
*
* @return string
*/
public function getPath()
public function getUri()
{
return $this->config['path'];
return $this->config['uri'];
}
}

View File

@ -279,7 +279,7 @@ class Inspector
}
// Inject configuration information into the config value
if (is_scalar($config->get($name)) && strpos($config->get($name), '{{') !== false) {
if (is_scalar($config->get($name)) && strpos($config->get($name), '{') !== false) {
$config->set($name, Guzzle::inject($config->get($name), $config));
}

View File

@ -133,8 +133,8 @@ class ServiceBuilder implements \ArrayAccess
// Convert references to the actual client
foreach ($this->builderConfig[$name]['params'] as $k => &$v) {
if (0 === strpos($v, '{{') && strlen($v) - 2 == strpos($v, '}}')) {
$v = $this->get(trim(substr($v, 2, -2)));
if (0 === strpos($v, '{') && strlen($v) - 1 == strrpos($v, '}')) {
$v = $this->get(trim(str_replace(array('{', '}'), '', $v)));
}
}

View File

@ -50,6 +50,7 @@ class GuzzleTest extends GuzzleTestCase
'abc' => 'this'
)),
array('_is_a_', '{{ abc }}_is_{{ not_found }}a_{{ 0 }}', array()),
array('_is_a_', '{abc}_is_{not_found}a_{{0}}', array()),
);
}

View File

@ -5,6 +5,7 @@ namespace Guzzle\Tests\Http;
use Guzzle\Guzzle;
use Guzzle\Common\Collection;
use Guzzle\Common\Log\ClosureLogAdapter;
use Guzzle\Http\UriTemplate;
use Guzzle\Http\Message\Response;
use Guzzle\Http\Message\RequestFactory;
use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
@ -101,7 +102,7 @@ class ClientTest extends \Guzzle\Tests\GuzzleTestCase
/**
* @covers Guzzle\Http\Client
*/
public function testInjectConfig()
public function testExpandsUriTemplatesUsingConfig()
{
$client = new Client('http://www.google.com/');
$client->setConfig(array(
@ -109,7 +110,7 @@ class ClientTest extends \Guzzle\Tests\GuzzleTestCase
'key' => 'value',
'foo' => 'bar'
));
$this->assertEquals('Testing...api/v1/key/value', $client->inject('Testing...api/{{api}}/key/{{key}}'));
$this->assertEquals('Testing...api/v1/key/value', $client->expandTemplate('Testing...api/{api}/key/{{key}}'));
// Make sure that the client properly validates and injects config
$this->assertEquals('bar', $client->getConfig('foo'));
@ -429,4 +430,114 @@ class ClientTest extends \Guzzle\Tests\GuzzleTestCase
$client->getEventDispatcher()->addSubscriber($mock);
$client->send(array($client->get(), $client->head()));
}
/**
* @covers Guzzle\Http\Client
*/
public function testQueryStringsAreNotDoubleEncoded()
{
$client = new Client('http://test.com', array(
'path' => array('foo', 'bar'),
'query' => 'hi there',
'data' => array(
'test' => 'a&b'
)
));
$request = $client->get('{/path*}{?query,data*}');
$this->assertEquals('http://test.com/foo/bar?query=hi%20there&test=a%26b', $request->getUrl());
$this->assertEquals('hi there', $request->getQuery()->get('query'));
$this->assertEquals('a&b', $request->getQuery()->get('test'));
}
/**
* @covers Guzzle\Http\Client
*/
public function testQueryStringsAreNotDoubleEncodedUsingAbsolutePaths()
{
$client = new Client('http://test.com', array(
'path' => array('foo', 'bar'),
'query' => 'hi there',
));
$request = $client->get('http://test.com{?query}');
$this->assertEquals('http://test.com/?query=hi%20there', $request->getUrl());
$this->assertEquals('hi there', $request->getQuery()->get('query'));
}
/**
* @covers Guzzle\Http\Client::setUriTemplate
* @covers Guzzle\Http\Client::getUriTemplate
*/
public function testAllowsUriTemplateInjection()
{
$client = new Client('http://test.com', array(
'path' => array('foo', 'bar'),
'query' => 'hi there',
));
$a = $client->getUriTemplate();
$this->assertSame($a, $client->getUriTemplate());
$client->setUriTemplate(new UriTemplate());
$this->assertNotSame($a, $client->getUriTemplate());
}
/**
* @covers Guzzle\Http\Client::expandTemplate
*/
public function testAllowsCustomVariablesWhenExpandingTemplates()
{
$client = new Client('http://test.com', array(
'test' => 'hi',
));
$uri = $client->expandTemplate('http://{test}{?query*}', array(
'query' => array(
'han' => 'solo'
)
));
$this->assertEquals('http://hi?han=solo', $uri);
}
/**
* @covers Guzzle\Http\Client::createRequest
* @expectedException InvalidArgumentException
*/
public function testUriArrayMustContainExactlyTwoElements()
{
$client = new Client();
$client->createRequest('GET', array('haha!'));
}
/**
* @covers Guzzle\Http\Client::createRequest
* @expectedException InvalidArgumentException
*/
public function testUriArrayMustContainAnArray()
{
$client = new Client();
$client->createRequest('GET', array('haha!', 'test'));
}
/**
* @covers Guzzle\Http\Client::createRequest
* @covers Guzzle\Http\Client::get
* @covers Guzzle\Http\Client::put
* @covers Guzzle\Http\Client::post
* @covers Guzzle\Http\Client::head
* @covers Guzzle\Http\Client::options
*/
public function testUriArrayAllowsCustomTemplateVariables()
{
$client = new Client();
$vars = array(
'var' => 'hi'
);
$this->assertEquals('/hi', (string) $client->createRequest('GET', array('/{var}', $vars))->getUrl());
$this->assertEquals('/hi', (string) $client->get(array('/{var}', $vars))->getUrl());
$this->assertEquals('/hi', (string) $client->put(array('/{var}', $vars))->getUrl());
$this->assertEquals('/hi', (string) $client->post(array('/{var}', $vars))->getUrl());
$this->assertEquals('/hi', (string) $client->head(array('/{var}', $vars))->getUrl());
$this->assertEquals('/hi', (string) $client->options(array('/{var}', $vars))->getUrl());
}
}

View File

@ -0,0 +1,188 @@
<?php
namespace Guzzle\Tests\Http;
use Guzzle\Http\UriTemplate;
/**
* @covers Guzzle\Http\UriTemplate
*/
class UriTemplateTest extends \Guzzle\Tests\GuzzleTestCase
{
/**
* @return array
*/
public function templateProvider()
{
$t = array();
// Level 1 template tests
$params = array(
'var' => 'value',
'hello' => 'Hello World!',
'empty' => '',
'path' => '/foo/bar',
'x' => '1024',
'y' => '768',
'list' => array('red', 'green', 'blue'),
'keys' => array(
"semi" => ';',
"dot" => '.',
"comma" => ','
)
);
return array_map(function($t) use ($params) {
$t[] = $params;
return $t;
}, array(
array('{var}', 'value'),
array('{hello}', 'Hello%20World%21'),
array('{+var}', 'value'),
array('{+hello}', 'Hello%20World!'),
array('{+path}/here', '/foo/bar/here'),
array('here?ref={+path}', 'here?ref=/foo/bar'),
array('X{#var}', 'X#value'),
array('X{#hello}', 'X#Hello%20World!'),
array('map?{x,y}', 'map?1024,768'),
array('{x,hello,y}', '1024,Hello%20World%21,768'),
array('{+x,hello,y}', '1024,Hello%20World!,768'),
array('{+path,x}/here', '/foo/bar,1024/here'),
array('{#x,hello,y}', '#1024,Hello%20World!,768'),
array('{#path,x}/here', '#/foo/bar,1024/here'),
array('X{.var}', 'X.value'),
array('X{.x,y}', 'X.1024.768'),
array('{/var}', '/value'),
array('{/var,x}/here', '/value/1024/here'),
array('{;x,y}', ';x=1024;y=768'),
array('{;x,y,empty}', ';x=1024;y=768;empty'),
array('{?x,y}', '?x=1024&y=768'),
array('{?x,y,empty}', '?x=1024&y=768&empty='),
array('?fixed=yes{&x}', '?fixed=yes&x=1024'),
array('{&x,y,empty}', '&x=1024&y=768&empty='),
array('{var:3}', 'val'),
array('{var:30}', 'value'),
array('{list}', 'red,green,blue'),
array('{list*}', 'red,green,blue'),
array('{keys}', 'semi,%3B,dot,.,comma,%2C'),
array('{keys*}', 'semi=%3B,dot=.,comma=%2C'),
array('{+path:6}/here', '/foo/b/here'),
array('{+list}', 'red,green,blue'),
array('{+list*}', 'red,green,blue'),
array('{+keys}', 'semi,;,dot,.,comma,,'),
array('{+keys*}', 'semi=;,dot=.,comma=,'),
array('{#path:6}/here', '#/foo/b/here'),
array('{#list}', '#red,green,blue'),
array('{#list*}', '#red,green,blue'),
array('{#keys}', '#semi,;,dot,.,comma,,'),
array('{#keys*}', '#semi=;,dot=.,comma=,'),
array('X{.var:3}', 'X.val'),
array('X{.list}', 'X.red,green,blue'),
array('X{.list*}', 'X.red.green.blue'),
array('X{.keys}', 'X.semi,%3B,dot,.,comma,%2C'),
array('X{.keys*}', 'X.semi=%3B.dot=..comma=%2C'),
array('{/var:1,var}', '/v/value'),
array('{/list}', '/red,green,blue'),
array('{/list*}', '/red/green/blue'),
array('{/list*,path:4}', '/red/green/blue/%2Ffoo'),
array('{/keys}', '/semi,%3B,dot,.,comma,%2C'),
array('{/keys*}', '/semi=%3B/dot=./comma=%2C'),
array('{;hello:5}', ';hello=Hello'),
array('{;list}', ';list=red,green,blue'),
array('{;list*}', ';list=red;list=green;list=blue'),
array('{;keys}', ';keys=semi,%3B,dot,.,comma,%2C'),
array('{;keys*}', ';semi=%3B;dot=.;comma=%2C'),
array('{?var:3}', '?var=val'),
array('{?list}', '?list=red,green,blue'),
array('{?list*}', '?list=red&list=green&list=blue'),
array('{?keys}', '?keys=semi,%3B,dot,.,comma,%2C'),
array('{?keys*}', '?semi=%3B&dot=.&comma=%2C'),
array('{&var:3}', '&var=val'),
array('{&list}', '&list=red,green,blue'),
array('{&list*}', '&list=red&list=green&list=blue'),
array('{&keys}', '&keys=semi,%3B,dot,.,comma,%2C'),
array('{&keys*}', '&semi=%3B&dot=.&comma=%2C'),
// Test that missing expansions are skipped
array('test{&missing*}', 'test'),
// Test that multiple expansions can be set
array('http://{var}/{var:2}{?keys*}', 'http://value/va?semi=%3B&dot=.&comma=%2C'),
// Test that it is backwards compatible with {{ }} syntax
array('{{var}}|{{var:3}}', 'value|val'),
// Test more complex query string stuff
array('http://www.test.com{+path}{?var,keys*}', 'http://www.test.com/foo/bar?var=value&semi=%3B&dot=.&comma=%2C')
));
}
/**
* @dataProvider templateProvider
*/
public function testExpandsUriTemplates($template, $expansion, $params)
{
$uri = new UriTemplate($template);
$this->assertEquals($template, $uri->getTemplate());
$result = $uri->expand($params);
$this->assertEquals($expansion, $result);
}
public function expressionProvider()
{
return array(
array(
'{+var*}', array(
'operator' => '+',
'values' => array(
array('value' => 'var', 'modifier' => '*')
)
),
),
array(
'{?keys,var,val}', array(
'operator' => '?',
'values' => array(
array('value' => 'keys', 'modifier' => ''),
array('value' => 'var', 'modifier' => ''),
array('value' => 'val', 'modifier' => '')
)
),
),
array(
'{+x,hello,y}', array(
'operator' => '+',
'values' => array(
array('value' => 'x', 'modifier' => ''),
array('value' => 'hello', 'modifier' => ''),
array('value' => 'y', 'modifier' => '')
)
)
)
);
}
/**
* @dataProvider expressionProvider
*/
public function testParsesExpressions($exp, $data)
{
$template = new UriTemplate($exp);
// Access the config object
$class = new \ReflectionClass($template);
$method = $class->getMethod('parseExpression');
$method->setAccessible(true);
$exp = substr($exp, 1, -1);
$this->assertEquals($data, $method->invokeArgs($template, array($exp)));
}
/**
* @covers Guzzle\Http\UriTemplate::setRegex
*/
public function testAllowsCustomUriTemplateRegex()
{
$template = new UriTemplate('abc_<$var>');
$template->setRegex('/\<\$(.+)\>/');
$this->assertEquals('abc_hi', $template->expand(array(
'var' => 'hi'
)));
}
}

View File

@ -27,16 +27,14 @@ class DynamicCommandTest extends \Guzzle\Tests\GuzzleTestCase
'test_command' => new ApiCommand(array(
'doc' => 'documentationForCommand',
'method' => 'HEAD',
'path' => '/{{key}}',
'uri' => '{/key}',
'params' => array(
'bucket' => array(
'required' => true,
'append' => '.',
'location' => 'path'
'append' => '.'
),
'key' => array(
'location' => 'path',
'prepend' => '/'
'prepend' => 'hi_'
),
'acl' => array(
'location' => 'query'
@ -106,12 +104,12 @@ class DynamicCommandTest extends \Guzzle\Tests\GuzzleTestCase
$request = $command->setClient($client)->prepare();
// Ensure that the path values were injected into the path and base_url
$this->assertEquals('/key', $request->getPath());
$this->assertEquals('/hi_key', $request->getPath());
$this->assertEquals('www.example.com', $request->getHost());
// Check the complete request
$this->assertEquals(
"HEAD /key HTTP/1.1\r\n" .
"HEAD /hi_key HTTP/1.1\r\n" .
"Host: www.example.com\r\n" .
"User-Agent: " . Guzzle::getDefaultUserAgent() . "\r\n" .
"\r\n", (string) $request);

View File

@ -35,7 +35,7 @@ class ApiCommandTest extends \Guzzle\Tests\GuzzleTestCase
$this->assertEquals('test', $c->getName());
$this->assertEquals('doc', $c->getDoc());
$this->assertEquals('POST', $c->getMethod());
$this->assertEquals('/api/v1', $c->getPath());
$this->assertEquals('/api/v1', $c->getUri());
$this->assertEquals('Guzzle\\Service\\Command\\DynamicCommand', $c->getConcreteClass());
$this->assertEquals(array(
'key' => new Collection(array(
@ -86,7 +86,7 @@ class ApiCommandTest extends \Guzzle\Tests\GuzzleTestCase
'class' => 'Guzzle\\Service\\Command\ClosureCommand',
'doc' => 'test',
'method' => 'PUT',
'path' => '/',
'uri' => '/',
'params' => array(
'p' => new Collection(array(
'name' => 'foo'

View File

@ -22,8 +22,8 @@ class JsonDescriptionBuilderTest extends \Guzzle\Tests\GuzzleTestCase
$description = JsonDescriptionBuilder::build(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'TestData' . DIRECTORY_SEPARATOR . 'test_service.json');
$this->assertTrue($description->hasCommand('test'));
$test = $description->getCommand('test');
$this->assertEquals('/path', $test->getPath());
$this->assertEquals('/path', $test->getUri());
$test = $description->getCommand('concrete');
$this->assertEquals('/abstract', $test->getPath());
$this->assertEquals('/abstract', $test->getUri());
}
}

View File

@ -37,7 +37,7 @@ class ServiceDescriptionTest extends \Guzzle\Tests\GuzzleTestCase
));
$c = $d->getCommand('concrete');
$this->assertEquals('/test', $c->getPath());
$this->assertEquals('/test', $c->getUri());
$this->assertEquals('GET', $c->getMethod());
$params = $c->getParams();
$param = $params['test'];

View File

@ -46,7 +46,7 @@ class XmlDescriptionBuilderTest extends \Guzzle\Tests\GuzzleTestCase
), $command->getParam('bucket')->getAll());
$this->assertEquals('DELETE', $command->getMethod());
$this->assertEquals('{{ bucket }}/{{ key }}{{ format }}', $command->getPath());
$this->assertEquals('{{ bucket }}/{{ key }}{{ format }}', $command->getUri());
$this->assertEquals('Documentation', $command->getDoc());
$this->assertArrayHasKey('custom_filter', Inspector::getInstance()->getRegisteredConstraints());
@ -59,6 +59,6 @@ class XmlDescriptionBuilderTest extends \Guzzle\Tests\GuzzleTestCase
{
$service = XmlDescriptionBuilder::build(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'TestData' . DIRECTORY_SEPARATOR . 'test_service.xml');
$command = $service->getCommand('concrete');
$this->assertEquals('/test', $command->getPath());
$this->assertEquals('/test', $command->getUri());
}
}

View File

@ -16,7 +16,7 @@
<!-- Add commands to the service -->
<commands>
<command name="test" method="DELETE" path="{{ bucket }}/{{ key }}{{ format }}" min_args="2">
<command name="test" method="DELETE" uri="{{ bucket }}/{{ key }}{{ format }}" min_args="2">
<doc>Documentation</doc>
<param name="format" required="false" default="json" type="choice:json,xml" location="path" prepend="." />
<param name="bucket" required="true" location="path" doc="Bucket location" />
@ -36,18 +36,18 @@
<param name="result_type" type="string" required="false"/>
</command>
<command name="trends.location" method="GET" min_args="1" path="/trends/{{ woeid }}">
<command name="trends.location" method="GET" min_args="1" uri="/trends/{{ woeid }}">
<param name="woeid" type="integer" required="true" location="path" />
<param name="acl" required="true" location="header:X-Amz-Acl" />
</command>
<command name="geo.id" method="GET" auth_required="true" path="/geo/id/{{ place_id }}">
<command name="geo.id" method="GET" auth_required="true" uri="/geo/id/{{ place_id }}">
<param name="place_id" type="string" required="true" prepend="/" append=".id" />
<!-- The request builder will remove '//', so it's okay to always prepend / if needed -->
<param name="second" type="string" required="true" prepend="/" />
</command>
<command name="path" method="GET" path="/test" />
<command name="path" method="GET" uri="/test" />
<command name="concrete" extends="abstract" />