[php] use http_build_query for deepObject support (#11225)

* Update ObjectSerializer::toQueryValue method

It looks a bit ugly right now, but at least all tests has been passed.

* Add tests of query serialization

I used fixtures from OpenAPISpec main doc. Some prop combinations are not
officially documented yet, for instance behavior for nested objects and
arrays.

* Add test fixture from @nadar

* Add tests of Guzzle query build failure

* Add query build wrapper static method

Co-authored-by: Yuriy Belenko <yura-bely@mail.ru>
This commit is contained in:
Basil 2022-03-22 10:56:15 +01:00 committed by GitHub
parent 36f0dd026c
commit 196b9f266f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 605 additions and 319 deletions

View File

@ -149,22 +149,61 @@ class ObjectSerializer
}
/**
* Take value and turn it into a string suitable for inclusion in
* the query, by imploding comma-separated if it's an object.
* If it's a string, pass through unchanged. It will be url-encoded
* later.
* Take query parameter properties and turn it into an array suitable for
* native http_build_query or GuzzleHttp\Psr7\Query::build.
*
* @param string[]|string|\DateTime $object an object to be serialized to a string
* @param mixed $value Parameter value
* @param string $paramName Parameter name
* @param string $openApiType OpenAPIType eg. array or object
* @param string $style Parameter serialization style
* @param bool $explode Parameter explode option
*
* @return string the serialized object
* @return array
*/
public static function toQueryValue($object)
{
if (is_array($object)) {
return implode(',', $object);
} else {
return self::toString($object);
public static function toQueryValue(
$value,
string $paramName,
string $openApiType = 'string',
string $style = 'form',
bool $explode = true
): array {
// return empty string
if (empty($value)) return ["{$paramName}" => ''];
$query = [];
$value = (in_array($openApiType, ['object', 'array'], true)) ? (array)$value : $value;
// since \GuzzleHttp\Psr7\Query::build fails with nested arrays
// need to flatten array first
$flattenArray = function ($arr, $name, &$result = []) use (&$flattenArray, $style, $explode) {
if (!is_array($arr)) return $arr;
foreach ($arr as $k => $v) {
$prop = ($style === 'deepObject') ? $prop = "{$name}[{$k}]" : $k;
if (is_array($v)) {
$flattenArray($v, $prop, $result);
} else {
if ($style !== 'deepObject' && !$explode) {
// push key itself
$result[] = $prop;
}
$result[$prop] = $v;
}
}
return $result;
};
$value = $flattenArray($value, $paramName);
if ($openApiType === 'object' && ($style === 'deepObject' || $explode)) {
return $value;
}
// handle style in serializeCollection
$query[$paramName] = ($explode) ? $value : self::serializeCollection((array)$value, $style);
return $query;
}
/**
@ -403,4 +442,24 @@ class ObjectSerializer
return $instance;
}
}
/**
* Native `http_build_query` wrapper.
* @see https://www.php.net/manual/en/function.http-build-query
*
* @param array|object $data May be an array or object containing properties.
* @param string $numeric_prefix If numeric indices are used in the base array and this parameter is provided, it will be prepended to the numeric index for elements in the base array only.
* @param string|null $arg_separator arg_separator.output is used to separate arguments but may be overridden by specifying this parameter.
* @param int $encoding_type Encoding type. By default, PHP_QUERY_RFC1738.
*
* @return string
*/
public static function buildQuery(
$data,
string $numeric_prefix = '',
?string $arg_separator = null,
int $encoding_type = \PHP_QUERY_RFC3986
): string {
return \GuzzleHttp\Psr7\Query::build($data, $encoding_type);
}
}

View File

@ -497,31 +497,13 @@ use {{invokerPackage}}\ObjectSerializer;
{{#queryParams}}
// query params
{{#isExplode}}
if (${{paramName}} !== null) {
{{#style}}
if('form' === '{{style}}' && is_array(${{paramName}})) {
foreach(${{paramName}} as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['{{baseName}}'] = ${{paramName}};
}
{{/style}}
{{^style}}
$queryParams['{{baseName}}'] = ${{paramName}};
{{/style}}
}
{{/isExplode}}
{{^isExplode}}
if (is_array(${{paramName}})) {
${{paramName}} = ObjectSerializer::serializeCollection(${{paramName}}, '{{style}}{{^style}}{{collectionFormat}}{{/style}}', true);
}
if (${{paramName}} !== null) {
$queryParams['{{baseName}}'] = ${{paramName}};
}
{{/isExplode}}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
${{paramName}},
'{{baseName}}', // param base name
'{{#schema}}{{openApiType}}{{/schema}}', // openApiType
'{{style}}', // style
{{#isExplode}}true{{/isExplode}}{{^isExplode}}false{{/isExplode}} // explode
) ?? []);
{{/queryParams}}
{{#headerParams}}
@ -615,7 +597,7 @@ use {{invokerPackage}}\ObjectSerializer;
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -668,7 +650,7 @@ use {{invokerPackage}}\ObjectSerializer;
$operationHost = $operationHosts[$this->hostIndex];
{{/servers.0}}
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'{{httpMethod}}',
{{^servers.0}}$this->config->getHost(){{/servers.0}}{{#servers.0}}$operationHost{{/servers.0}} . $resourcePath . ($query ? "?{$query}" : ''),

View File

@ -359,7 +359,7 @@ class AnotherFakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -375,7 +375,7 @@ class AnotherFakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'PATCH',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),

View File

@ -334,7 +334,7 @@ class DefaultApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -350,7 +350,7 @@ class DefaultApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),

View File

@ -342,7 +342,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -358,7 +358,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -535,16 +535,13 @@ class FakeApi
$multipart = false;
// query params
if ($query_1 !== null) {
if('form' === 'form' && is_array($query_1)) {
foreach($query_1 as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['query_1'] = $query_1;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$query_1,
'query_1', // param base name
'string', // openApiType
'form', // style
true // explode
) ?? []);
// header params
if ($header_1 !== null) {
@ -591,7 +588,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -607,7 +604,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -846,7 +843,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -862,7 +859,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -1101,7 +1098,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1117,7 +1114,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -1356,7 +1353,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1372,7 +1369,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -1611,7 +1608,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1627,7 +1624,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -1872,7 +1869,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1888,7 +1885,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -2088,7 +2085,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -2104,7 +2101,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'PUT',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -2304,7 +2301,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -2320,7 +2317,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'PUT',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -2490,16 +2487,13 @@ class FakeApi
$multipart = false;
// query params
if ($query !== null) {
if('form' === 'form' && is_array($query)) {
foreach($query as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['query'] = $query;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$query,
'query', // param base name
'string', // openApiType
'form', // style
true // explode
) ?? []);
@ -2542,7 +2536,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -2558,7 +2552,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'PUT',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -2811,7 +2805,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -2827,7 +2821,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'PATCH',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -3223,7 +3217,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -3243,7 +3237,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -3439,49 +3433,37 @@ class FakeApi
$multipart = false;
// query params
if ($enum_query_string_array !== null) {
if('form' === 'form' && is_array($enum_query_string_array)) {
foreach($enum_query_string_array as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['enum_query_string_array'] = $enum_query_string_array;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$enum_query_string_array,
'enum_query_string_array', // param base name
'array', // openApiType
'form', // style
true // explode
) ?? []);
// query params
if ($enum_query_string !== null) {
if('form' === 'form' && is_array($enum_query_string)) {
foreach($enum_query_string as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['enum_query_string'] = $enum_query_string;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$enum_query_string,
'enum_query_string', // param base name
'string', // openApiType
'form', // style
true // explode
) ?? []);
// query params
if ($enum_query_integer !== null) {
if('form' === 'form' && is_array($enum_query_integer)) {
foreach($enum_query_integer as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['enum_query_integer'] = $enum_query_integer;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$enum_query_integer,
'enum_query_integer', // param base name
'integer', // openApiType
'form', // style
true // explode
) ?? []);
// query params
if ($enum_query_double !== null) {
if('form' === 'form' && is_array($enum_query_double)) {
foreach($enum_query_double as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['enum_query_double'] = $enum_query_double;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$enum_query_double,
'enum_query_double', // param base name
'number', // openApiType
'form', // style
true // explode
) ?? []);
// header params
if (is_array($enum_header_string_array)) {
@ -3537,7 +3519,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -3553,7 +3535,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -3775,49 +3757,37 @@ class FakeApi
$multipart = false;
// query params
if ($required_string_group !== null) {
if('form' === 'form' && is_array($required_string_group)) {
foreach($required_string_group as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['required_string_group'] = $required_string_group;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$required_string_group,
'required_string_group', // param base name
'integer', // openApiType
'form', // style
true // explode
) ?? []);
// query params
if ($required_int64_group !== null) {
if('form' === 'form' && is_array($required_int64_group)) {
foreach($required_int64_group as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['required_int64_group'] = $required_int64_group;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$required_int64_group,
'required_int64_group', // param base name
'integer', // openApiType
'form', // style
true // explode
) ?? []);
// query params
if ($string_group !== null) {
if('form' === 'form' && is_array($string_group)) {
foreach($string_group as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['string_group'] = $string_group;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$string_group,
'string_group', // param base name
'integer', // openApiType
'form', // style
true // explode
) ?? []);
// query params
if ($int64_group !== null) {
if('form' === 'form' && is_array($int64_group)) {
foreach($int64_group as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['int64_group'] = $int64_group;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$int64_group,
'int64_group', // param base name
'integer', // openApiType
'form', // style
true // explode
) ?? []);
// header params
if ($required_boolean_group !== null) {
@ -3862,7 +3832,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -3882,7 +3852,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'DELETE',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -4090,7 +4060,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -4106,7 +4076,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -4327,7 +4297,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -4343,7 +4313,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -4562,66 +4532,61 @@ class FakeApi
$multipart = false;
// query params
if (is_array($pipe)) {
$pipe = ObjectSerializer::serializeCollection($pipe, 'pipeDelimited', true);
}
if ($pipe !== null) {
$queryParams['pipe'] = $pipe;
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$pipe,
'pipe', // param base name
'array', // openApiType
'pipeDelimited', // style
false // explode
) ?? []);
// query params
if (is_array($ioutil)) {
$ioutil = ObjectSerializer::serializeCollection($ioutil, 'form', true);
}
if ($ioutil !== null) {
$queryParams['ioutil'] = $ioutil;
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$ioutil,
'ioutil', // param base name
'array', // openApiType
'form', // style
false // explode
) ?? []);
// query params
if (is_array($http)) {
$http = ObjectSerializer::serializeCollection($http, 'spaceDelimited', true);
}
if ($http !== null) {
$queryParams['http'] = $http;
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$http,
'http', // param base name
'array', // openApiType
'spaceDelimited', // style
false // explode
) ?? []);
// query params
if (is_array($url)) {
$url = ObjectSerializer::serializeCollection($url, 'form', true);
}
if ($url !== null) {
$queryParams['url'] = $url;
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$url,
'url', // param base name
'array', // openApiType
'form', // style
false // explode
) ?? []);
// query params
if ($context !== null) {
if('form' === 'form' && is_array($context)) {
foreach($context as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['context'] = $context;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$context,
'context', // param base name
'array', // openApiType
'form', // style
true // explode
) ?? []);
// query params
if ($language !== null) {
if('form' === 'form' && is_array($language)) {
foreach($language as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['language'] = $language;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$language,
'language', // param base name
'object', // openApiType
'form', // style
true // explode
) ?? []);
// query params
if ($allow_empty !== null) {
if('form' === 'form' && is_array($allow_empty)) {
foreach($allow_empty as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['allowEmpty'] = $allow_empty;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$allow_empty,
'allowEmpty', // param base name
'string', // openApiType
'form', // style
true // explode
) ?? []);
@ -4658,7 +4623,7 @@ class FakeApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -4674,7 +4639,7 @@ class FakeApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'PUT',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),

View File

@ -359,7 +359,7 @@ class FakeClassnameTags123Api
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -380,7 +380,7 @@ class FakeClassnameTags123Api
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'PATCH',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),

View File

@ -334,7 +334,7 @@ class PetApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -360,7 +360,7 @@ class PetApi
}
$operationHost = $operationHosts[$this->hostIndex];
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$operationHost . $resourcePath . ($query ? "?{$query}" : ''),
@ -579,7 +579,7 @@ class PetApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -599,7 +599,7 @@ class PetApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'DELETE',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -811,12 +811,13 @@ class PetApi
$multipart = false;
// query params
if (is_array($status)) {
$status = ObjectSerializer::serializeCollection($status, 'form', true);
}
if ($status !== null) {
$queryParams['status'] = $status;
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$status,
'status', // param base name
'array', // openApiType
'form', // style
false // explode
) ?? []);
@ -853,7 +854,7 @@ class PetApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -873,7 +874,7 @@ class PetApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -1091,12 +1092,13 @@ class PetApi
$multipart = false;
// query params
if (is_array($tags)) {
$tags = ObjectSerializer::serializeCollection($tags, 'form', true);
}
if ($tags !== null) {
$queryParams['tags'] = $tags;
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$tags,
'tags', // param base name
'array', // openApiType
'form', // style
false // explode
) ?? []);
@ -1133,7 +1135,7 @@ class PetApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1153,7 +1155,7 @@ class PetApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -1408,7 +1410,7 @@ class PetApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1429,7 +1431,7 @@ class PetApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -1657,7 +1659,7 @@ class PetApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1683,7 +1685,7 @@ class PetApi
}
$operationHost = $operationHosts[$this->hostIndex];
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'PUT',
$operationHost . $resourcePath . ($query ? "?{$query}" : ''),
@ -1911,7 +1913,7 @@ class PetApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1931,7 +1933,7 @@ class PetApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -2212,7 +2214,7 @@ class PetApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -2232,7 +2234,7 @@ class PetApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -2519,7 +2521,7 @@ class PetApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -2539,7 +2541,7 @@ class PetApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),

View File

@ -316,7 +316,7 @@ class StoreApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -332,7 +332,7 @@ class StoreApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'DELETE',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -568,7 +568,7 @@ class StoreApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -589,7 +589,7 @@ class StoreApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -851,7 +851,7 @@ class StoreApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -867,7 +867,7 @@ class StoreApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -1120,7 +1120,7 @@ class StoreApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1136,7 +1136,7 @@ class StoreApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),

View File

@ -314,7 +314,7 @@ class UserApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -330,7 +330,7 @@ class UserApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -538,7 +538,7 @@ class UserApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -554,7 +554,7 @@ class UserApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -762,7 +762,7 @@ class UserApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -778,7 +778,7 @@ class UserApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -988,7 +988,7 @@ class UserApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1004,7 +1004,7 @@ class UserApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'DELETE',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -1259,7 +1259,7 @@ class UserApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1275,7 +1275,7 @@ class UserApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -1498,27 +1498,21 @@ class UserApi
$multipart = false;
// query params
if ($username !== null) {
if('form' === 'form' && is_array($username)) {
foreach($username as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['username'] = $username;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$username,
'username', // param base name
'string', // openApiType
'form', // style
true // explode
) ?? []);
// query params
if ($password !== null) {
if('form' === 'form' && is_array($password)) {
foreach($password as $key => $value) {
$queryParams[$key] = $value;
}
}
else {
$queryParams['password'] = $password;
}
}
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$password,
'password', // param base name
'string', // openApiType
'form', // style
true // explode
) ?? []);
@ -1555,7 +1549,7 @@ class UserApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1571,7 +1565,7 @@ class UserApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -1762,7 +1756,7 @@ class UserApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -1778,7 +1772,7 @@ class UserApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),
@ -2005,7 +1999,7 @@ class UserApi
} else {
// for HTTP post (form)
$httpBody = \GuzzleHttp\Psr7\Query::build($formParams);
$httpBody = ObjectSerializer::buildQuery($queryParams);
}
}
@ -2021,7 +2015,7 @@ class UserApi
$headers
);
$query = \GuzzleHttp\Psr7\Query::build($queryParams);
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'PUT',
$this->config->getHost() . $resourcePath . ($query ? "?{$query}" : ''),

View File

@ -158,22 +158,61 @@ class ObjectSerializer
}
/**
* Take value and turn it into a string suitable for inclusion in
* the query, by imploding comma-separated if it's an object.
* If it's a string, pass through unchanged. It will be url-encoded
* later.
* Take query parameter properties and turn it into an array suitable for
* native http_build_query or GuzzleHttp\Psr7\Query::build.
*
* @param string[]|string|\DateTime $object an object to be serialized to a string
* @param mixed $value Parameter value
* @param string $paramName Parameter name
* @param string $openApiType OpenAPIType eg. array or object
* @param string $style Parameter serialization style
* @param bool $explode Parameter explode option
*
* @return string the serialized object
* @return array
*/
public static function toQueryValue($object)
{
if (is_array($object)) {
return implode(',', $object);
} else {
return self::toString($object);
public static function toQueryValue(
$value,
string $paramName,
string $openApiType = 'string',
string $style = 'form',
bool $explode = true
): array {
// return empty string
if (empty($value)) return ["{$paramName}" => ''];
$query = [];
$value = (in_array($openApiType, ['object', 'array'], true)) ? (array)$value : $value;
// since \GuzzleHttp\Psr7\Query::build fails with nested arrays
// need to flatten array first
$flattenArray = function ($arr, $name, &$result = []) use (&$flattenArray, $style, $explode) {
if (!is_array($arr)) return $arr;
foreach ($arr as $k => $v) {
$prop = ($style === 'deepObject') ? $prop = "{$name}[{$k}]" : $k;
if (is_array($v)) {
$flattenArray($v, $prop, $result);
} else {
if ($style !== 'deepObject' && !$explode) {
// push key itself
$result[] = $prop;
}
$result[$prop] = $v;
}
}
return $result;
};
$value = $flattenArray($value, $paramName);
if ($openApiType === 'object' && ($style === 'deepObject' || $explode)) {
return $value;
}
// handle style in serializeCollection
$query[$paramName] = ($explode) ? $value : self::serializeCollection((array)$value, $style);
return $query;
}
/**
@ -412,4 +451,24 @@ class ObjectSerializer
return $instance;
}
}
/**
* Native `http_build_query` wrapper.
* @see https://www.php.net/manual/en/function.http-build-query
*
* @param array|object $data May be an array or object containing properties.
* @param string $numeric_prefix If numeric indices are used in the base array and this parameter is provided, it will be prepended to the numeric index for elements in the base array only.
* @param string|null $arg_separator arg_separator.output is used to separate arguments but may be overridden by specifying this parameter.
* @param int $encoding_type Encoding type. By default, PHP_QUERY_RFC1738.
*
* @return string
*/
public static function buildQuery(
$data,
string $numeric_prefix = '',
?string $arg_separator = null,
int $encoding_type = \PHP_QUERY_RFC3986
): string {
return \GuzzleHttp\Psr7\Query::build($data, $encoding_type);
}
}

View File

@ -130,4 +130,208 @@ class ObjectSerializerTest extends TestCase
],
];
}
/**
* @covers ObjectSerializer::toQueryValue
* @dataProvider provideQueryParams
*/
public function testToQueryValue(
$data,
string $paramName,
string $openApiType,
string $style,
bool $explode,
$expected
): void {
$value = ObjectSerializer::toQueryValue($data, $paramName, $openApiType, $style, $explode);
$query = ObjectSerializer::buildQuery($value);
$this->assertEquals($expected, $query);
}
public function provideQueryParams(): array
{
$array = ['blue', 'black', 'brown'];
$object = ['R' => 100, 'G' => 200, 'B' => 150];
return [
// style form
// color=
'form empty, explode on' => [
'', 'color', 'string', 'form', true, 'color=',
],
// color=
'form empty, explode off' => [
'', 'color', 'string', 'form', false, 'color=',
],
// color=blue
'form string, explode on' => [
'blue', 'color', 'string', 'form', true, 'color=blue',
],
// color=blue
'form string, explode off' => [
'blue', 'color', 'string', 'form', false, 'color=blue',
],
// color=blue&color=black&color=brown
'form array, explode on' => [
$array, 'color', 'array', 'form', true, 'color=blue&color=black&color=brown',
],
// color=blue&color=black&color=brown
'form nested array, explode on' => [
['foobar' => $array], 'color', 'array', 'form', true, 'color=blue&color=black&color=brown',
],
// color=blue,black,brown
'form array, explode off' => [
$array, 'color', 'array', 'form', false, 'color=blue%2Cblack%2Cbrown',
],
// color=blue,black,brown
'form nested array, explode off' => [
['foobar' => $array], 'color', 'array', 'form', false, 'color=blue%2Cblack%2Cbrown',
],
// R=100&G=200&B=150
'form object, explode on' => [
$object, 'color', 'object', 'form', true, 'R=100&G=200&B=150',
],
// color=R,100,G,200,B,150
'form object, explode off' => [
$object, 'color', 'object', 'form', false, 'color=R%2C100%2CG%2C200%2CB%2C150',
],
// SPACE DELIMITED
// color=blue
'spaceDelimited primitive, explode off' => [
'blue', 'color', 'string', 'spaceDelimited', false, 'color=blue',
],
// color=blue
'spaceDelimited primitive, explode on' => [
'blue', 'color', 'string', 'spaceDelimited', true, 'color=blue',
],
// color=blue%20black%20brown
'spaceDelimited array, explode off' => [
$array, 'color', 'array', 'spaceDelimited', false, 'color=blue%20black%20brown',
],
// color=blue&color=black&color=brown
'spaceDelimited array, explode on' => [
$array, 'color', 'array', 'spaceDelimited', true, 'color=blue&color=black&color=brown',
],
// color=R%20100%20G%20200%20B%20150
// swagger editor gives color=R,100,G,200,B,150
'spaceDelimited object, explode off' => [
$object, 'color', 'object', 'spaceDelimited', false, 'color=R%20100%20G%20200%20B%20150',
],
// R=100&G=200&B=150
'spaceDelimited object, explode on' => [
$object, 'color', 'object', 'spaceDelimited', true, 'R=100&G=200&B=150',
],
// PIPE DELIMITED
// color=blue
'pipeDelimited primitive, explode off' => [
'blue', 'color', 'string', 'pipeDelimited', false, 'color=blue',
],
// color=blue
'pipeDelimited primitive, explode on' => [
'blue', 'color', 'string', 'pipeDelimited', true, 'color=blue',
],
// color=blue|black|brown
'pipeDelimited array, explode off' => [
$array, 'color', 'array', 'pipeDelimited', false, 'color=blue%7Cblack%7Cbrown',
],
// color=blue&color=black&color=brown
'pipeDelimited array, explode on' => [
$array, 'color', 'array', 'pipeDelimited', true, 'color=blue&color=black&color=brown',
],
// color=R|100|G|200|B|150
// swagger editor gives color=R,100,G,200,B,150
'pipeDelimited object, explode off' => [
$object, 'color', 'object', 'pipeDelimited', false, 'color=R%7C100%7CG%7C200%7CB%7C150',
],
// R=100&G=200&B=150
'pipeDelimited object, explode on' => [
$object, 'color', 'object', 'pipeDelimited', true, 'R=100&G=200&B=150',
],
// DEEP OBJECT
// color=blue
'deepObject primitive, explode off' => [
'blue', 'color', 'string', 'deepObject', false, 'color=blue',
],
'deepObject primitive, explode on' => [
'blue', 'color', 'string', 'deepObject', true, 'color=blue',
],
// color=blue,black,brown
'deepObject array, explode off' => [
$array, 'color', 'array', 'deepObject', false, 'color=blue%2Cblack%2Cbrown',
],
// color=blue&color=black&color=brown
'deepObject array, explode on' => [
$array, 'color', 'array', 'deepObject', true, 'color=blue&color=black&color=brown',
],
// color[R]=100&color[G]=200&color[B]=150
'deepObject object, explode off' => [
$object, 'color', 'object', 'deepObject', false, 'color%5BR%5D=100&color%5BG%5D=200&color%5BB%5D=150',
],
// color[R]=100&color[G]=200&color[B]=150
'deepObject object, explode on' => [
$object, 'color', 'object', 'deepObject', true, 'color%5BR%5D=100&color%5BG%5D=200&color%5BB%5D=150',
],
// filter[or][0][name]=John&filter[or][1][email]=john@doe.com
'example from @nadar' => [
[
'or' =>
[
['name' => 'John'],
['email' => 'john@doe.com'],
],
],
'filter',
'object',
'deepObject',
true,
'filter%5Bor%5D%5B0%5D%5Bname%5D=John&filter%5Bor%5D%5B1%5D%5Bemail%5D=john%40doe.com'
],
];
}
/**
* @covers ObjectSerializer::toQueryValue
* @dataProvider provideDeepObjects
*/
public function testDeepObjectStyleQueryParam(
$data,
$paramName,
$expected
): void {
$value = ObjectSerializer::buildQuery(ObjectSerializer::toQueryValue($data, $paramName, 'object', 'deepObject', true));
parse_str($value, $result);
$this->assertEquals($expected, $result);
}
public function provideDeepObjects(): array
{
return [
/** example @see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-examples */
'example OpenAPISpec doc' => [
['R' => 100, 'G' => 200, 'B' => 150],
'color',
[
'color' => [
'R' => 100,
'G' => 200,
'B' => 150,
],
],
],
/** example @see https://swagger.io/docs/specification/serialization/ */
'example Swagger doc' => [
['role' => 'admin', 'firstName' => 'Alex'],
'id',
[
'id' => [
'role' => 'admin',
'firstName' => 'Alex',
],
],
],
];
}
}

View File

@ -56,6 +56,27 @@ class ParametersTest extends TestCase
$this->assertSame('{"foo":"bar"}', $request->getBody()->getContents());
}
/**
* @see https://github.com/OpenAPITools/openapi-generator/pull/11225
* @dataProvider provideQueryParameters
*/
public function testQueryParameterCollectionFormatRequest($pipe, $ioutil, $http, $url, $context, $allow_empty, $language, $expected)
{
$request = $this->fakeApi->testQueryParameterCollectionFormatRequest($pipe, $ioutil, $http, $url, $context, $allow_empty, $language);
$this->assertEquals($expected, urldecode($request->getUri()->getQuery()));
}
public function provideQueryParameters()
{
$array = ['blue', 'black', 'brown'];
$object = ['R' => 100, 'G' => 200, 'B' => 150];
return [
[
$array, $array, $array, $array, $array, 'blue', $object, 'pipe=blue|black|brown&ioutil=blue,black,brown&http=blue black brown&url=blue,black,brown&context=blue&context=black&context=brown&R=100&G=200&B=150&allowEmpty=blue',
],
];
}
// missing example for collection path param in config
// public function testPathParamCollection()
// {