mirror of
https://github.com/guzzle/guzzle.git
synced 2025-02-12 11:30:49 +01:00
Moving query aggregators to callables
This commit is contained in:
parent
2b779dccb3
commit
ce56db078b
@ -5,8 +5,6 @@ namespace GuzzleHttp\Message\Post;
|
||||
use GuzzleHttp\Message\RequestInterface;
|
||||
use GuzzleHttp\Stream\Stream;
|
||||
use GuzzleHttp\Stream\StreamInterface;
|
||||
use GuzzleHttp\QueryAggregator\PhpAggregator;
|
||||
use GuzzleHttp\QueryAggregator\QueryAggregatorInterface;
|
||||
use GuzzleHttp\Query;
|
||||
|
||||
/**
|
||||
@ -17,7 +15,7 @@ class PostBody implements PostBodyInterface
|
||||
/** @var StreamInterface */
|
||||
private $body;
|
||||
|
||||
/** @var QueryAggregatorInterface */
|
||||
/** @var callable */
|
||||
private $aggregator;
|
||||
|
||||
private $fields = [];
|
||||
@ -57,11 +55,15 @@ class PostBody implements PostBodyInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the aggregation strategy that will be used to turn multi-valued fields into a string
|
||||
* Set the aggregation strategy that will be used to turn multi-valued
|
||||
* fields into a string.
|
||||
*
|
||||
* @param QueryAggregatorInterface $aggregator
|
||||
* The aggregation function accepts a deeply nested array of query string
|
||||
* values and returns a flattened associative array of key value pairs.
|
||||
*
|
||||
* @param callable $aggregator
|
||||
*/
|
||||
final public function setAggregator(QueryAggregatorInterface $aggregator)
|
||||
final public function setAggregator(callable $aggregator)
|
||||
{
|
||||
$this->aggregator = $aggregator;
|
||||
}
|
||||
@ -224,12 +226,12 @@ class PostBody implements PostBodyInterface
|
||||
/**
|
||||
* Get the aggregator used to join multi-valued field parameters
|
||||
*
|
||||
* @return QueryAggregatorInterface
|
||||
* @return callable
|
||||
*/
|
||||
final protected function getAggregator()
|
||||
{
|
||||
if (!$this->aggregator) {
|
||||
$this->aggregator = new PhpAggregator();
|
||||
$this->aggregator = Query::phpAggregator();
|
||||
}
|
||||
|
||||
return $this->aggregator;
|
||||
|
103
src/Query.php
103
src/Query.php
@ -2,10 +2,6 @@
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\QueryAggregator\QueryAggregatorInterface;
|
||||
use GuzzleHttp\QueryAggregator\DuplicateAggregator;
|
||||
use GuzzleHttp\QueryAggregator\PhpAggregator;
|
||||
|
||||
/**
|
||||
* Manages query string variables and can aggregate them into a string
|
||||
*/
|
||||
@ -17,7 +13,7 @@ class Query extends Collection
|
||||
/** @var bool URL encode fields and values */
|
||||
private $encoding = self::RFC3986;
|
||||
|
||||
/** @var QueryAggregatorInterface */
|
||||
/** @var callable */
|
||||
private $aggregator;
|
||||
|
||||
/**
|
||||
@ -60,7 +56,7 @@ class Query extends Collection
|
||||
|
||||
// Use the duplicate aggregator if duplicates were found and not using PHP style arrays
|
||||
if ($foundDuplicates && !$foundPhpStyle) {
|
||||
$q->setAggregator(new DuplicateAggregator());
|
||||
$q->setAggregator(self::duplicateAggregator());
|
||||
}
|
||||
|
||||
return $q;
|
||||
@ -77,12 +73,20 @@ class Query extends Collection
|
||||
return '';
|
||||
}
|
||||
|
||||
// The default aggregator is statically cached
|
||||
static $defaultAggregator;
|
||||
|
||||
if (!$this->aggregator) {
|
||||
$this->aggregator = new PhpAggregator();
|
||||
if (!$defaultAggregator) {
|
||||
$defaultAggregator = self::phpAggregator();
|
||||
}
|
||||
$this->aggregator = $defaultAggregator;
|
||||
}
|
||||
|
||||
$result = '';
|
||||
foreach ($this->aggregator->aggregate($this->data) as $key => $values) {
|
||||
$aggregator = $this->aggregator;
|
||||
|
||||
foreach ($aggregator($this->data) as $key => $values) {
|
||||
foreach ($values as $value) {
|
||||
if ($result) {
|
||||
$result .= '&';
|
||||
@ -110,13 +114,20 @@ class Query extends Collection
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls how multi-valued query string parameters are aggregated into a string
|
||||
* Controls how multi-valued query string parameters are aggregated into a
|
||||
* string.
|
||||
*
|
||||
* @param QueryAggregatorInterface $aggregator Converts an array of query string variables into a string
|
||||
* $query->setAggregator($query::duplicateAggregator());
|
||||
*
|
||||
* @param callable $aggregator Callable used to converts a deeply nested
|
||||
* array of query string variables into a flattened array of key value
|
||||
* pairs. The callable accepts an array of query data and returns a
|
||||
* flattened array of key value pairs where each value is an array of
|
||||
* strings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setAggregator(QueryAggregatorInterface $aggregator)
|
||||
public function setAggregator(callable $aggregator)
|
||||
{
|
||||
$this->aggregator = $aggregator;
|
||||
|
||||
@ -141,4 +152,74 @@ class Query extends Collection
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query string aggregator that does not aggregate nested query string
|
||||
* values and allows duplicates in the resulting array.
|
||||
*
|
||||
* Example: http://test.com?q=1&q=2
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function duplicateAggregator()
|
||||
{
|
||||
return function (array $data) {
|
||||
return self::walkQuery($data, '', function ($key, $prefix) {
|
||||
return is_int($key) ? $prefix : "{$prefix}[{$key}]";
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates nested query string variables using the same techinque as
|
||||
* ``http_build_query()``.
|
||||
*
|
||||
* @param bool $numericIndices Pass false to not include numeric indices
|
||||
* when multi-values query string parameters are present.
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function phpAggregator($numericIndices = true)
|
||||
{
|
||||
return function (array $data) use ($numericIndices) {
|
||||
return self::walkQuery(
|
||||
$data,
|
||||
'',
|
||||
function ($key, $prefix) use ($numericIndices) {
|
||||
return !$numericIndices && is_int($key)
|
||||
? "{$prefix}[]"
|
||||
: "{$prefix}[{$key}]";
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Easily create query aggregation functions by providing a key prefix
|
||||
* function to this query string array walker.
|
||||
*
|
||||
* @param array $query Query string to walk
|
||||
* @param string $keyPrefix Key prefix (start with '')
|
||||
* @param callable $prefixer Function used to create a key prefix
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function walkQuery(array $query, $keyPrefix, callable $prefixer)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($query as $key => $value) {
|
||||
if ($keyPrefix) {
|
||||
$key = $prefixer($key, $keyPrefix);
|
||||
}
|
||||
if (is_array($value)) {
|
||||
$result += self::walkQuery($value, $key, $prefixer);
|
||||
} elseif (isset($result[$key])) {
|
||||
$result[$key][] = $value;
|
||||
} else {
|
||||
$result[$key] = array($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\QueryAggregator;
|
||||
|
||||
abstract class AbstractAggregator implements QueryAggregatorInterface
|
||||
{
|
||||
public function aggregate(array $query)
|
||||
{
|
||||
return $this->walkQuery($query, '');
|
||||
}
|
||||
|
||||
protected function walkQuery(array $query, $keyPrefix)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($query as $key => $value) {
|
||||
if ($keyPrefix) {
|
||||
$key = $this->createPrefixKey($key, $keyPrefix);
|
||||
}
|
||||
if (is_array($value)) {
|
||||
$result += $this->walkQuery($value, $key);
|
||||
} elseif (isset($result[$key])) {
|
||||
$result[$key][] = $value;
|
||||
} else {
|
||||
$result[$key] = array($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a key for a key and prefix
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $keyPrefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function createPrefixKey($key, $keyPrefix);
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\QueryAggregator;
|
||||
|
||||
/**
|
||||
* Aggregates nested query string variables using PHP style arrays, but does not
|
||||
* combine duplicate values of the same name under array keys.
|
||||
*/
|
||||
class DuplicateAggregator extends AbstractAggregator
|
||||
{
|
||||
protected function createPrefixKey($key, $keyPrefix)
|
||||
{
|
||||
return is_int($key) ? $keyPrefix : "{$keyPrefix}[{$key}]";
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\QueryAggregator;
|
||||
|
||||
/**
|
||||
* Aggregates nested query string variables using PHP style []
|
||||
*/
|
||||
class PhpAggregator extends AbstractAggregator
|
||||
{
|
||||
private $numericIndices;
|
||||
|
||||
/**
|
||||
* @param bool $numericIndices Set to false to disable numeric indices (e.g. foo[] = bar vs foo[0] = bar)
|
||||
*/
|
||||
public function __construct($numericIndices = true)
|
||||
{
|
||||
$this->numericIndices = $numericIndices;
|
||||
}
|
||||
|
||||
protected function createPrefixKey($key, $prefix)
|
||||
{
|
||||
return !$this->numericIndices && is_int($key) ? "{$prefix}[]" : "{$prefix}[{$key}]";
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\QueryAggregator;
|
||||
|
||||
/**
|
||||
* Interface used for aggregating multi-value query string parameters into a flattened array
|
||||
*/
|
||||
interface QueryAggregatorInterface
|
||||
{
|
||||
/**
|
||||
* Aggregate a query string array into a flattened array
|
||||
*
|
||||
* @param array $query Query string parameters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function aggregate(array $query);
|
||||
}
|
@ -5,7 +5,7 @@ namespace GuzzleHttp\Tests\Message\Post;
|
||||
use GuzzleHttp\Message\Post\PostBody;
|
||||
use GuzzleHttp\Message\Request;
|
||||
use GuzzleHttp\Message\Post\PostFile;
|
||||
use GuzzleHttp\QueryAggregator\DuplicateAggregator;
|
||||
use GuzzleHttp\Query;
|
||||
|
||||
/**
|
||||
* @covers GuzzleHttp\Message\Post\PostBody
|
||||
@ -112,7 +112,7 @@ class PostBodyTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('foo%5B0%5D=baz&foo%5B1%5D=bar', (string) $b);
|
||||
$b = new PostBody();
|
||||
$b->setField('foo', ['baz', 'bar']);
|
||||
$agg = new DuplicateAggregator();
|
||||
$agg = Query::duplicateAggregator();
|
||||
$b->setAggregator($agg);
|
||||
$this->assertEquals('foo=baz&foo=bar', (string) $b);
|
||||
}
|
||||
|
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Tests\QueryAggregator;
|
||||
|
||||
use GuzzleHttp\QueryAggregator\DuplicateAggregator;
|
||||
|
||||
/**
|
||||
* @covers \GuzzleHttp\QueryAggregator\DuplicateAggregator
|
||||
* @covers \GuzzleHttp\QueryAggregator\AbstractAggregator
|
||||
*/
|
||||
class DuplicateAggregatorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $encodeData = [
|
||||
't' => [
|
||||
'v1' => ['a', '1'],
|
||||
'v2' => 'b',
|
||||
'v3' => ['v4' => 'c', 'v5' => 'd']
|
||||
]
|
||||
];
|
||||
|
||||
public function testEncodes()
|
||||
{
|
||||
$agg = new DuplicateAggregator();
|
||||
$result = $agg->aggregate($this->encodeData);
|
||||
$this->assertEquals(array(
|
||||
't[v1]' => ['a', '1'],
|
||||
't[v2]' => ['b'],
|
||||
't[v3][v4]' => ['c'],
|
||||
't[v3][v5]' => ['d'],
|
||||
), $result);
|
||||
}
|
||||
|
||||
public function testEncodesNoNumericIndices()
|
||||
{
|
||||
$agg = new DuplicateAggregator(false);
|
||||
$result = $agg->aggregate($this->encodeData);
|
||||
$this->assertEquals(array(
|
||||
't[v1]' => ['a', '1'],
|
||||
't[v2]' => ['b'],
|
||||
't[v3][v4]' => ['c'],
|
||||
't[v3][v5]' => ['d'],
|
||||
), $result);
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Tests\QueryAggregator;
|
||||
|
||||
use GuzzleHttp\QueryAggregator\PhpAggregator;
|
||||
|
||||
/**
|
||||
* @covers \GuzzleHttp\QueryAggregator\PhpAggregator
|
||||
* @covers \GuzzleHttp\QueryAggregator\AbstractAggregator
|
||||
*/
|
||||
class PhpAggregatorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $encodeData = [
|
||||
't' => [
|
||||
'v1' => ['a', '1'],
|
||||
'v2' => 'b',
|
||||
'v3' => ['v4' => 'c', 'v5' => 'd']
|
||||
]
|
||||
];
|
||||
|
||||
public function testEncodes()
|
||||
{
|
||||
$agg = new PhpAggregator();
|
||||
$result = $agg->aggregate($this->encodeData);
|
||||
$this->assertEquals(array(
|
||||
't[v1][0]' => ['a'],
|
||||
't[v1][1]' => ['1'],
|
||||
't[v2]' => ['b'],
|
||||
't[v3][v4]' => ['c'],
|
||||
't[v3][v5]' => ['d'],
|
||||
), $result);
|
||||
}
|
||||
|
||||
public function testEncodesNoNumericIndices()
|
||||
{
|
||||
$agg = new PhpAggregator(false);
|
||||
$result = $agg->aggregate($this->encodeData);
|
||||
$this->assertEquals(array(
|
||||
't[v1][]' => ['a', '1'],
|
||||
't[v2]' => ['b'],
|
||||
't[v3][v4]' => ['c'],
|
||||
't[v3][v5]' => ['d'],
|
||||
), $result);
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
namespace GuzzleHttp\Tests;
|
||||
|
||||
use GuzzleHttp\Query;
|
||||
use GuzzleHttp\QueryAggregator\DuplicateAggregator;
|
||||
|
||||
class QueryTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -50,17 +49,10 @@ class QueryTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public function testCanSetAggregator()
|
||||
{
|
||||
$agg = $this->getMockBuilder('GuzzleHttp\QueryAggregator\QueryAggregatorInterface')
|
||||
->setMethods('aggregate')
|
||||
->getMockForAbstractClass();
|
||||
|
||||
$q = new Query(['foo' => ['bar', 'baz']]);
|
||||
$q->setAggregator($agg);
|
||||
|
||||
$agg->expects($this->once())
|
||||
->method('aggregate')
|
||||
->will($this->returnValue(['foo' => ['barANDbaz']]));
|
||||
|
||||
$q->setAggregator(function (array $data) {
|
||||
return ['foo' => ['barANDbaz']];
|
||||
});
|
||||
$this->assertEquals('foo=barANDbaz', (string) $q);
|
||||
}
|
||||
|
||||
@ -71,7 +63,7 @@ class QueryTest extends \PHPUnit_Framework_TestCase
|
||||
$q->add('facet', 'width');
|
||||
$q->add('facet.field', 'foo');
|
||||
// Use the duplicate aggregator
|
||||
$q->setAggregator(new DuplicateAggregator());
|
||||
$q->setAggregator($q::duplicateAggregator());
|
||||
$this->assertEquals('facet=size&facet=width&facet.field=foo', (string) $q);
|
||||
}
|
||||
|
||||
@ -173,4 +165,61 @@ class QueryTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$this->assertEquals('', (string) Query::fromString(''));
|
||||
}
|
||||
|
||||
private $encodeData = [
|
||||
't' => [
|
||||
'v1' => ['a', '1'],
|
||||
'v2' => 'b',
|
||||
'v3' => ['v4' => 'c', 'v5' => 'd']
|
||||
]
|
||||
];
|
||||
|
||||
public function testEncodesDuplicateAggregator()
|
||||
{
|
||||
$agg = Query::duplicateAggregator();
|
||||
$result = $agg($this->encodeData);
|
||||
$this->assertEquals(array(
|
||||
't[v1]' => ['a', '1'],
|
||||
't[v2]' => ['b'],
|
||||
't[v3][v4]' => ['c'],
|
||||
't[v3][v5]' => ['d'],
|
||||
), $result);
|
||||
}
|
||||
|
||||
public function testDuplicateEncodesNoNumericIndices()
|
||||
{
|
||||
$agg = Query::duplicateAggregator();
|
||||
$result = $agg($this->encodeData);
|
||||
$this->assertEquals(array(
|
||||
't[v1]' => ['a', '1'],
|
||||
't[v2]' => ['b'],
|
||||
't[v3][v4]' => ['c'],
|
||||
't[v3][v5]' => ['d'],
|
||||
), $result);
|
||||
}
|
||||
|
||||
public function testEncodesPhpAggregator()
|
||||
{
|
||||
$agg = Query::phpAggregator();
|
||||
$result = $agg($this->encodeData);
|
||||
$this->assertEquals(array(
|
||||
't[v1][0]' => ['a'],
|
||||
't[v1][1]' => ['1'],
|
||||
't[v2]' => ['b'],
|
||||
't[v3][v4]' => ['c'],
|
||||
't[v3][v5]' => ['d'],
|
||||
), $result);
|
||||
}
|
||||
|
||||
public function testPhpEncodesNoNumericIndices()
|
||||
{
|
||||
$agg = Query::phpAggregator(false);
|
||||
$result = $agg($this->encodeData);
|
||||
$this->assertEquals(array(
|
||||
't[v1][]' => ['a', '1'],
|
||||
't[v2]' => ['b'],
|
||||
't[v3][v4]' => ['c'],
|
||||
't[v3][v5]' => ['d'],
|
||||
), $result);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user