Skip to content

Commit

Permalink
Merge pull request #1710 from antograssiot/maintain-order-in-filters
Browse files Browse the repository at this point in the history
Ensure that OrderFilter preserves the query string parameters order
  • Loading branch information
dunglas committed Feb 16, 2018
2 parents d5591e0 + fb7cc47 commit 45c537f
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 2 deletions.
61 changes: 61 additions & 0 deletions features/doctrine/order_filter.feature
Expand Up @@ -300,6 +300,67 @@ Feature: Order filter on collections
}
"""

Scenario: Get collection ordered collection on several property keep the order
# Adding 30 more data with the same name
Given there are 30 dummy objects
When I send a "GET" request to "/dummies?order[name]=desc&order[id]=desc"
Then the response status code should be 200
Then print last JSON response
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be valid according to this schema:
"""
{
"type": "object",
"properties": {
"@context": {"pattern": "^/contexts/Dummy$"},
"@id": {"pattern": "^/dummies$"},
"@type": {"pattern": "^hydra:Collection$"},
"hydra:member": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"@id": {
"type": "string",
"pattern": "^/dummies/39$"
}
}
},
{
"type": "object",
"properties": {
"@id": {
"type": "string",
"pattern": "^/dummies/9$"
}
}
},
{
"type": "object",
"properties": {
"@id": {
"type": "string",
"pattern": "^/dummies/38$"
}
}
}
],
"additionalItems": false,
"maxItems": 3,
"minItems": 3
},
"hydra:view": {
"type": "object",
"properties": {
"@id": {"pattern": "^/dummies\\?order%5Bname%5D=desc"},
"@type": {"pattern": "^hydra:PartialCollectionView$"}
}
}
}
}
"""

Scenario: Get collection ordered in ascending order on an association and on which order filter has been enabled in whitelist mode
Given there are 30 dummy objects with relatedDummy
When I send a "GET" request to "/dummies?order[relatedDummy]=asc"
Expand Down
2 changes: 1 addition & 1 deletion src/EventListener/ReadListener.php
Expand Up @@ -63,7 +63,7 @@ public function onKernelRequest(GetResponseEvent $event)
}

if (null === $filters = $request->attributes->get('_api_filters')) {
$queryString = $request->getQueryString();
$queryString = RequestParser::getQueryString($request);
$filters = $queryString ? RequestParser::parseRequestParams($queryString) : null;
}

Expand Down
40 changes: 39 additions & 1 deletion src/Util/RequestParser.php
Expand Up @@ -38,7 +38,7 @@ private function __construct()
*/
public static function parseAndDuplicateRequest(Request $request): Request
{
$query = self::parseRequestParams($request->getQueryString() ?? '');
$query = self::parseRequestParams(self::getQueryString($request) ?? '');
$body = self::parseRequestParams($request->getContent());

return $request->duplicate($query, $body);
Expand Down Expand Up @@ -74,4 +74,42 @@ function ($key) {

return array_combine(array_map('hex2bin', array_keys($params)), $params);
}

/**
* Generates the normalized query string for the Request.
*
* It builds a normalized query string, where keys/value pairs are alphabetized
* and have consistent escaping.
*
* @return string|null A normalized query string for the Request
*/
public static function getQueryString(Request $request)
{
$qs = $request->server->get('QUERY_STRING', '');
if ('' === $qs) {
return null;
}

$parts = [];

foreach (explode('&', $qs) as $param) {
if ('' === $param || '=' === $param[0]) {
// Ignore useless delimiters, e.g. "x=y&".
// Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
// PHP also does not include them when building _GET.
continue;
}

$keyValuePair = explode('=', $param, 2);

// GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
// PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to
// RFC 3986 with rawurlencode.
$parts[] = isset($keyValuePair[1]) ?
rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) :
rawurlencode(urldecode($keyValuePair[0]));
}

return implode('&', $parts);
}
}

0 comments on commit 45c537f

Please sign in to comment.