diff --git a/modules/openapi-generator/src/main/resources/php/ObjectSerializer.mustache b/modules/openapi-generator/src/main/resources/php/ObjectSerializer.mustache index 476938159f0..5cd224a6e04 100644 --- a/modules/openapi-generator/src/main/resources/php/ObjectSerializer.mustache +++ b/modules/openapi-generator/src/main/resources/php/ObjectSerializer.mustache @@ -148,6 +148,49 @@ class ObjectSerializer return rawurlencode(self::toString($value)); } + /** + * Checks if a value is empty, based on its OpenAPI type. + * + * @param mixed $value + * @param string $openApiType + * + * @return bool true if $value is empty + */ + private static function isEmptyValue($value, string $openApiType): bool + { + # If empty() returns false, it is not empty regardless of its type. + if (!empty($value)) { + return false; + } + + # Null is always empty, as we cannot send a real "null" value in a query parameter. + if ($value === null) { + return true; + } + + switch ($openApiType) { + # For numeric values, false and '' are considered empty. + # This comparison is safe for floating point values, since the previous call to empty() will + # filter out values that don't match 0. + case 'int': + case 'integer': + return $value !== 0; + + case 'number': + case 'float': + return $value !== 0 && $value !== 0.0; + + # For boolean values, '' is considered empty + case 'bool': + case 'boolean': + return !in_array($value, [false, 0], true); + + # For all the other types, any value at this point can be considered empty. + default: + return true; + } + } + /** * Take query parameter properties and turn it into an array suitable for * native http_build_query or GuzzleHttp\Psr7\Query::build. @@ -169,10 +212,12 @@ class ObjectSerializer bool $explode = true, bool $required = true ): array { - if ( - empty($value) - && ($value !== false || $openApiType !== 'boolean') // if $value === false and $openApiType ==='boolean' it isn't empty - ) { + + # Check if we should omit this parameter from the query. This should only happen when: + # - Parameter is NOT required; AND + # - its value is set to a value that is equivalent to "empty", depending on its OpenAPI type. For + # example, 0 as "int" or "boolean" is NOT an empty value. + if (self::isEmptyValue($value, $openApiType)) { if ($required) { return ["{$paramName}" => '']; } else { diff --git a/samples/client/petstore/php/OpenAPIClient-php/lib/ObjectSerializer.php b/samples/client/petstore/php/OpenAPIClient-php/lib/ObjectSerializer.php index a4cf679dfbe..0dd7232b1b4 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/ObjectSerializer.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/ObjectSerializer.php @@ -157,6 +157,49 @@ class ObjectSerializer return rawurlencode(self::toString($value)); } + /** + * Checks if a value is empty, based on its OpenAPI type. + * + * @param mixed $value + * @param string $openApiType + * + * @return bool true if $value is empty + */ + private static function isEmptyValue($value, string $openApiType): bool + { + # If empty() returns false, it is not empty regardless of its type. + if (!empty($value)) { + return false; + } + + # Null is always empty, as we cannot send a real "null" value in a query parameter. + if ($value === null) { + return true; + } + + switch ($openApiType) { + # For numeric values, false and '' are considered empty. + # This comparison is safe for floating point values, since the previous call to empty() will + # filter out values that don't match 0. + case 'int': + case 'integer': + return $value !== 0; + + case 'number': + case 'float': + return $value !== 0 && $value !== 0.0; + + # For boolean values, '' is considered empty + case 'bool': + case 'boolean': + return !in_array($value, [false, 0], true); + + # For all the other types, any value at this point can be considered empty. + default: + return true; + } + } + /** * Take query parameter properties and turn it into an array suitable for * native http_build_query or GuzzleHttp\Psr7\Query::build. @@ -178,10 +221,12 @@ class ObjectSerializer bool $explode = true, bool $required = true ): array { - if ( - empty($value) - && ($value !== false || $openApiType !== 'boolean') // if $value === false and $openApiType ==='boolean' it isn't empty - ) { + + # Check if we should omit this parameter from the query. This should only happen when: + # - Parameter is NOT required; AND + # - its value is set to a value that is equivalent to "empty", depending on its OpenAPI type. For + # example, 0 as "int" or "boolean" is NOT an empty value. + if (self::isEmptyValue($value, $openApiType)) { if ($required) { return ["{$paramName}" => '']; } else { diff --git a/samples/client/petstore/php/OpenAPIClient-php/tests/ObjectSerializerTest.php b/samples/client/petstore/php/OpenAPIClient-php/tests/ObjectSerializerTest.php index 8743611a520..84f59c95134 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/tests/ObjectSerializerTest.php +++ b/samples/client/petstore/php/OpenAPIClient-php/tests/ObjectSerializerTest.php @@ -336,6 +336,97 @@ class ObjectSerializerTest extends TestCase 'form null DateTime object, explode on, required false' => [ null, 'dateTime', '\DateTime', 'form', true, false, '', ], + 'form 1 int, explode on, required false' => [ + 1, 'field', 'int', 'form', true, false, 'field=1', + ], + 'form 0 int, explode on, required false' => [ + 0, 'field', 'int', 'form', true, false, 'field=0', + ], + 'form 0 int, explode on, required true' => [ + 0, 'field', 'int', 'form', true, true, 'field=0', + ], + 'form null int, explode on, required false' => [ + null, 'field', 'int', 'form', true, false, '', + ], + 'form null int, explode on, required true' => [ + null, 'field', 'int', 'form', true, true, 'field=', + ], + 'form 1 integer, explode on, required false' => [ + 1, 'field', 'integer', 'form', true, false, 'field=1', + ], + 'form 0 integer, explode on, required false' => [ + 0, 'field', 'integer', 'form', true, false, 'field=0', + ], + 'form 0 integer, explode on, required true' => [ + 0, 'field', 'integer', 'form', true, true, 'field=0', + ], + 'form null integer, explode on, required false' => [ + null, 'field', 'integer', 'form', true, false, '', + ], + 'form null integer, explode on, required true' => [ + null, 'field', 'integer', 'form', true, true, 'field=', + ], + 'form 1.1 float, explode on, required false' => [ + 1.1, 'field', 'float', 'form', true, false, 'field=1.1', + ], + 'form 0 float, explode on, required false' => [ + 0, 'field', 'float', 'form', true, false, 'field=0', + ], + 'form 0.0 float, explode on, required false' => [ + 0.0, 'field', 'float', 'form', true, false, 'field=0', + ], + 'form 0 float, explode on, required true' => [ + 0, 'field', 'float', 'form', true, true, 'field=0', + ], + 'form 0.0 float, explode on, required true' => [ + 0.0, 'field', 'float', 'form', true, true, 'field=0', + ], + 'form null float, explode on, required false' => [ + null, 'field', 'float', 'form', true, false, '', + ], + 'form null float, explode on, required true' => [ + null, 'field', 'float', 'form', true, true, 'field=', + ], + 'form 1.1 number, explode on, required false' => [ + 1.1, 'field', 'number', 'form', true, false, 'field=1.1', + ], + 'form 0 number, explode on, required false' => [ + 0, 'field', 'number', 'form', true, false, 'field=0', + ], + 'form 0.0 number, explode on, required false' => [ + 0.0, 'field', 'number', 'form', true, false, 'field=0', + ], + 'form 0 number, explode on, required true' => [ + 0, 'field', 'number', 'form', true, true, 'field=0', + ], + 'form 0.0 number, explode on, required true' => [ + 0.0, 'field', 'number', 'form', true, true, 'field=0', + ], + 'form null number, explode on, required false' => [ + null, 'field', 'number', 'form', true, false, '', + ], + 'form null number, explode on, required true' => [ + null, 'field', 'number', 'form', true, true, 'field=', + ], + 'form true bool, explode on, required false' => [ + true, 'field', 'bool', 'form', true, false, 'field=1', + ], + 'form false bool, explode on, required false' => [ + false, 'field', 'bool', 'form', true, false, 'field=0', + ], + 'form empty bool, explode on, required false' => [ + null, 'field', 'bool', 'form', true, false, '', + ], + 'form empty bool, explode on, required true' => [ + null, 'field', 'bool', 'form', true, true, 'field=', + ], + # Entries for "boolean" type are already covered in the beginning of this provider + 'form 1 bool, explode on, required false' => [ + 1, 'field', 'bool', 'form', true, false, 'field=1', + ], + 'form 0 bool, explode on, required false' => [ + 0, 'field', 'bool', 'form', true, false, 'field=0', + ], ]; }