diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpClientCodegen.java index 686c389846e6..baeae7e39486 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpClientCodegen.java @@ -117,6 +117,7 @@ public void processOpts() { supportingFiles.add(new SupportingFile("ApiException.mustache", toSrcPath(invokerPackage, srcBasePath), "ApiException.php")); supportingFiles.add(new SupportingFile("Configuration.mustache", toSrcPath(invokerPackage, srcBasePath), "Configuration.php")); + supportingFiles.add(new SupportingFile("FormDataProcessor.mustache", toSrcPath(invokerPackage, srcBasePath), "FormDataProcessor.php")); supportingFiles.add(new SupportingFile("ObjectSerializer.mustache", toSrcPath(invokerPackage, srcBasePath), "ObjectSerializer.php")); supportingFiles.add(new SupportingFile("ModelInterface.mustache", toSrcPath(modelPackage, srcBasePath), "ModelInterface.php")); supportingFiles.add(new SupportingFile("HeaderSelector.mustache", toSrcPath(invokerPackage, srcBasePath), "HeaderSelector.php")); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpNextgenClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpNextgenClientCodegen.java index 64e8c94b28c2..b24127146811 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpNextgenClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpNextgenClientCodegen.java @@ -121,6 +121,7 @@ public void processOpts() { supportingFiles.add(new SupportingFile("ApiException.mustache", toSrcPath(invokerPackage, srcBasePath), "ApiException.php")); supportingFiles.add(new SupportingFile("Configuration.mustache", toSrcPath(invokerPackage, srcBasePath), "Configuration.php")); + supportingFiles.add(new SupportingFile("FormDataProcessor.mustache", toSrcPath(invokerPackage, srcBasePath), "FormDataProcessor.php")); supportingFiles.add(new SupportingFile("ObjectSerializer.mustache", toSrcPath(invokerPackage, srcBasePath), "ObjectSerializer.php")); supportingFiles.add(new SupportingFile("ModelInterface.mustache", toSrcPath(modelPackage, srcBasePath), "ModelInterface.php")); supportingFiles.add(new SupportingFile("HeaderSelector.mustache", toSrcPath(invokerPackage, srcBasePath), "HeaderSelector.php")); diff --git a/modules/openapi-generator/src/main/resources/php-nextgen/FormDataProcessor.mustache b/modules/openapi-generator/src/main/resources/php-nextgen/FormDataProcessor.mustache new file mode 100644 index 000000000000..ad9808c4ab12 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/php-nextgen/FormDataProcessor.mustache @@ -0,0 +1,227 @@ +partial_header}} +/** + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +namespace {{invokerPackage}}; + +use ArrayAccess; +use DateTime; +use GuzzleHttp\Psr7\Utils; +use Psr\Http\Message\StreamInterface; +use SplFileObject; +use {{modelPackage}}\ModelInterface; + +class FormDataProcessor +{ + /** + * Tags whether payload passed to ::prepare() contains one or more + * SplFileObject or stream values. + */ + public bool $has_file = false; + + /** + * Take value and turn it into an array suitable for inclusion in + * the http body (form parameter). If it's a string, pass through unchanged + * If it's a datetime object, format it in ISO8601 + * + * @param array $values the value of the form parameter + * + * @return array [key => value] of formdata + */ + public function prepare(array $values): array + { + $this->has_file = false; + $result = []; + + foreach ($values as $k => $v) { + if ($v === null) { + continue; + } + + $result[$k] = $this->makeFormSafe($v); + } + + return $result; + } + + /** + * Flattens a multi-level array of data and generates a single-level array + * compatible with formdata - a single-level array where the keys use bracket + * notation to signify nested data. + * + * credit: https://github.com/FranBar1966/FlatPHP + */ + public static function flatten(array $source, string $start = ''): array + { + $opt = [ + 'prefix' => '[', + 'suffix' => ']', + 'suffix-end' => true, + 'prefix-list' => '[', + 'suffix-list' => ']', + 'suffix-list-end' => true, + ]; + + if ($start === '') { + $currentPrefix = ''; + $currentSuffix = ''; + $currentSuffixEnd = false; + } elseif (array_is_list($source)) { + $currentPrefix = $opt['prefix-list']; + $currentSuffix = $opt['suffix-list']; + $currentSuffixEnd = $opt['suffix-list-end']; + } else { + $currentPrefix = $opt['prefix']; + $currentSuffix = $opt['suffix']; + $currentSuffixEnd = $opt['suffix-end']; + } + + $currentName = $start; + $result = []; + + foreach ($source as $key => $val) { + $currentName .= $currentPrefix . $key; + + if (is_array($val) && !empty($val)) { + $currentName .= $currentSuffix; + $result += self::flatten($val, $currentName); + } else { + if ($currentSuffixEnd) { + $currentName .= $currentSuffix; + } + + $result[$currentName] = ObjectSerializer::toString($val); + } + + $currentName = $start; + } + + return $result; + } + + /** + * formdata must be limited to scalars or arrays of scalar values, + * or a resource for a file upload. Here we iterate through all available + * data and identify how to handle each scenario + * + * @param string|bool|array|DateTime|ArrayAccess|SplFileObject $value + */ + protected function makeFormSafe(mixed $value) + { + if ($value instanceof SplFileObject) { + return $this->processFiles([$value])[0]; + } + + if (is_resource($value)) { + $this->has_file = true; + + return $value; + } + + if ($value instanceof ModelInterface) { + return $this->processModel($value); + } + + if (is_array($value) || is_object($value)) { + $data = []; + + foreach ($value as $k => $v) { + $data[$k] = $this->makeFormSafe($v); + } + + return $data; + } + + return ObjectSerializer::toString($value); + } + + /** + * We are able to handle nested ModelInterface. We do not simply call + * json_decode(json_encode()) because any given model may have binary data + * or other data that cannot be serialized to a JSON string + */ + protected function processModel(ModelInterface $model): array + { + $result = []; + + foreach ($model::openAPITypes() as $name => $type) { + $value = $model->offsetGet($name); + + if ($value === null) { + continue; + } + + if (str_contains($type, '\SplFileObject')) { + $file = is_array($value) ? $value : [$value]; + $result[$name] = $this->processFiles($file); + + continue; + } + + if ($value instanceof ModelInterface) { + $result[$name] = $this->processModel($value); + + continue; + } + + if (is_array($value) || is_object($value)) { + $result[$name] = $this->makeFormSafe($value); + + continue; + } + + $result[$name] = ObjectSerializer::toString($value); + } + + return $result; + } + + /** + * Handle file data + */ + protected function processFiles(array $files): array + { + $this->has_file = true; + + $result = []; + + foreach ($files as $i => $file) { + if (is_array($file)) { + $result[$i] = $this->processFiles($file); + + continue; + } + + if ($file instanceof StreamInterface) { + $result[$i] = $file; + + continue; + } + + if ($file instanceof SplFileObject) { + $result[$i] = $this->tryFopen($file); + } + } + + return $result; + } + + private function tryFopen(SplFileObject $file) + { + return Utils::tryFopen($file->getRealPath(), 'rb'); + } +} diff --git a/modules/openapi-generator/src/main/resources/php-nextgen/ObjectSerializer.mustache b/modules/openapi-generator/src/main/resources/php-nextgen/ObjectSerializer.mustache index 81dd8a50387f..32a77bef4bac 100644 --- a/modules/openapi-generator/src/main/resources/php-nextgen/ObjectSerializer.mustache +++ b/modules/openapi-generator/src/main/resources/php-nextgen/ObjectSerializer.mustache @@ -18,7 +18,6 @@ namespace {{invokerPackage}}; -use ArrayAccess; use DateTimeInterface; use DateTime; use GuzzleHttp\Psr7\Utils; @@ -316,37 +315,6 @@ class ObjectSerializer return self::toString($value); } - /** - * Take value and turn it into an array suitable for inclusion in - * the http body (form parameter). If it's a string, pass through unchanged - * If it's a datetime object, format it in ISO8601 - * - * @param string|bool|array|DateTime|ArrayAccess|\SplFileObject $value the value of the form parameter - * - * @return array [key => value] of formdata - */ - public static function toFormValue( - string $key, - string|bool|array|DateTime|ArrayAccess|\SplFileObject $value, - ): array { - if ($value instanceof \SplFileObject) { - return [$key => $value->getRealPath()]; - } elseif (is_array($value) || $value instanceof ArrayAccess) { - $flattened = []; - $result = []; - - self::flattenArray(json_decode(json_encode($value), true), $flattened); - - foreach ($flattened as $k => $v) { - $result["{$key}{$k}"] = self::toString($v); - } - - return $result; - } else { - return [$key => self::toString($value)]; - } - } - /** * Take value and turn it into a string suitable for inclusion in * the parameter. If it's a string, pass through unchanged @@ -612,58 +580,4 @@ class ObjectSerializer return $qs ? (string) substr($qs, 0, -1) : ''; } - - /** - * Flattens an array of Model object and generates an array compatible - * with formdata - a single-level array where the keys use bracket - * notation to signify nested data. - * - * credit: https://github.com/FranBar1966/FlatPHP - */ - private static function flattenArray( - ArrayAccess|array $source, - array &$destination, - string $start = '', - ) { - $opt = [ - 'prefix' => '[', - 'suffix' => ']', - 'suffix-end' => true, - 'prefix-list' => '[', - 'suffix-list' => ']', - 'suffix-list-end' => true, - ]; - - if (!is_array($source)) { - $source = (array) $source; - } - - if (array_is_list($source)) { - $currentPrefix = $opt['prefix-list']; - $currentSuffix = $opt['suffix-list']; - $currentSuffixEnd = $opt['suffix-list-end']; - } else { - $currentPrefix = $opt['prefix']; - $currentSuffix = $opt['suffix']; - $currentSuffixEnd = $opt['suffix-end']; - } - - $currentName = $start; - - foreach ($source as $key => $val) { - $currentName .= $currentPrefix.$key; - - if (is_array($val) && !empty($val)) { - $currentName .= "{$currentSuffix}"; - self::flattenArray($val, $destination, $currentName); - } else { - if ($currentSuffixEnd) { - $currentName .= $currentSuffix; - } - $destination[$currentName] = self::toString($val); - } - - $currentName = $start; - } - } } diff --git a/modules/openapi-generator/src/main/resources/php-nextgen/api.mustache b/modules/openapi-generator/src/main/resources/php-nextgen/api.mustache index dcf2019d0bfd..066db4a9e59b 100644 --- a/modules/openapi-generator/src/main/resources/php-nextgen/api.mustache +++ b/modules/openapi-generator/src/main/resources/php-nextgen/api.mustache @@ -31,6 +31,7 @@ use Psr\Http\Message\ResponseInterface; use {{invokerPackage}}\ApiException; use {{invokerPackage}}\Configuration; use {{invokerPackage}}\HeaderSelector; +use {{invokerPackage}}\FormDataProcessor; use {{invokerPackage}}\ObjectSerializer; /** @@ -724,25 +725,19 @@ use {{invokerPackage}}\ObjectSerializer; {{/pathParams}} {{#formParams}} + {{#-first}} // form params - if (${{paramName}} !== null) { - {{#isFile}} - $multipart = true; - $formParams['{{baseName}}'] = []; - $paramFiles = is_array(${{paramName}}) ? ${{paramName}} : [${{paramName}}]; - foreach ($paramFiles as $paramFile) { - $formParams['{{baseName}}'][] = $paramFile instanceof \Psr\Http\Message\StreamInterface - ? $paramFile - : \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('{{baseName}}', $paramFile)['{{baseName}}'], - 'rb' - ); - } - {{/isFile}} - {{^isFile}} - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('{{baseName}}', ${{paramName}})); - {{/isFile}} - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + {{/-first}} + '{{paramName}}' => ${{paramName}}, + {{#-last}} + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; + {{/-last}} {{/formParams}} $headers = $this->headerSelector->selectHeaders( diff --git a/modules/openapi-generator/src/main/resources/php/FormDataProcessor.mustache b/modules/openapi-generator/src/main/resources/php/FormDataProcessor.mustache new file mode 100644 index 000000000000..6985fd6c573f --- /dev/null +++ b/modules/openapi-generator/src/main/resources/php/FormDataProcessor.mustache @@ -0,0 +1,233 @@ +partial_header}} +/** + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +namespace {{invokerPackage}}; + +use ArrayAccess; +use DateTime; +use GuzzleHttp\Psr7\Utils; +use Psr\Http\Message\StreamInterface; +use SplFileObject; +use {{modelPackage}}\ModelInterface; + +/** + * FormDataProcessor Class Doc Comment + * + * @category Class + * @package {{invokerPackage}} + * @author OpenAPI Generator team + * @link https://openapi-generator.tech + */ +class FormDataProcessor +{ + /** + * Tags whether payload passed to ::prepare() contains one or more + * SplFileObject or stream values. + */ + public bool $has_file = false; + + /** + * Take value and turn it into an array suitable for inclusion in + * the http body (form parameter). If it's a string, pass through unchanged + * If it's a datetime object, format it in ISO8601 + * + * @param array $values the value of the form parameter + * + * @return array [key => value] of formdata + */ + public function prepare(array $values): array + { + $this->has_file = false; + $result = []; + + foreach ($values as $k => $v) { + if ($v === null) { + continue; + } + + $result[$k] = $this->makeFormSafe($v); + } + + return $result; + } + + /** + * Flattens a multi-level array of data and generates a single-level array + * compatible with formdata - a single-level array where the keys use bracket + * notation to signify nested data. + * + * credit: https://github.com/FranBar1966/FlatPHP + */ + public static function flatten(array $source, string $start = ''): array + { + $opt = [ + 'prefix' => '[', + 'suffix' => ']', + 'suffix-end' => true, + 'prefix-list' => '[', + 'suffix-list' => ']', + 'suffix-list-end' => true, + ]; + + if ($start === '') { + $currentPrefix = ''; + $currentSuffix = ''; + $currentSuffixEnd = false; + } elseif (array_is_list($source)) { + $currentPrefix = $opt['prefix-list']; + $currentSuffix = $opt['suffix-list']; + $currentSuffixEnd = $opt['suffix-list-end']; + } else { + $currentPrefix = $opt['prefix']; + $currentSuffix = $opt['suffix']; + $currentSuffixEnd = $opt['suffix-end']; + } + + $currentName = $start; + $result = []; + + foreach ($source as $key => $val) { + $currentName .= $currentPrefix.$key; + + if (is_array($val) && !empty($val)) { + $currentName .= $currentSuffix; + $result += self::flatten($val, $currentName); + } else { + if ($currentSuffixEnd) { + $currentName .= $currentSuffix; + } + + $result[$currentName] = ObjectSerializer::toString($val); + } + + $currentName = $start; + } + + return $result; + } + + /** + * formdata must be limited to scalars or arrays of scalar values, + * or a resource for a file upload. Here we iterate through all available + * data and identify how to handle each scenario + */ + protected function makeFormSafe($value) + { + if ($value instanceof SplFileObject) { + return $this->processFiles([$value])[0]; + } + + if (is_resource($value)) { + $this->has_file = true; + + return $value; + } + + if ($value instanceof ModelInterface) { + return $this->processModel($value); + } + + if (is_array($value) || (is_object($value) && !$value instanceof \DateTimeInterface)) { + $data = []; + + foreach ($value as $k => $v) { + $data[$k] = $this->makeFormSafe($v); + } + + return $data; + } + + return ObjectSerializer::toString($value); + } + + /** + * We are able to handle nested ModelInterface. We do not simply call + * json_decode(json_encode()) because any given model may have binary data + * or other data that cannot be serialized to a JSON string + */ + protected function processModel(ModelInterface $model): array + { + $result = []; + + foreach ($model::openAPITypes() as $name => $type) { + $value = $model->offsetGet($name); + + if ($value === null) { + continue; + } + + if (strpos($type, '\SplFileObject') !== false) { + $file = is_array($value) ? $value : [$value]; + $result[$name] = $this->processFiles($file); + + continue; + } + + if ($value instanceof ModelInterface) { + $result[$name] = $this->processModel($value); + + continue; + } + + if (is_array($value) || is_object($value)) { + $result[$name] = $this->makeFormSafe($value); + + continue; + } + + $result[$name] = ObjectSerializer::toString($value); + } + + return $result; + } + + /** + * Handle file data + */ + protected function processFiles(array $files): array + { + $this->has_file = true; + + $result = []; + + foreach ($files as $i => $file) { + if (is_array($file)) { + $result[$i] = $this->processFiles($file); + + continue; + } + + if ($file instanceof StreamInterface) { + $result[$i] = $file; + + continue; + } + + if ($file instanceof SplFileObject) { + $result[$i] = $this->tryFopen($file); + } + } + + return $result; + } + + private function tryFopen(SplFileObject $file) + { + return Utils::tryFopen($file->getRealPath(), 'rb'); + } +} diff --git a/modules/openapi-generator/src/main/resources/php/ObjectSerializer.mustache b/modules/openapi-generator/src/main/resources/php/ObjectSerializer.mustache index b076414630ca..9bf66a48288f 100644 --- a/modules/openapi-generator/src/main/resources/php/ObjectSerializer.mustache +++ b/modules/openapi-generator/src/main/resources/php/ObjectSerializer.mustache @@ -19,7 +19,6 @@ namespace {{invokerPackage}}; -use ArrayAccess; use GuzzleHttp\Psr7\Utils; use {{modelPackage}}\ModelInterface; @@ -315,35 +314,6 @@ class ObjectSerializer return self::toString($value); } - /** - * Take value and turn it into an array suitable for inclusion in - * the http body (form parameter). If it's a string, pass through unchanged - * If it's a datetime object, format it in ISO8601 - * - * @param string|bool|array|DateTime|ArrayAccess|\SplFileObject $value the value of the form parameter - * - * @return array [key => value] of formdata - */ - public static function toFormValue(string $key, $value) - { - if ($value instanceof \SplFileObject) { - return [$key => $value->getRealPath()]; - } elseif (is_array($value) || $value instanceof ArrayAccess) { - $flattened = []; - $result = []; - - self::flattenArray(json_decode(json_encode($value), true), $flattened); - - foreach ($flattened as $k => $v) { - $result["{$key}{$k}"] = self::toString($v); - } - - return $result; - } else { - return [$key => self::toString($value)]; - } - } - /** * Take value and turn it into a string suitable for inclusion in * the parameter. If it's a string, pass through unchanged @@ -617,81 +587,4 @@ class ObjectSerializer return $qs ? (string) substr($qs, 0, -1) : ''; } - - /** - * Flattens an array of Model object and generates an array compatible - * with formdata - a single-level array where the keys use bracket - * notation to signify nested data. - * - * @param \ArrayAccess|array $source - * - * credit: https://github.com/FranBar1966/FlatPHP - */ - private static function flattenArray( - $source, - array &$destination, - string $start = '' - ) { - $opt = [ - 'prefix' => '[', - 'suffix' => ']', - 'suffix-end' => true, - 'prefix-list' => '[', - 'suffix-list' => ']', - 'suffix-list-end' => true, - ]; - - if (!is_array($source)) { - $source = (array) $source; - } - - /** - * array_is_list only in PHP >= 8.1 - * - * credit: https://www.php.net/manual/en/function.array-is-list.php#127044 - */ - if (!function_exists('array_is_list')) { - function array_is_list(array $array) - { - $i = -1; - - foreach ($array as $k => $v) { - ++$i; - if ($k !== $i) { - return false; - } - } - - return true; - } - } - - if (array_is_list($source)) { - $currentPrefix = $opt['prefix-list']; - $currentSuffix = $opt['suffix-list']; - $currentSuffixEnd = $opt['suffix-list-end']; - } else { - $currentPrefix = $opt['prefix']; - $currentSuffix = $opt['suffix']; - $currentSuffixEnd = $opt['suffix-end']; - } - - $currentName = $start; - - foreach ($source as $key => $val) { - $currentName .= $currentPrefix.$key; - - if (is_array($val) && !empty($val)) { - $currentName .= "{$currentSuffix}"; - self::flattenArray($val, $destination, $currentName); - } else { - if ($currentSuffixEnd) { - $currentName .= $currentSuffix; - } - $destination[$currentName] = self::toString($val); - } - - $currentName = $start; - } - } } diff --git a/modules/openapi-generator/src/main/resources/php/api.mustache b/modules/openapi-generator/src/main/resources/php/api.mustache index 5e014c011e4e..a564a6220e7c 100644 --- a/modules/openapi-generator/src/main/resources/php/api.mustache +++ b/modules/openapi-generator/src/main/resources/php/api.mustache @@ -29,6 +29,7 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use {{invokerPackage}}\ApiException; use {{invokerPackage}}\Configuration; +use {{invokerPackage}}\FormDataProcessor; use {{invokerPackage}}\HeaderSelector; use {{invokerPackage}}\ObjectSerializer; @@ -646,23 +647,19 @@ use {{invokerPackage}}\ObjectSerializer; {{/pathParams}} {{#formParams}} + {{#-first}} // form params - if (${{paramName}} !== null) { - {{#isFile}} - $multipart = true; - $formParams['{{baseName}}'] = []; - $paramFiles = is_array(${{paramName}}) ? ${{paramName}} : [${{paramName}}]; - foreach ($paramFiles as $paramFile) { - $formParams['{{baseName}}'][] = \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('{{baseName}}', $paramFile)['{{baseName}}'], - 'rb' - ); - } - {{/isFile}} - {{^isFile}} - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('{{baseName}}', ${{paramName}})); - {{/isFile}} - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + {{/-first}} + '{{paramName}}' => ${{paramName}}, + {{#-last}} + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; + {{/-last}} {{/formParams}} {{#isMultipart}} diff --git a/modules/openapi-generator/src/main/resources/php/libraries/psr-18/api.mustache b/modules/openapi-generator/src/main/resources/php/libraries/psr-18/api.mustache index 7cbb99a58f0f..727f0eb25c2e 100644 --- a/modules/openapi-generator/src/main/resources/php/libraries/psr-18/api.mustache +++ b/modules/openapi-generator/src/main/resources/php/libraries/psr-18/api.mustache @@ -34,6 +34,7 @@ use {{invokerPackage}}\ApiException; use {{invokerPackage}}\Configuration; use {{invokerPackage}}\DebugPlugin; use {{invokerPackage}}\HeaderSelector; +use {{invokerPackage}}\FormDataProcessor; use {{invokerPackage}}\ObjectSerializer; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; @@ -605,23 +606,19 @@ use function sprintf; {{/pathParams}} {{#formParams}} + {{#-first}} // form params - if (${{paramName}} !== null) { - {{#isFile}} - $multipart = true; - $formParams['{{baseName}}'] = []; - $paramFiles = is_array(${{paramName}}) ? ${{paramName}} : [${{paramName}}]; - foreach ($paramFiles as $paramFile) { - $formParams['{{baseName}}'][] = \GuzzleHttp\Psr7\try_fopen( - ObjectSerializer::toFormValue('{{baseName}}', $paramFile)['{{baseName}}'], - 'rb' - ); - } - {{/isFile}} - {{^isFile}} - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('{{baseName}}', ${{paramName}})); - {{/isFile}} - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + {{/-first}} + '{{paramName}}' => ${{paramName}}, + {{#-last}} + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; + {{/-last}} {{/formParams}} $headers = $this->headerSelector->selectHeaders( diff --git a/modules/openapi-generator/src/test/resources/3_0/php/petstore-with-fake-endpoints-models-for-testing.yaml b/modules/openapi-generator/src/test/resources/3_0/php/petstore-with-fake-endpoints-models-for-testing.yaml index 18161aec8bed..c24a3993f617 100644 --- a/modules/openapi-generator/src/test/resources/3_0/php/petstore-with-fake-endpoints-models-for-testing.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/php/petstore-with-fake-endpoints-models-for-testing.yaml @@ -342,6 +342,40 @@ paths: multipart/form-data: schema: $ref: '#/components/schemas/PetWithFile' + '/pet/{petId}/uploadImageFullFormDataNested': + post: + tags: + - pet + summary: uploads an image attached to a Pet object as formdata + description: '' + operationId: uploadImageFullFormDataNested + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + pet: + $ref: '#/components/schemas/PetWithFile' /store/inventory: get: tags: diff --git a/samples/client/echo_api/php-nextgen-streaming/.openapi-generator/FILES b/samples/client/echo_api/php-nextgen-streaming/.openapi-generator/FILES index c51141c743e7..d849d4cffa36 100644 --- a/samples/client/echo_api/php-nextgen-streaming/.openapi-generator/FILES +++ b/samples/client/echo_api/php-nextgen-streaming/.openapi-generator/FILES @@ -32,6 +32,7 @@ src/Api/PathApi.php src/Api/QueryApi.php src/ApiException.php src/Configuration.php +src/FormDataProcessor.php src/HeaderSelector.php src/Model/Bird.php src/Model/Category.php diff --git a/samples/client/echo_api/php-nextgen-streaming/src/Api/AuthApi.php b/samples/client/echo_api/php-nextgen-streaming/src/Api/AuthApi.php index eac402dd75dd..3bbb12c0fde8 100644 --- a/samples/client/echo_api/php-nextgen-streaming/src/Api/AuthApi.php +++ b/samples/client/echo_api/php-nextgen-streaming/src/Api/AuthApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/echo_api/php-nextgen-streaming/src/Api/BodyApi.php b/samples/client/echo_api/php-nextgen-streaming/src/Api/BodyApi.php index bdf56a65bb26..934a6a5aff54 100644 --- a/samples/client/echo_api/php-nextgen-streaming/src/Api/BodyApi.php +++ b/samples/client/echo_api/php-nextgen-streaming/src/Api/BodyApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -899,19 +900,14 @@ public function testBodyMultipartFormdataArrayOfBinaryRequest( // form params - if ($files !== null) { - $multipart = true; - $formParams['files'] = []; - $paramFiles = is_array($files) ? $files : [$files]; - foreach ($paramFiles as $paramFile) { - $formParams['files'][] = $paramFile instanceof \Psr\Http\Message\StreamInterface - ? $paramFile - : \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('files', $paramFile)['files'], - 'rb' - ); - } - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'files' => $files, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['text/plain', ], @@ -1179,19 +1175,14 @@ public function testBodyMultipartFormdataSingleBinaryRequest( // form params - if ($my_file !== null) { - $multipart = true; - $formParams['my-file'] = []; - $paramFiles = is_array($my_file) ? $my_file : [$my_file]; - foreach ($paramFiles as $paramFile) { - $formParams['my-file'][] = $paramFile instanceof \Psr\Http\Message\StreamInterface - ? $paramFile - : \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('my-file', $paramFile)['my-file'], - 'rb' - ); - } - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'my_file' => $my_file, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['text/plain', ], diff --git a/samples/client/echo_api/php-nextgen-streaming/src/Api/FormApi.php b/samples/client/echo_api/php-nextgen-streaming/src/Api/FormApi.php index 23be7e56f066..8b78e059fa78 100644 --- a/samples/client/echo_api/php-nextgen-streaming/src/Api/FormApi.php +++ b/samples/client/echo_api/php-nextgen-streaming/src/Api/FormApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -366,17 +367,16 @@ public function testFormIntegerBooleanStringRequest( // form params - if ($integer_form !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('integer_form', $integer_form)); - } - // form params - if ($boolean_form !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('boolean_form', $boolean_form)); - } - // form params - if ($string_form !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('string_form', $string_form)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'integer_form' => $integer_form, + 'boolean_form' => $boolean_form, + 'string_form' => $string_form, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['text/plain', ], @@ -650,9 +650,14 @@ public function testFormObjectMultipartRequest( // form params - if ($marker !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('marker', $marker)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'marker' => $marker, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['text/plain', ], @@ -975,29 +980,19 @@ public function testFormOneofRequest( // form params - if ($form1 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('form1', $form1)); - } - // form params - if ($form2 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('form2', $form2)); - } - // form params - if ($form3 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('form3', $form3)); - } - // form params - if ($form4 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('form4', $form4)); - } - // form params - if ($id !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('id', $id)); - } - // form params - if ($name !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('name', $name)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'form1' => $form1, + 'form2' => $form2, + 'form3' => $form3, + 'form4' => $form4, + 'id' => $id, + 'name' => $name, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['text/plain', ], diff --git a/samples/client/echo_api/php-nextgen-streaming/src/Api/HeaderApi.php b/samples/client/echo_api/php-nextgen-streaming/src/Api/HeaderApi.php index 7590397a6d90..bda9d2c58c20 100644 --- a/samples/client/echo_api/php-nextgen-streaming/src/Api/HeaderApi.php +++ b/samples/client/echo_api/php-nextgen-streaming/src/Api/HeaderApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/echo_api/php-nextgen-streaming/src/Api/PathApi.php b/samples/client/echo_api/php-nextgen-streaming/src/Api/PathApi.php index d40c949e05e3..b74bfc1c40d5 100644 --- a/samples/client/echo_api/php-nextgen-streaming/src/Api/PathApi.php +++ b/samples/client/echo_api/php-nextgen-streaming/src/Api/PathApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/echo_api/php-nextgen-streaming/src/Api/QueryApi.php b/samples/client/echo_api/php-nextgen-streaming/src/Api/QueryApi.php index 34b6a4d324c8..2b9f868f18ca 100644 --- a/samples/client/echo_api/php-nextgen-streaming/src/Api/QueryApi.php +++ b/samples/client/echo_api/php-nextgen-streaming/src/Api/QueryApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/echo_api/php-nextgen-streaming/src/FormDataProcessor.php b/samples/client/echo_api/php-nextgen-streaming/src/FormDataProcessor.php new file mode 100644 index 000000000000..b7a31b3f2a01 --- /dev/null +++ b/samples/client/echo_api/php-nextgen-streaming/src/FormDataProcessor.php @@ -0,0 +1,237 @@ + $values the value of the form parameter + * + * @return array [key => value] of formdata + */ + public function prepare(array $values): array + { + $this->has_file = false; + $result = []; + + foreach ($values as $k => $v) { + if ($v === null) { + continue; + } + + $result[$k] = $this->makeFormSafe($v); + } + + return $result; + } + + /** + * Flattens a multi-level array of data and generates a single-level array + * compatible with formdata - a single-level array where the keys use bracket + * notation to signify nested data. + * + * credit: https://github.com/FranBar1966/FlatPHP + */ + public static function flatten(array $source, string $start = ''): array + { + $opt = [ + 'prefix' => '[', + 'suffix' => ']', + 'suffix-end' => true, + 'prefix-list' => '[', + 'suffix-list' => ']', + 'suffix-list-end' => true, + ]; + + if ($start === '') { + $currentPrefix = ''; + $currentSuffix = ''; + $currentSuffixEnd = false; + } elseif (array_is_list($source)) { + $currentPrefix = $opt['prefix-list']; + $currentSuffix = $opt['suffix-list']; + $currentSuffixEnd = $opt['suffix-list-end']; + } else { + $currentPrefix = $opt['prefix']; + $currentSuffix = $opt['suffix']; + $currentSuffixEnd = $opt['suffix-end']; + } + + $currentName = $start; + $result = []; + + foreach ($source as $key => $val) { + $currentName .= $currentPrefix . $key; + + if (is_array($val) && !empty($val)) { + $currentName .= $currentSuffix; + $result += self::flatten($val, $currentName); + } else { + if ($currentSuffixEnd) { + $currentName .= $currentSuffix; + } + + $result[$currentName] = ObjectSerializer::toString($val); + } + + $currentName = $start; + } + + return $result; + } + + /** + * formdata must be limited to scalars or arrays of scalar values, + * or a resource for a file upload. Here we iterate through all available + * data and identify how to handle each scenario + * + * @param string|bool|array|DateTime|ArrayAccess|SplFileObject $value + */ + protected function makeFormSafe(mixed $value) + { + if ($value instanceof SplFileObject) { + return $this->processFiles([$value])[0]; + } + + if (is_resource($value)) { + $this->has_file = true; + + return $value; + } + + if ($value instanceof ModelInterface) { + return $this->processModel($value); + } + + if (is_array($value) || is_object($value)) { + $data = []; + + foreach ($value as $k => $v) { + $data[$k] = $this->makeFormSafe($v); + } + + return $data; + } + + return ObjectSerializer::toString($value); + } + + /** + * We are able to handle nested ModelInterface. We do not simply call + * json_decode(json_encode()) because any given model may have binary data + * or other data that cannot be serialized to a JSON string + */ + protected function processModel(ModelInterface $model): array + { + $result = []; + + foreach ($model::openAPITypes() as $name => $type) { + $value = $model->offsetGet($name); + + if ($value === null) { + continue; + } + + if (str_contains($type, '\SplFileObject')) { + $file = is_array($value) ? $value : [$value]; + $result[$name] = $this->processFiles($file); + + continue; + } + + if ($value instanceof ModelInterface) { + $result[$name] = $this->processModel($value); + + continue; + } + + if (is_array($value) || is_object($value)) { + $result[$name] = $this->makeFormSafe($value); + + continue; + } + + $result[$name] = ObjectSerializer::toString($value); + } + + return $result; + } + + /** + * Handle file data + */ + protected function processFiles(array $files): array + { + $this->has_file = true; + + $result = []; + + foreach ($files as $i => $file) { + if (is_array($file)) { + $result[$i] = $this->processFiles($file); + + continue; + } + + if ($file instanceof StreamInterface) { + $result[$i] = $file; + + continue; + } + + if ($file instanceof SplFileObject) { + $result[$i] = $this->tryFopen($file); + } + } + + return $result; + } + + private function tryFopen(SplFileObject $file) + { + return Utils::tryFopen($file->getRealPath(), 'rb'); + } +} diff --git a/samples/client/echo_api/php-nextgen-streaming/src/ObjectSerializer.php b/samples/client/echo_api/php-nextgen-streaming/src/ObjectSerializer.php index ac7cf8884fd6..135df37659ed 100644 --- a/samples/client/echo_api/php-nextgen-streaming/src/ObjectSerializer.php +++ b/samples/client/echo_api/php-nextgen-streaming/src/ObjectSerializer.php @@ -28,7 +28,6 @@ namespace OpenAPI\Client; -use ArrayAccess; use DateTimeInterface; use DateTime; use GuzzleHttp\Psr7\Utils; @@ -326,37 +325,6 @@ public static function toHeaderValue(string $value): string return self::toString($value); } - /** - * Take value and turn it into an array suitable for inclusion in - * the http body (form parameter). If it's a string, pass through unchanged - * If it's a datetime object, format it in ISO8601 - * - * @param string|bool|array|DateTime|ArrayAccess|\SplFileObject $value the value of the form parameter - * - * @return array [key => value] of formdata - */ - public static function toFormValue( - string $key, - string|bool|array|DateTime|ArrayAccess|\SplFileObject $value, - ): array { - if ($value instanceof \SplFileObject) { - return [$key => $value->getRealPath()]; - } elseif (is_array($value) || $value instanceof ArrayAccess) { - $flattened = []; - $result = []; - - self::flattenArray(json_decode(json_encode($value), true), $flattened); - - foreach ($flattened as $k => $v) { - $result["{$key}{$k}"] = self::toString($v); - } - - return $result; - } else { - return [$key => self::toString($value)]; - } - } - /** * Take value and turn it into a string suitable for inclusion in * the parameter. If it's a string, pass through unchanged @@ -622,58 +590,4 @@ public static function buildQuery(array $params, $encoding = PHP_QUERY_RFC3986): return $qs ? (string) substr($qs, 0, -1) : ''; } - - /** - * Flattens an array of Model object and generates an array compatible - * with formdata - a single-level array where the keys use bracket - * notation to signify nested data. - * - * credit: https://github.com/FranBar1966/FlatPHP - */ - private static function flattenArray( - ArrayAccess|array $source, - array &$destination, - string $start = '', - ) { - $opt = [ - 'prefix' => '[', - 'suffix' => ']', - 'suffix-end' => true, - 'prefix-list' => '[', - 'suffix-list' => ']', - 'suffix-list-end' => true, - ]; - - if (!is_array($source)) { - $source = (array) $source; - } - - if (array_is_list($source)) { - $currentPrefix = $opt['prefix-list']; - $currentSuffix = $opt['suffix-list']; - $currentSuffixEnd = $opt['suffix-list-end']; - } else { - $currentPrefix = $opt['prefix']; - $currentSuffix = $opt['suffix']; - $currentSuffixEnd = $opt['suffix-end']; - } - - $currentName = $start; - - foreach ($source as $key => $val) { - $currentName .= $currentPrefix.$key; - - if (is_array($val) && !empty($val)) { - $currentName .= "{$currentSuffix}"; - self::flattenArray($val, $destination, $currentName); - } else { - if ($currentSuffixEnd) { - $currentName .= $currentSuffix; - } - $destination[$currentName] = self::toString($val); - } - - $currentName = $start; - } - } } diff --git a/samples/client/echo_api/php-nextgen/.openapi-generator/FILES b/samples/client/echo_api/php-nextgen/.openapi-generator/FILES index c51141c743e7..d849d4cffa36 100644 --- a/samples/client/echo_api/php-nextgen/.openapi-generator/FILES +++ b/samples/client/echo_api/php-nextgen/.openapi-generator/FILES @@ -32,6 +32,7 @@ src/Api/PathApi.php src/Api/QueryApi.php src/ApiException.php src/Configuration.php +src/FormDataProcessor.php src/HeaderSelector.php src/Model/Bird.php src/Model/Category.php diff --git a/samples/client/echo_api/php-nextgen/src/Api/AuthApi.php b/samples/client/echo_api/php-nextgen/src/Api/AuthApi.php index eac402dd75dd..3bbb12c0fde8 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/AuthApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/AuthApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/echo_api/php-nextgen/src/Api/BodyApi.php b/samples/client/echo_api/php-nextgen/src/Api/BodyApi.php index 3d63c8a2d4b7..831c78b37c59 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/BodyApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/BodyApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -899,19 +900,14 @@ public function testBodyMultipartFormdataArrayOfBinaryRequest( // form params - if ($files !== null) { - $multipart = true; - $formParams['files'] = []; - $paramFiles = is_array($files) ? $files : [$files]; - foreach ($paramFiles as $paramFile) { - $formParams['files'][] = $paramFile instanceof \Psr\Http\Message\StreamInterface - ? $paramFile - : \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('files', $paramFile)['files'], - 'rb' - ); - } - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'files' => $files, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['text/plain', ], @@ -1179,19 +1175,14 @@ public function testBodyMultipartFormdataSingleBinaryRequest( // form params - if ($my_file !== null) { - $multipart = true; - $formParams['my-file'] = []; - $paramFiles = is_array($my_file) ? $my_file : [$my_file]; - foreach ($paramFiles as $paramFile) { - $formParams['my-file'][] = $paramFile instanceof \Psr\Http\Message\StreamInterface - ? $paramFile - : \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('my-file', $paramFile)['my-file'], - 'rb' - ); - } - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'my_file' => $my_file, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['text/plain', ], diff --git a/samples/client/echo_api/php-nextgen/src/Api/FormApi.php b/samples/client/echo_api/php-nextgen/src/Api/FormApi.php index 23be7e56f066..8b78e059fa78 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/FormApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/FormApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -366,17 +367,16 @@ public function testFormIntegerBooleanStringRequest( // form params - if ($integer_form !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('integer_form', $integer_form)); - } - // form params - if ($boolean_form !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('boolean_form', $boolean_form)); - } - // form params - if ($string_form !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('string_form', $string_form)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'integer_form' => $integer_form, + 'boolean_form' => $boolean_form, + 'string_form' => $string_form, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['text/plain', ], @@ -650,9 +650,14 @@ public function testFormObjectMultipartRequest( // form params - if ($marker !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('marker', $marker)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'marker' => $marker, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['text/plain', ], @@ -975,29 +980,19 @@ public function testFormOneofRequest( // form params - if ($form1 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('form1', $form1)); - } - // form params - if ($form2 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('form2', $form2)); - } - // form params - if ($form3 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('form3', $form3)); - } - // form params - if ($form4 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('form4', $form4)); - } - // form params - if ($id !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('id', $id)); - } - // form params - if ($name !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('name', $name)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'form1' => $form1, + 'form2' => $form2, + 'form3' => $form3, + 'form4' => $form4, + 'id' => $id, + 'name' => $name, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['text/plain', ], diff --git a/samples/client/echo_api/php-nextgen/src/Api/HeaderApi.php b/samples/client/echo_api/php-nextgen/src/Api/HeaderApi.php index 7590397a6d90..bda9d2c58c20 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/HeaderApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/HeaderApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/echo_api/php-nextgen/src/Api/PathApi.php b/samples/client/echo_api/php-nextgen/src/Api/PathApi.php index d40c949e05e3..b74bfc1c40d5 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/PathApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/PathApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/echo_api/php-nextgen/src/Api/QueryApi.php b/samples/client/echo_api/php-nextgen/src/Api/QueryApi.php index 34b6a4d324c8..2b9f868f18ca 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/QueryApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/QueryApi.php @@ -41,6 +41,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/echo_api/php-nextgen/src/FormDataProcessor.php b/samples/client/echo_api/php-nextgen/src/FormDataProcessor.php new file mode 100644 index 000000000000..b7a31b3f2a01 --- /dev/null +++ b/samples/client/echo_api/php-nextgen/src/FormDataProcessor.php @@ -0,0 +1,237 @@ + $values the value of the form parameter + * + * @return array [key => value] of formdata + */ + public function prepare(array $values): array + { + $this->has_file = false; + $result = []; + + foreach ($values as $k => $v) { + if ($v === null) { + continue; + } + + $result[$k] = $this->makeFormSafe($v); + } + + return $result; + } + + /** + * Flattens a multi-level array of data and generates a single-level array + * compatible with formdata - a single-level array where the keys use bracket + * notation to signify nested data. + * + * credit: https://github.com/FranBar1966/FlatPHP + */ + public static function flatten(array $source, string $start = ''): array + { + $opt = [ + 'prefix' => '[', + 'suffix' => ']', + 'suffix-end' => true, + 'prefix-list' => '[', + 'suffix-list' => ']', + 'suffix-list-end' => true, + ]; + + if ($start === '') { + $currentPrefix = ''; + $currentSuffix = ''; + $currentSuffixEnd = false; + } elseif (array_is_list($source)) { + $currentPrefix = $opt['prefix-list']; + $currentSuffix = $opt['suffix-list']; + $currentSuffixEnd = $opt['suffix-list-end']; + } else { + $currentPrefix = $opt['prefix']; + $currentSuffix = $opt['suffix']; + $currentSuffixEnd = $opt['suffix-end']; + } + + $currentName = $start; + $result = []; + + foreach ($source as $key => $val) { + $currentName .= $currentPrefix . $key; + + if (is_array($val) && !empty($val)) { + $currentName .= $currentSuffix; + $result += self::flatten($val, $currentName); + } else { + if ($currentSuffixEnd) { + $currentName .= $currentSuffix; + } + + $result[$currentName] = ObjectSerializer::toString($val); + } + + $currentName = $start; + } + + return $result; + } + + /** + * formdata must be limited to scalars or arrays of scalar values, + * or a resource for a file upload. Here we iterate through all available + * data and identify how to handle each scenario + * + * @param string|bool|array|DateTime|ArrayAccess|SplFileObject $value + */ + protected function makeFormSafe(mixed $value) + { + if ($value instanceof SplFileObject) { + return $this->processFiles([$value])[0]; + } + + if (is_resource($value)) { + $this->has_file = true; + + return $value; + } + + if ($value instanceof ModelInterface) { + return $this->processModel($value); + } + + if (is_array($value) || is_object($value)) { + $data = []; + + foreach ($value as $k => $v) { + $data[$k] = $this->makeFormSafe($v); + } + + return $data; + } + + return ObjectSerializer::toString($value); + } + + /** + * We are able to handle nested ModelInterface. We do not simply call + * json_decode(json_encode()) because any given model may have binary data + * or other data that cannot be serialized to a JSON string + */ + protected function processModel(ModelInterface $model): array + { + $result = []; + + foreach ($model::openAPITypes() as $name => $type) { + $value = $model->offsetGet($name); + + if ($value === null) { + continue; + } + + if (str_contains($type, '\SplFileObject')) { + $file = is_array($value) ? $value : [$value]; + $result[$name] = $this->processFiles($file); + + continue; + } + + if ($value instanceof ModelInterface) { + $result[$name] = $this->processModel($value); + + continue; + } + + if (is_array($value) || is_object($value)) { + $result[$name] = $this->makeFormSafe($value); + + continue; + } + + $result[$name] = ObjectSerializer::toString($value); + } + + return $result; + } + + /** + * Handle file data + */ + protected function processFiles(array $files): array + { + $this->has_file = true; + + $result = []; + + foreach ($files as $i => $file) { + if (is_array($file)) { + $result[$i] = $this->processFiles($file); + + continue; + } + + if ($file instanceof StreamInterface) { + $result[$i] = $file; + + continue; + } + + if ($file instanceof SplFileObject) { + $result[$i] = $this->tryFopen($file); + } + } + + return $result; + } + + private function tryFopen(SplFileObject $file) + { + return Utils::tryFopen($file->getRealPath(), 'rb'); + } +} diff --git a/samples/client/echo_api/php-nextgen/src/ObjectSerializer.php b/samples/client/echo_api/php-nextgen/src/ObjectSerializer.php index ac7cf8884fd6..135df37659ed 100644 --- a/samples/client/echo_api/php-nextgen/src/ObjectSerializer.php +++ b/samples/client/echo_api/php-nextgen/src/ObjectSerializer.php @@ -28,7 +28,6 @@ namespace OpenAPI\Client; -use ArrayAccess; use DateTimeInterface; use DateTime; use GuzzleHttp\Psr7\Utils; @@ -326,37 +325,6 @@ public static function toHeaderValue(string $value): string return self::toString($value); } - /** - * Take value and turn it into an array suitable for inclusion in - * the http body (form parameter). If it's a string, pass through unchanged - * If it's a datetime object, format it in ISO8601 - * - * @param string|bool|array|DateTime|ArrayAccess|\SplFileObject $value the value of the form parameter - * - * @return array [key => value] of formdata - */ - public static function toFormValue( - string $key, - string|bool|array|DateTime|ArrayAccess|\SplFileObject $value, - ): array { - if ($value instanceof \SplFileObject) { - return [$key => $value->getRealPath()]; - } elseif (is_array($value) || $value instanceof ArrayAccess) { - $flattened = []; - $result = []; - - self::flattenArray(json_decode(json_encode($value), true), $flattened); - - foreach ($flattened as $k => $v) { - $result["{$key}{$k}"] = self::toString($v); - } - - return $result; - } else { - return [$key => self::toString($value)]; - } - } - /** * Take value and turn it into a string suitable for inclusion in * the parameter. If it's a string, pass through unchanged @@ -622,58 +590,4 @@ public static function buildQuery(array $params, $encoding = PHP_QUERY_RFC3986): return $qs ? (string) substr($qs, 0, -1) : ''; } - - /** - * Flattens an array of Model object and generates an array compatible - * with formdata - a single-level array where the keys use bracket - * notation to signify nested data. - * - * credit: https://github.com/FranBar1966/FlatPHP - */ - private static function flattenArray( - ArrayAccess|array $source, - array &$destination, - string $start = '', - ) { - $opt = [ - 'prefix' => '[', - 'suffix' => ']', - 'suffix-end' => true, - 'prefix-list' => '[', - 'suffix-list' => ']', - 'suffix-list-end' => true, - ]; - - if (!is_array($source)) { - $source = (array) $source; - } - - if (array_is_list($source)) { - $currentPrefix = $opt['prefix-list']; - $currentSuffix = $opt['suffix-list']; - $currentSuffixEnd = $opt['suffix-list-end']; - } else { - $currentPrefix = $opt['prefix']; - $currentSuffix = $opt['suffix']; - $currentSuffixEnd = $opt['suffix-end']; - } - - $currentName = $start; - - foreach ($source as $key => $val) { - $currentName .= $currentPrefix.$key; - - if (is_array($val) && !empty($val)) { - $currentName .= "{$currentSuffix}"; - self::flattenArray($val, $destination, $currentName); - } else { - if ($currentSuffixEnd) { - $currentName .= $currentSuffix; - } - $destination[$currentName] = self::toString($val); - } - - $currentName = $start; - } - } } diff --git a/samples/client/petstore/php-nextgen/OpenAPIClient-php/.openapi-generator/FILES b/samples/client/petstore/php-nextgen/OpenAPIClient-php/.openapi-generator/FILES index 15feb03a699f..235705a8ad23 100644 --- a/samples/client/petstore/php-nextgen/OpenAPIClient-php/.openapi-generator/FILES +++ b/samples/client/petstore/php-nextgen/OpenAPIClient-php/.openapi-generator/FILES @@ -74,6 +74,7 @@ src/Api/StoreApi.php src/Api/UserApi.php src/ApiException.php src/Configuration.php +src/FormDataProcessor.php src/HeaderSelector.php src/Model/AdditionalPropertiesClass.php src/Model/AllOfWithSingleRef.php diff --git a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/AnotherFakeApi.php b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/AnotherFakeApi.php index 9927734e053a..1253d56a33a2 100644 --- a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/AnotherFakeApi.php +++ b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/AnotherFakeApi.php @@ -40,6 +40,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/DefaultApi.php b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/DefaultApi.php index bdc2f7af0c14..3d148ab72e86 100644 --- a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/DefaultApi.php +++ b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/DefaultApi.php @@ -40,6 +40,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/FakeApi.php b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/FakeApi.php index 88940c8bc124..b1c87f2bfab0 100644 --- a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/FakeApi.php +++ b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/FakeApi.php @@ -40,6 +40,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -5678,71 +5679,27 @@ public function testEndpointParametersRequest( // form params - if ($integer !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('integer', $integer)); - } - // form params - if ($int32 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('int32', $int32)); - } - // form params - if ($int64 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('int64', $int64)); - } - // form params - if ($number !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('number', $number)); - } - // form params - if ($float !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('float', $float)); - } - // form params - if ($double !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('double', $double)); - } - // form params - if ($string !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('string', $string)); - } - // form params - if ($pattern_without_delimiter !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('pattern_without_delimiter', $pattern_without_delimiter)); - } - // form params - if ($byte !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('byte', $byte)); - } - // form params - if ($binary !== null) { - $multipart = true; - $formParams['binary'] = []; - $paramFiles = is_array($binary) ? $binary : [$binary]; - foreach ($paramFiles as $paramFile) { - $formParams['binary'][] = $paramFile instanceof \Psr\Http\Message\StreamInterface - ? $paramFile - : \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('binary', $paramFile)['binary'], - 'rb' - ); - } - } - // form params - if ($date !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('date', $date)); - } - // form params - if ($date_time !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('dateTime', $date_time)); - } - // form params - if ($password !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('password', $password)); - } - // form params - if ($callback !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('callback', $callback)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'integer' => $integer, + 'int32' => $int32, + 'int64' => $int64, + 'number' => $number, + 'float' => $float, + 'double' => $double, + 'string' => $string, + 'pattern_without_delimiter' => $pattern_without_delimiter, + 'byte' => $byte, + 'binary' => $binary, + 'date' => $date, + 'date_time' => $date_time, + 'password' => $password, + 'callback' => $callback, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], @@ -6110,13 +6067,15 @@ public function testEnumParametersRequest( // form params - if ($enum_form_string_array !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('enum_form_string_array', $enum_form_string_array)); - } - // form params - if ($enum_form_string !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('enum_form_string', $enum_form_string)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'enum_form_string_array' => $enum_form_string_array, + 'enum_form_string' => $enum_form_string, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], @@ -7148,13 +7107,15 @@ public function testJsonFormDataRequest( // form params - if ($param !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('param', $param)); - } - // form params - if ($param2 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('param2', $param2)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'param' => $param, + 'param2' => $param2, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], diff --git a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/FakeClassnameTags123Api.php b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/FakeClassnameTags123Api.php index 30635a2fe9ee..49d4dc6236d1 100644 --- a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/FakeClassnameTags123Api.php +++ b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/FakeClassnameTags123Api.php @@ -40,6 +40,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/PetApi.php b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/PetApi.php index 702bc9e51cc6..31a2e1de91eb 100644 --- a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/PetApi.php +++ b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/PetApi.php @@ -40,6 +40,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -2236,13 +2237,15 @@ public function updatePetWithFormRequest( } // form params - if ($name !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('name', $name)); - } - // form params - if ($status !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('status', $status)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'name' => $name, + 'status' => $status, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], @@ -2550,23 +2553,15 @@ public function uploadFileRequest( } // form params - if ($additional_metadata !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('additionalMetadata', $additional_metadata)); - } - // form params - if ($file !== null) { - $multipart = true; - $formParams['file'] = []; - $paramFiles = is_array($file) ? $file : [$file]; - foreach ($paramFiles as $paramFile) { - $formParams['file'][] = $paramFile instanceof \Psr\Http\Message\StreamInterface - ? $paramFile - : \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('file', $paramFile)['file'], - 'rb' - ); - } - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'additional_metadata' => $additional_metadata, + 'file' => $file, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['application/json', ], @@ -2880,23 +2875,15 @@ public function uploadFileWithRequiredFileRequest( } // form params - if ($additional_metadata !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('additionalMetadata', $additional_metadata)); - } - // form params - if ($required_file !== null) { - $multipart = true; - $formParams['requiredFile'] = []; - $paramFiles = is_array($required_file) ? $required_file : [$required_file]; - foreach ($paramFiles as $paramFile) { - $formParams['requiredFile'][] = $paramFile instanceof \Psr\Http\Message\StreamInterface - ? $paramFile - : \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('requiredFile', $paramFile)['requiredFile'], - 'rb' - ); - } - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'additional_metadata' => $additional_metadata, + 'required_file' => $required_file, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['application/json', ], diff --git a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/StoreApi.php b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/StoreApi.php index d815e6651c47..eb4a1024cc36 100644 --- a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/StoreApi.php +++ b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/StoreApi.php @@ -40,6 +40,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/UserApi.php b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/UserApi.php index 880491926710..c6d18d78a134 100644 --- a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/UserApi.php +++ b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/Api/UserApi.php @@ -40,6 +40,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** diff --git a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/FormDataProcessor.php b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/FormDataProcessor.php new file mode 100644 index 000000000000..f044f1c6dbc7 --- /dev/null +++ b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/FormDataProcessor.php @@ -0,0 +1,236 @@ + $values the value of the form parameter + * + * @return array [key => value] of formdata + */ + public function prepare(array $values): array + { + $this->has_file = false; + $result = []; + + foreach ($values as $k => $v) { + if ($v === null) { + continue; + } + + $result[$k] = $this->makeFormSafe($v); + } + + return $result; + } + + /** + * Flattens a multi-level array of data and generates a single-level array + * compatible with formdata - a single-level array where the keys use bracket + * notation to signify nested data. + * + * credit: https://github.com/FranBar1966/FlatPHP + */ + public static function flatten(array $source, string $start = ''): array + { + $opt = [ + 'prefix' => '[', + 'suffix' => ']', + 'suffix-end' => true, + 'prefix-list' => '[', + 'suffix-list' => ']', + 'suffix-list-end' => true, + ]; + + if ($start === '') { + $currentPrefix = ''; + $currentSuffix = ''; + $currentSuffixEnd = false; + } elseif (array_is_list($source)) { + $currentPrefix = $opt['prefix-list']; + $currentSuffix = $opt['suffix-list']; + $currentSuffixEnd = $opt['suffix-list-end']; + } else { + $currentPrefix = $opt['prefix']; + $currentSuffix = $opt['suffix']; + $currentSuffixEnd = $opt['suffix-end']; + } + + $currentName = $start; + $result = []; + + foreach ($source as $key => $val) { + $currentName .= $currentPrefix . $key; + + if (is_array($val) && !empty($val)) { + $currentName .= $currentSuffix; + $result += self::flatten($val, $currentName); + } else { + if ($currentSuffixEnd) { + $currentName .= $currentSuffix; + } + + $result[$currentName] = ObjectSerializer::toString($val); + } + + $currentName = $start; + } + + return $result; + } + + /** + * formdata must be limited to scalars or arrays of scalar values, + * or a resource for a file upload. Here we iterate through all available + * data and identify how to handle each scenario + * + * @param string|bool|array|DateTime|ArrayAccess|SplFileObject $value + */ + protected function makeFormSafe(mixed $value) + { + if ($value instanceof SplFileObject) { + return $this->processFiles([$value])[0]; + } + + if (is_resource($value)) { + $this->has_file = true; + + return $value; + } + + if ($value instanceof ModelInterface) { + return $this->processModel($value); + } + + if (is_array($value) || is_object($value)) { + $data = []; + + foreach ($value as $k => $v) { + $data[$k] = $this->makeFormSafe($v); + } + + return $data; + } + + return ObjectSerializer::toString($value); + } + + /** + * We are able to handle nested ModelInterface. We do not simply call + * json_decode(json_encode()) because any given model may have binary data + * or other data that cannot be serialized to a JSON string + */ + protected function processModel(ModelInterface $model): array + { + $result = []; + + foreach ($model::openAPITypes() as $name => $type) { + $value = $model->offsetGet($name); + + if ($value === null) { + continue; + } + + if (str_contains($type, '\SplFileObject')) { + $file = is_array($value) ? $value : [$value]; + $result[$name] = $this->processFiles($file); + + continue; + } + + if ($value instanceof ModelInterface) { + $result[$name] = $this->processModel($value); + + continue; + } + + if (is_array($value) || is_object($value)) { + $result[$name] = $this->makeFormSafe($value); + + continue; + } + + $result[$name] = ObjectSerializer::toString($value); + } + + return $result; + } + + /** + * Handle file data + */ + protected function processFiles(array $files): array + { + $this->has_file = true; + + $result = []; + + foreach ($files as $i => $file) { + if (is_array($file)) { + $result[$i] = $this->processFiles($file); + + continue; + } + + if ($file instanceof StreamInterface) { + $result[$i] = $file; + + continue; + } + + if ($file instanceof SplFileObject) { + $result[$i] = $this->tryFopen($file); + } + } + + return $result; + } + + private function tryFopen(SplFileObject $file) + { + return Utils::tryFopen($file->getRealPath(), 'rb'); + } +} diff --git a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/ObjectSerializer.php b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/ObjectSerializer.php index a3f70ac9a987..87daefd327c2 100644 --- a/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/ObjectSerializer.php +++ b/samples/client/petstore/php-nextgen/OpenAPIClient-php/src/ObjectSerializer.php @@ -27,7 +27,6 @@ namespace OpenAPI\Client; -use ArrayAccess; use DateTimeInterface; use DateTime; use GuzzleHttp\Psr7\Utils; @@ -325,37 +324,6 @@ public static function toHeaderValue(string $value): string return self::toString($value); } - /** - * Take value and turn it into an array suitable for inclusion in - * the http body (form parameter). If it's a string, pass through unchanged - * If it's a datetime object, format it in ISO8601 - * - * @param string|bool|array|DateTime|ArrayAccess|\SplFileObject $value the value of the form parameter - * - * @return array [key => value] of formdata - */ - public static function toFormValue( - string $key, - string|bool|array|DateTime|ArrayAccess|\SplFileObject $value, - ): array { - if ($value instanceof \SplFileObject) { - return [$key => $value->getRealPath()]; - } elseif (is_array($value) || $value instanceof ArrayAccess) { - $flattened = []; - $result = []; - - self::flattenArray(json_decode(json_encode($value), true), $flattened); - - foreach ($flattened as $k => $v) { - $result["{$key}{$k}"] = self::toString($v); - } - - return $result; - } else { - return [$key => self::toString($value)]; - } - } - /** * Take value and turn it into a string suitable for inclusion in * the parameter. If it's a string, pass through unchanged @@ -621,58 +589,4 @@ public static function buildQuery(array $params, $encoding = PHP_QUERY_RFC3986): return $qs ? (string) substr($qs, 0, -1) : ''; } - - /** - * Flattens an array of Model object and generates an array compatible - * with formdata - a single-level array where the keys use bracket - * notation to signify nested data. - * - * credit: https://github.com/FranBar1966/FlatPHP - */ - private static function flattenArray( - ArrayAccess|array $source, - array &$destination, - string $start = '', - ) { - $opt = [ - 'prefix' => '[', - 'suffix' => ']', - 'suffix-end' => true, - 'prefix-list' => '[', - 'suffix-list' => ']', - 'suffix-list-end' => true, - ]; - - if (!is_array($source)) { - $source = (array) $source; - } - - if (array_is_list($source)) { - $currentPrefix = $opt['prefix-list']; - $currentSuffix = $opt['suffix-list']; - $currentSuffixEnd = $opt['suffix-list-end']; - } else { - $currentPrefix = $opt['prefix']; - $currentSuffix = $opt['suffix']; - $currentSuffixEnd = $opt['suffix-end']; - } - - $currentName = $start; - - foreach ($source as $key => $val) { - $currentName .= $currentPrefix.$key; - - if (is_array($val) && !empty($val)) { - $currentName .= "{$currentSuffix}"; - self::flattenArray($val, $destination, $currentName); - } else { - if ($currentSuffixEnd) { - $currentName .= $currentSuffix; - } - $destination[$currentName] = self::toString($val); - } - - $currentName = $start; - } - } } diff --git a/samples/client/petstore/php/OpenAPIClient-php/.openapi-generator/FILES b/samples/client/petstore/php/OpenAPIClient-php/.openapi-generator/FILES index 9b385cc2b953..35ab7abe7d2c 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/.openapi-generator/FILES +++ b/samples/client/petstore/php/OpenAPIClient-php/.openapi-generator/FILES @@ -54,6 +54,7 @@ docs/Model/OuterEnumInteger.md docs/Model/OuterEnumIntegerDefaultValue.md docs/Model/OuterObjectWithEnumProperty.md docs/Model/Pet.md +docs/Model/PetWithFile.md docs/Model/PropertyNameMapping.md docs/Model/ReadOnlyFirst.md docs/Model/SingleRefType.md @@ -71,6 +72,7 @@ lib/Api/StoreApi.php lib/Api/UserApi.php lib/ApiException.php lib/Configuration.php +lib/FormDataProcessor.php lib/HeaderSelector.php lib/Model/AdditionalPropertiesClass.php lib/Model/AllOfWithSingleRef.php @@ -117,6 +119,7 @@ lib/Model/OuterEnumInteger.php lib/Model/OuterEnumIntegerDefaultValue.php lib/Model/OuterObjectWithEnumProperty.php lib/Model/Pet.php +lib/Model/PetWithFile.php lib/Model/PropertyNameMapping.php lib/Model/ReadOnlyFirst.php lib/Model/SingleRefType.php diff --git a/samples/client/petstore/php/OpenAPIClient-php/README.md b/samples/client/petstore/php/OpenAPIClient-php/README.md index 90688e77c356..9763fb3fd6ee 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/README.md +++ b/samples/client/petstore/php/OpenAPIClient-php/README.md @@ -112,6 +112,7 @@ Class | Method | HTTP request | Description *PetApi* | [**uploadFile**](docs/Api/PetApi.md#uploadfile) | **POST** /pet/{petId}/uploadImage | uploads an image *PetApi* | [**uploadFileWithRequiredFile**](docs/Api/PetApi.md#uploadfilewithrequiredfile) | **POST** /fake/{petId}/uploadImageWithRequiredFile | uploads an image (required) *PetApi* | [**uploadImageFullFormData**](docs/Api/PetApi.md#uploadimagefullformdata) | **POST** /pet/{petId}/uploadImageFullFormData | uploads an image attached to a Pet object as formdata +*PetApi* | [**uploadImageFullFormDataNested**](docs/Api/PetApi.md#uploadimagefullformdatanested) | **POST** /pet/{petId}/uploadImageFullFormDataNested | uploads an image attached to a Pet object as formdata *StoreApi* | [**deleteOrder**](docs/Api/StoreApi.md#deleteorder) | **DELETE** /store/order/{order_id} | Delete purchase order by ID *StoreApi* | [**getInventory**](docs/Api/StoreApi.md#getinventory) | **GET** /store/inventory | Returns pet inventories by status *StoreApi* | [**getOrderById**](docs/Api/StoreApi.md#getorderbyid) | **GET** /store/order/{order_id} | Find purchase order by ID @@ -171,6 +172,7 @@ Class | Method | HTTP request | Description - [OuterEnumIntegerDefaultValue](docs/Model/OuterEnumIntegerDefaultValue.md) - [OuterObjectWithEnumProperty](docs/Model/OuterObjectWithEnumProperty.md) - [Pet](docs/Model/Pet.md) +- [PetWithFile](docs/Model/PetWithFile.md) - [PropertyNameMapping](docs/Model/PropertyNameMapping.md) - [ReadOnlyFirst](docs/Model/ReadOnlyFirst.md) - [SingleRefType](docs/Model/SingleRefType.md) diff --git a/samples/client/petstore/php/OpenAPIClient-php/docs/Api/PetApi.md b/samples/client/petstore/php/OpenAPIClient-php/docs/Api/PetApi.md index b81cba25df60..7e0512051f17 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/docs/Api/PetApi.md +++ b/samples/client/petstore/php/OpenAPIClient-php/docs/Api/PetApi.md @@ -14,6 +14,7 @@ All URIs are relative to http://petstore.swagger.io:80/v2, except if the operati | [**uploadFile()**](PetApi.md#uploadFile) | **POST** /pet/{petId}/uploadImage | uploads an image | | [**uploadFileWithRequiredFile()**](PetApi.md#uploadFileWithRequiredFile) | **POST** /fake/{petId}/uploadImageWithRequiredFile | uploads an image (required) | | [**uploadImageFullFormData()**](PetApi.md#uploadImageFullFormData) | **POST** /pet/{petId}/uploadImageFullFormData | uploads an image attached to a Pet object as formdata | +| [**uploadImageFullFormDataNested()**](PetApi.md#uploadImageFullFormDataNested) | **POST** /pet/{petId}/uploadImageFullFormDataNested | uploads an image attached to a Pet object as formdata | ## `addPet()` @@ -689,3 +690,65 @@ try { [[Back to top]](#) [[Back to API list]](../../README.md#endpoints) [[Back to Model list]](../../README.md#models) [[Back to README]](../../README.md) + +## `uploadImageFullFormDataNested()` + +```php +uploadImageFullFormDataNested($pet_id, $pet): \OpenAPI\Client\Model\ApiResponse +``` + +uploads an image attached to a Pet object as formdata + + + +### Example + +```php +setAccessToken('YOUR_ACCESS_TOKEN'); + + +$apiInstance = new OpenAPI\Client\Api\PetApi( + // If you want use custom http client, pass your client which implements `GuzzleHttp\ClientInterface`. + // This is optional, `GuzzleHttp\Client` will be used as default. + new GuzzleHttp\Client(), + $config +); +$pet_id = 56; // int | ID of pet to update +$pet = new \OpenAPI\Client\Model\PetWithFile(); // \OpenAPI\Client\Model\PetWithFile + +try { + $result = $apiInstance->uploadImageFullFormDataNested($pet_id, $pet); + print_r($result); +} catch (Exception $e) { + echo 'Exception when calling PetApi->uploadImageFullFormDataNested: ', $e->getMessage(), PHP_EOL; +} +``` + +### Parameters + +| Name | Type | Description | Notes | +| ------------- | ------------- | ------------- | ------------- | +| **pet_id** | **int**| ID of pet to update | | +| **pet** | [**\OpenAPI\Client\Model\PetWithFile**](../Model/PetWithFile.md)| | [optional] | + +### Return type + +[**\OpenAPI\Client\Model\ApiResponse**](../Model/ApiResponse.md) + +### Authorization + +[petstore_auth](../../README.md#petstore_auth) + +### HTTP request headers + +- **Content-Type**: `multipart/form-data` +- **Accept**: `application/json` + +[[Back to top]](#) [[Back to API list]](../../README.md#endpoints) +[[Back to Model list]](../../README.md#models) +[[Back to README]](../../README.md) diff --git a/samples/client/petstore/php/OpenAPIClient-php/docs/Model/PetWithFile.md b/samples/client/petstore/php/OpenAPIClient-php/docs/Model/PetWithFile.md new file mode 100644 index 000000000000..520cd0b8725a --- /dev/null +++ b/samples/client/petstore/php/OpenAPIClient-php/docs/Model/PetWithFile.md @@ -0,0 +1,16 @@ +# # PetWithFile + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **int** | | [optional] +**category** | [**\OpenAPI\Client\Model\Category**](Category.md) | | [optional] +**name** | **string** | | +**photo_urls** | **string[]** | | +**tags** | [**\OpenAPI\Client\Model\Tag[]**](Tag.md) | | [optional] +**status** | **string** | pet status in the store | [optional] +**file** | **\SplFileObject** | file to upload | [optional] +**multiple_files** | **\SplFileObject[]** | | [optional] + +[[Back to Model list]](../../README.md#models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md) diff --git a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/AnotherFakeApi.php b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/AnotherFakeApi.php index 34f82fd71744..d50d661539e1 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/AnotherFakeApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/AnotherFakeApi.php @@ -38,6 +38,7 @@ use Psr\Http\Message\ResponseInterface; use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\HeaderSelector; use OpenAPI\Client\ObjectSerializer; diff --git a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/DefaultApi.php b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/DefaultApi.php index 00ff8ae25454..fe72d1716d6a 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/DefaultApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/DefaultApi.php @@ -38,6 +38,7 @@ use Psr\Http\Message\ResponseInterface; use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\HeaderSelector; use OpenAPI\Client\ObjectSerializer; diff --git a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeApi.php b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeApi.php index 762e9d7a1849..d4845564804e 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeApi.php @@ -38,6 +38,7 @@ use Psr\Http\Message\ResponseInterface; use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\HeaderSelector; use OpenAPI\Client\ObjectSerializer; @@ -5632,69 +5633,27 @@ public function testEndpointParametersRequest($number, $double, $pattern_without // form params - if ($integer !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('integer', $integer)); - } - // form params - if ($int32 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('int32', $int32)); - } - // form params - if ($int64 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('int64', $int64)); - } - // form params - if ($number !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('number', $number)); - } - // form params - if ($float !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('float', $float)); - } - // form params - if ($double !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('double', $double)); - } - // form params - if ($string !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('string', $string)); - } - // form params - if ($pattern_without_delimiter !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('pattern_without_delimiter', $pattern_without_delimiter)); - } - // form params - if ($byte !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('byte', $byte)); - } - // form params - if ($binary !== null) { - $multipart = true; - $formParams['binary'] = []; - $paramFiles = is_array($binary) ? $binary : [$binary]; - foreach ($paramFiles as $paramFile) { - $formParams['binary'][] = \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('binary', $paramFile)['binary'], - 'rb' - ); - } - } - // form params - if ($date !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('date', $date)); - } - // form params - if ($date_time !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('dateTime', $date_time)); - } - // form params - if ($password !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('password', $password)); - } - // form params - if ($callback !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('callback', $callback)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'integer' => $integer, + 'int32' => $int32, + 'int64' => $int64, + 'number' => $number, + 'float' => $float, + 'double' => $double, + 'string' => $string, + 'pattern_without_delimiter' => $pattern_without_delimiter, + 'byte' => $byte, + 'binary' => $binary, + 'date' => $date, + 'date_time' => $date_time, + 'password' => $password, + 'callback' => $callback, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], @@ -6008,13 +5967,15 @@ public function testEnumParametersRequest($enum_header_string_array = null, $enu // form params - if ($enum_form_string_array !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('enum_form_string_array', $enum_form_string_array)); - } - // form params - if ($enum_form_string !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('enum_form_string', $enum_form_string)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'enum_form_string_array' => $enum_form_string_array, + 'enum_form_string' => $enum_form_string, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], @@ -6990,13 +6951,15 @@ public function testJsonFormDataRequest($param, $param2, string $contentType = s // form params - if ($param !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('param', $param)); - } - // form params - if ($param2 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('param2', $param2)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'param' => $param, + 'param2' => $param2, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], diff --git a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeClassnameTags123Api.php b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeClassnameTags123Api.php index 3508d16ea37f..4c0cfd5c8c0a 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeClassnameTags123Api.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeClassnameTags123Api.php @@ -38,6 +38,7 @@ use Psr\Http\Message\ResponseInterface; use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\HeaderSelector; use OpenAPI\Client\ObjectSerializer; diff --git a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/PetApi.php b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/PetApi.php index 48b57f7f140d..61f60642dadf 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/PetApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/PetApi.php @@ -38,6 +38,7 @@ use Psr\Http\Message\ResponseInterface; use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\HeaderSelector; use OpenAPI\Client\ObjectSerializer; @@ -105,6 +106,9 @@ class PetApi 'uploadImageFullFormData' => [ 'multipart/form-data', ], + 'uploadImageFullFormDataNested' => [ + 'multipart/form-data', + ], ]; /** @@ -2112,13 +2116,15 @@ public function updatePetWithFormRequest($pet_id, $name = null, $status = null, } // form params - if ($name !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('name', $name)); - } - // form params - if ($status !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('status', $status)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'name' => $name, + 'status' => $status, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], @@ -2404,21 +2410,15 @@ public function uploadFileRequest($pet_id, $additional_metadata = null, $file = } // form params - if ($additional_metadata !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('additionalMetadata', $additional_metadata)); - } - // form params - if ($file !== null) { - $multipart = true; - $formParams['file'] = []; - $paramFiles = is_array($file) ? $file : [$file]; - foreach ($paramFiles as $paramFile) { - $formParams['file'][] = \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('file', $paramFile)['file'], - 'rb' - ); - } - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'additional_metadata' => $additional_metadata, + 'file' => $file, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $multipart = true; $headers = $this->headerSelector->selectHeaders( @@ -2711,21 +2711,15 @@ public function uploadFileWithRequiredFileRequest($pet_id, $required_file, $addi } // form params - if ($additional_metadata !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('additionalMetadata', $additional_metadata)); - } - // form params - if ($required_file !== null) { - $multipart = true; - $formParams['requiredFile'] = []; - $paramFiles = is_array($required_file) ? $required_file : [$required_file]; - foreach ($paramFiles as $paramFile) { - $formParams['requiredFile'][] = \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('requiredFile', $paramFile)['requiredFile'], - 'rb' - ); - } - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'additional_metadata' => $additional_metadata, + 'required_file' => $required_file, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $multipart = true; $headers = $this->headerSelector->selectHeaders( @@ -3060,54 +3054,310 @@ public function uploadImageFullFormDataRequest($pet_id, $name, $photo_urls, $id } // form params - if ($id !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('id', $id)); - } - // form params - if ($category !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('category', $category)); - } - // form params - if ($name !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('name', $name)); - } - // form params - if ($photo_urls !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('photoUrls', $photo_urls)); + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'id' => $id, + 'category' => $category, + 'name' => $name, + 'photo_urls' => $photo_urls, + 'tags' => $tags, + 'status' => $status, + 'file' => $file, + 'multiple_files' => $multiple_files, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; + + $multipart = true; + $headers = $this->headerSelector->selectHeaders( + ['application/json', ], + $contentType, + $multipart + ); + + // for model (json/xml) + if (count($formParams) > 0) { + if ($multipart) { + $multipartContents = []; + foreach ($formParams as $formParamName => $formParamValue) { + $formParamValueItems = is_array($formParamValue) ? $formParamValue : [$formParamValue]; + foreach ($formParamValueItems as $formParamValueItem) { + $multipartContents[] = [ + 'name' => $formParamName, + 'contents' => $formParamValueItem + ]; + } + } + // for HTTP post (form) + $httpBody = new MultipartStream($multipartContents); + + } elseif (stripos($headers['Content-Type'], 'application/json') !== false) { + # if Content-Type contains "application/json", json_encode the form parameters + $httpBody = \GuzzleHttp\Utils::jsonEncode($formParams); + } else { + // for HTTP post (form) + $httpBody = ObjectSerializer::buildQuery($formParams); + } } - // form params - if ($tags !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('tags', $tags)); + + // this endpoint requires OAuth (access token) + if (!empty($this->config->getAccessToken())) { + $headers['Authorization'] = 'Bearer ' . $this->config->getAccessToken(); } - // form params - if ($status !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('status', $status)); + + $defaultHeaders = []; + if ($this->config->getUserAgent()) { + $defaultHeaders['User-Agent'] = $this->config->getUserAgent(); } - // form params - if ($file !== null) { - $multipart = true; - $formParams['file'] = []; - $paramFiles = is_array($file) ? $file : [$file]; - foreach ($paramFiles as $paramFile) { - $formParams['file'][] = \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('file', $paramFile)['file'], - 'rb' + + $headers = array_merge( + $defaultHeaders, + $headerParams, + $headers + ); + + $operationHost = $this->config->getHost(); + $query = ObjectSerializer::buildQuery($queryParams); + return new Request( + 'POST', + $operationHost . $resourcePath . ($query ? "?{$query}" : ''), + $headers, + $httpBody + ); + } + + /** + * Operation uploadImageFullFormDataNested + * + * uploads an image attached to a Pet object as formdata + * + * @param int $pet_id ID of pet to update (required) + * @param \OpenAPI\Client\Model\PetWithFile|null $pet pet (optional) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['uploadImageFullFormDataNested'] to see the possible values for this operation + * + * @throws \OpenAPI\Client\ApiException on non-2xx response or if the response body is not in the expected format + * @throws \InvalidArgumentException + * @return \OpenAPI\Client\Model\ApiResponse + */ + public function uploadImageFullFormDataNested($pet_id, $pet = null, string $contentType = self::contentTypes['uploadImageFullFormDataNested'][0]) + { + list($response) = $this->uploadImageFullFormDataNestedWithHttpInfo($pet_id, $pet, $contentType); + return $response; + } + + /** + * Operation uploadImageFullFormDataNestedWithHttpInfo + * + * uploads an image attached to a Pet object as formdata + * + * @param int $pet_id ID of pet to update (required) + * @param \OpenAPI\Client\Model\PetWithFile|null $pet (optional) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['uploadImageFullFormDataNested'] to see the possible values for this operation + * + * @throws \OpenAPI\Client\ApiException on non-2xx response or if the response body is not in the expected format + * @throws \InvalidArgumentException + * @return array of \OpenAPI\Client\Model\ApiResponse, HTTP status code, HTTP response headers (array of strings) + */ + public function uploadImageFullFormDataNestedWithHttpInfo($pet_id, $pet = null, string $contentType = self::contentTypes['uploadImageFullFormDataNested'][0]) + { + $request = $this->uploadImageFullFormDataNestedRequest($pet_id, $pet, $contentType); + + try { + $options = $this->createHttpClientOption(); + try { + $response = $this->client->send($request, $options); + } catch (RequestException $e) { + throw new ApiException( + "[{$e->getCode()}] {$e->getMessage()}", + (int) $e->getCode(), + $e->getResponse() ? $e->getResponse()->getHeaders() : null, + $e->getResponse() ? (string) $e->getResponse()->getBody() : null + ); + } catch (ConnectException $e) { + throw new ApiException( + "[{$e->getCode()}] {$e->getMessage()}", + (int) $e->getCode(), + null, + null ); } - } - // form params - if ($multiple_files !== null) { - $multipart = true; - $formParams['multiple_files'] = []; - $paramFiles = is_array($multiple_files) ? $multiple_files : [$multiple_files]; - foreach ($paramFiles as $paramFile) { - $formParams['multiple_files'][] = \GuzzleHttp\Psr7\Utils::tryFopen( - ObjectSerializer::toFormValue('multiple_files', $paramFile)['multiple_files'], - 'rb' + + $statusCode = $response->getStatusCode(); + + + switch($statusCode) { + case 200: + return $this->handleResponseWithDataType( + '\OpenAPI\Client\Model\ApiResponse', + $request, + $response, + ); + } + + + + if ($statusCode < 200 || $statusCode > 299) { + throw new ApiException( + sprintf( + '[%d] Error connecting to the API (%s)', + $statusCode, + (string) $request->getUri() + ), + $statusCode, + $response->getHeaders(), + (string) $response->getBody() ); } + + return $this->handleResponseWithDataType( + '\OpenAPI\Client\Model\ApiResponse', + $request, + $response, + ); + } catch (ApiException $e) { + switch ($e->getCode()) { + case 200: + $data = ObjectSerializer::deserialize( + $e->getResponseBody(), + '\OpenAPI\Client\Model\ApiResponse', + $e->getResponseHeaders() + ); + $e->setResponseObject($data); + throw $e; + } + + + throw $e; + } + } + + /** + * Operation uploadImageFullFormDataNestedAsync + * + * uploads an image attached to a Pet object as formdata + * + * @param int $pet_id ID of pet to update (required) + * @param \OpenAPI\Client\Model\PetWithFile|null $pet (optional) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['uploadImageFullFormDataNested'] to see the possible values for this operation + * + * @throws \InvalidArgumentException + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public function uploadImageFullFormDataNestedAsync($pet_id, $pet = null, string $contentType = self::contentTypes['uploadImageFullFormDataNested'][0]) + { + return $this->uploadImageFullFormDataNestedAsyncWithHttpInfo($pet_id, $pet, $contentType) + ->then( + function ($response) { + return $response[0]; + } + ); + } + + /** + * Operation uploadImageFullFormDataNestedAsyncWithHttpInfo + * + * uploads an image attached to a Pet object as formdata + * + * @param int $pet_id ID of pet to update (required) + * @param \OpenAPI\Client\Model\PetWithFile|null $pet (optional) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['uploadImageFullFormDataNested'] to see the possible values for this operation + * + * @throws \InvalidArgumentException + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public function uploadImageFullFormDataNestedAsyncWithHttpInfo($pet_id, $pet = null, string $contentType = self::contentTypes['uploadImageFullFormDataNested'][0]) + { + $returnType = '\OpenAPI\Client\Model\ApiResponse'; + $request = $this->uploadImageFullFormDataNestedRequest($pet_id, $pet, $contentType); + + return $this->client + ->sendAsync($request, $this->createHttpClientOption()) + ->then( + function ($response) use ($returnType) { + if ($returnType === '\SplFileObject') { + $content = $response->getBody(); //stream goes to serializer + } else { + $content = (string) $response->getBody(); + if ($returnType !== 'string') { + $content = json_decode($content); + } + } + + return [ + ObjectSerializer::deserialize($content, $returnType, []), + $response->getStatusCode(), + $response->getHeaders() + ]; + }, + function ($exception) { + $response = $exception->getResponse(); + $statusCode = $response->getStatusCode(); + throw new ApiException( + sprintf( + '[%d] Error connecting to the API (%s)', + $statusCode, + $exception->getRequest()->getUri() + ), + $statusCode, + $response->getHeaders(), + (string) $response->getBody() + ); + } + ); + } + + /** + * Create request for operation 'uploadImageFullFormDataNested' + * + * @param int $pet_id ID of pet to update (required) + * @param \OpenAPI\Client\Model\PetWithFile|null $pet (optional) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['uploadImageFullFormDataNested'] to see the possible values for this operation + * + * @throws \InvalidArgumentException + * @return \GuzzleHttp\Psr7\Request + */ + public function uploadImageFullFormDataNestedRequest($pet_id, $pet = null, string $contentType = self::contentTypes['uploadImageFullFormDataNested'][0]) + { + + // verify the required parameter 'pet_id' is set + if ($pet_id === null || (is_array($pet_id) && count($pet_id) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $pet_id when calling uploadImageFullFormDataNested' + ); } + + + $resourcePath = '/pet/{petId}/uploadImageFullFormDataNested'; + $formParams = []; + $queryParams = []; + $headerParams = []; + $httpBody = ''; + $multipart = false; + + + + // path params + if ($pet_id !== null) { + $resourcePath = str_replace( + '{' . 'petId' . '}', + ObjectSerializer::toPathValue($pet_id), + $resourcePath + ); + } + + // form params + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'pet' => $pet, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; + $multipart = true; $headers = $this->headerSelector->selectHeaders( ['application/json', ], diff --git a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/StoreApi.php b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/StoreApi.php index bfbcfcd2e119..f04b8a45f9fa 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/StoreApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/StoreApi.php @@ -38,6 +38,7 @@ use Psr\Http\Message\ResponseInterface; use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\HeaderSelector; use OpenAPI\Client\ObjectSerializer; diff --git a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/UserApi.php b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/UserApi.php index 2fd2e9423493..1d2b64ec9bae 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/UserApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/UserApi.php @@ -38,6 +38,7 @@ use Psr\Http\Message\ResponseInterface; use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\HeaderSelector; use OpenAPI\Client\ObjectSerializer; diff --git a/samples/client/petstore/php/OpenAPIClient-php/lib/FormDataProcessor.php b/samples/client/petstore/php/OpenAPIClient-php/lib/FormDataProcessor.php new file mode 100644 index 000000000000..c416f584e6aa --- /dev/null +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/FormDataProcessor.php @@ -0,0 +1,242 @@ + $values the value of the form parameter + * + * @return array [key => value] of formdata + */ + public function prepare(array $values): array + { + $this->has_file = false; + $result = []; + + foreach ($values as $k => $v) { + if ($v === null) { + continue; + } + + $result[$k] = $this->makeFormSafe($v); + } + + return $result; + } + + /** + * Flattens a multi-level array of data and generates a single-level array + * compatible with formdata - a single-level array where the keys use bracket + * notation to signify nested data. + * + * credit: https://github.com/FranBar1966/FlatPHP + */ + public static function flatten(array $source, string $start = ''): array + { + $opt = [ + 'prefix' => '[', + 'suffix' => ']', + 'suffix-end' => true, + 'prefix-list' => '[', + 'suffix-list' => ']', + 'suffix-list-end' => true, + ]; + + if ($start === '') { + $currentPrefix = ''; + $currentSuffix = ''; + $currentSuffixEnd = false; + } elseif (array_is_list($source)) { + $currentPrefix = $opt['prefix-list']; + $currentSuffix = $opt['suffix-list']; + $currentSuffixEnd = $opt['suffix-list-end']; + } else { + $currentPrefix = $opt['prefix']; + $currentSuffix = $opt['suffix']; + $currentSuffixEnd = $opt['suffix-end']; + } + + $currentName = $start; + $result = []; + + foreach ($source as $key => $val) { + $currentName .= $currentPrefix.$key; + + if (is_array($val) && !empty($val)) { + $currentName .= $currentSuffix; + $result += self::flatten($val, $currentName); + } else { + if ($currentSuffixEnd) { + $currentName .= $currentSuffix; + } + + $result[$currentName] = ObjectSerializer::toString($val); + } + + $currentName = $start; + } + + return $result; + } + + /** + * formdata must be limited to scalars or arrays of scalar values, + * or a resource for a file upload. Here we iterate through all available + * data and identify how to handle each scenario + */ + protected function makeFormSafe($value) + { + if ($value instanceof SplFileObject) { + return $this->processFiles([$value])[0]; + } + + if (is_resource($value)) { + $this->has_file = true; + + return $value; + } + + if ($value instanceof ModelInterface) { + return $this->processModel($value); + } + + if (is_array($value) || (is_object($value) && !$value instanceof \DateTimeInterface)) { + $data = []; + + foreach ($value as $k => $v) { + $data[$k] = $this->makeFormSafe($v); + } + + return $data; + } + + return ObjectSerializer::toString($value); + } + + /** + * We are able to handle nested ModelInterface. We do not simply call + * json_decode(json_encode()) because any given model may have binary data + * or other data that cannot be serialized to a JSON string + */ + protected function processModel(ModelInterface $model): array + { + $result = []; + + foreach ($model::openAPITypes() as $name => $type) { + $value = $model->offsetGet($name); + + if ($value === null) { + continue; + } + + if (strpos($type, '\SplFileObject') !== false) { + $file = is_array($value) ? $value : [$value]; + $result[$name] = $this->processFiles($file); + + continue; + } + + if ($value instanceof ModelInterface) { + $result[$name] = $this->processModel($value); + + continue; + } + + if (is_array($value) || is_object($value)) { + $result[$name] = $this->makeFormSafe($value); + + continue; + } + + $result[$name] = ObjectSerializer::toString($value); + } + + return $result; + } + + /** + * Handle file data + */ + protected function processFiles(array $files): array + { + $this->has_file = true; + + $result = []; + + foreach ($files as $i => $file) { + if (is_array($file)) { + $result[$i] = $this->processFiles($file); + + continue; + } + + if ($file instanceof StreamInterface) { + $result[$i] = $file; + + continue; + } + + if ($file instanceof SplFileObject) { + $result[$i] = $this->tryFopen($file); + } + } + + return $result; + } + + private function tryFopen(SplFileObject $file) + { + return Utils::tryFopen($file->getRealPath(), 'rb'); + } +} diff --git a/samples/client/petstore/php/OpenAPIClient-php/lib/Model/PetWithFile.php b/samples/client/petstore/php/OpenAPIClient-php/lib/Model/PetWithFile.php new file mode 100644 index 000000000000..3e91dabb4b55 --- /dev/null +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Model/PetWithFile.php @@ -0,0 +1,691 @@ + + */ +class PetWithFile implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'PetWithFile'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'id' => 'int', + 'category' => '\OpenAPI\Client\Model\Category', + 'name' => 'string', + 'photo_urls' => 'string[]', + 'tags' => '\OpenAPI\Client\Model\Tag[]', + 'status' => 'string', + 'file' => '\SplFileObject', + 'multiple_files' => '\SplFileObject[]' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'id' => 'int64', + 'category' => null, + 'name' => null, + 'photo_urls' => null, + 'tags' => null, + 'status' => null, + 'file' => 'binary', + 'multiple_files' => 'binary' + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'id' => false, + 'category' => false, + 'name' => false, + 'photo_urls' => false, + 'tags' => false, + 'status' => false, + 'file' => false, + 'multiple_files' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'id' => 'id', + 'category' => 'category', + 'name' => 'name', + 'photo_urls' => 'photoUrls', + 'tags' => 'tags', + 'status' => 'status', + 'file' => 'file', + 'multiple_files' => 'multiple_files' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'id' => 'setId', + 'category' => 'setCategory', + 'name' => 'setName', + 'photo_urls' => 'setPhotoUrls', + 'tags' => 'setTags', + 'status' => 'setStatus', + 'file' => 'setFile', + 'multiple_files' => 'setMultipleFiles' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'id' => 'getId', + 'category' => 'getCategory', + 'name' => 'getName', + 'photo_urls' => 'getPhotoUrls', + 'tags' => 'getTags', + 'status' => 'getStatus', + 'file' => 'getFile', + 'multiple_files' => 'getMultipleFiles' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + public const STATUS_AVAILABLE = 'available'; + public const STATUS_PENDING = 'pending'; + public const STATUS_SOLD = 'sold'; + + /** + * Gets allowable values of the enum + * + * @return string[] + */ + public function getStatusAllowableValues() + { + return [ + self::STATUS_AVAILABLE, + self::STATUS_PENDING, + self::STATUS_SOLD, + ]; + } + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('id', $data ?? [], null); + $this->setIfExists('category', $data ?? [], null); + $this->setIfExists('name', $data ?? [], null); + $this->setIfExists('photo_urls', $data ?? [], null); + $this->setIfExists('tags', $data ?? [], null); + $this->setIfExists('status', $data ?? [], null); + $this->setIfExists('file', $data ?? [], null); + $this->setIfExists('multiple_files', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['name'] === null) { + $invalidProperties[] = "'name' can't be null"; + } + if ($this->container['photo_urls'] === null) { + $invalidProperties[] = "'photo_urls' can't be null"; + } + $allowedValues = $this->getStatusAllowableValues(); + if (!is_null($this->container['status']) && !in_array($this->container['status'], $allowedValues, true)) { + $invalidProperties[] = sprintf( + "invalid value '%s' for 'status', must be one of '%s'", + $this->container['status'], + implode("', '", $allowedValues) + ); + } + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets id + * + * @return int|null + */ + public function getId() + { + return $this->container['id']; + } + + /** + * Sets id + * + * @param int|null $id id + * + * @return self + */ + public function setId($id) + { + if (is_null($id)) { + throw new \InvalidArgumentException('non-nullable id cannot be null'); + } + $this->container['id'] = $id; + + return $this; + } + + /** + * Gets category + * + * @return \OpenAPI\Client\Model\Category|null + */ + public function getCategory() + { + return $this->container['category']; + } + + /** + * Sets category + * + * @param \OpenAPI\Client\Model\Category|null $category category + * + * @return self + */ + public function setCategory($category) + { + if (is_null($category)) { + throw new \InvalidArgumentException('non-nullable category cannot be null'); + } + $this->container['category'] = $category; + + return $this; + } + + /** + * Gets name + * + * @return string + */ + public function getName() + { + return $this->container['name']; + } + + /** + * Sets name + * + * @param string $name name + * + * @return self + */ + public function setName($name) + { + if (is_null($name)) { + throw new \InvalidArgumentException('non-nullable name cannot be null'); + } + $this->container['name'] = $name; + + return $this; + } + + /** + * Gets photo_urls + * + * @return string[] + */ + public function getPhotoUrls() + { + return $this->container['photo_urls']; + } + + /** + * Sets photo_urls + * + * @param string[] $photo_urls photo_urls + * + * @return self + */ + public function setPhotoUrls($photo_urls) + { + if (is_null($photo_urls)) { + throw new \InvalidArgumentException('non-nullable photo_urls cannot be null'); + } + + + $this->container['photo_urls'] = $photo_urls; + + return $this; + } + + /** + * Gets tags + * + * @return \OpenAPI\Client\Model\Tag[]|null + */ + public function getTags() + { + return $this->container['tags']; + } + + /** + * Sets tags + * + * @param \OpenAPI\Client\Model\Tag[]|null $tags tags + * + * @return self + */ + public function setTags($tags) + { + if (is_null($tags)) { + throw new \InvalidArgumentException('non-nullable tags cannot be null'); + } + $this->container['tags'] = $tags; + + return $this; + } + + /** + * Gets status + * + * @return string|null + */ + public function getStatus() + { + return $this->container['status']; + } + + /** + * Sets status + * + * @param string|null $status pet status in the store + * + * @return self + */ + public function setStatus($status) + { + if (is_null($status)) { + throw new \InvalidArgumentException('non-nullable status cannot be null'); + } + $allowedValues = $this->getStatusAllowableValues(); + if (!in_array($status, $allowedValues, true)) { + throw new \InvalidArgumentException( + sprintf( + "Invalid value '%s' for 'status', must be one of '%s'", + $status, + implode("', '", $allowedValues) + ) + ); + } + $this->container['status'] = $status; + + return $this; + } + + /** + * Gets file + * + * @return \SplFileObject|null + */ + public function getFile() + { + return $this->container['file']; + } + + /** + * Sets file + * + * @param \SplFileObject|null $file file to upload + * + * @return self + */ + public function setFile($file) + { + if (is_null($file)) { + throw new \InvalidArgumentException('non-nullable file cannot be null'); + } + $this->container['file'] = $file; + + return $this; + } + + /** + * Gets multiple_files + * + * @return \SplFileObject[]|null + */ + public function getMultipleFiles() + { + return $this->container['multiple_files']; + } + + /** + * Sets multiple_files + * + * @param \SplFileObject[]|null $multiple_files multiple_files + * + * @return self + */ + public function setMultipleFiles($multiple_files) + { + if (is_null($multiple_files)) { + throw new \InvalidArgumentException('non-nullable multiple_files cannot be null'); + } + $this->container['multiple_files'] = $multiple_files; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer $offset Offset + * + * @return boolean + */ + public function offsetExists($offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer $offset Offset + * + * @return void + */ + public function offsetUnset($offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/samples/client/petstore/php/OpenAPIClient-php/lib/ObjectSerializer.php b/samples/client/petstore/php/OpenAPIClient-php/lib/ObjectSerializer.php index a144eb7641b1..daa7d49244ce 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/ObjectSerializer.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/ObjectSerializer.php @@ -28,7 +28,6 @@ namespace OpenAPI\Client; -use ArrayAccess; use GuzzleHttp\Psr7\Utils; use OpenAPI\Client\Model\ModelInterface; @@ -324,35 +323,6 @@ public static function toHeaderValue($value) return self::toString($value); } - /** - * Take value and turn it into an array suitable for inclusion in - * the http body (form parameter). If it's a string, pass through unchanged - * If it's a datetime object, format it in ISO8601 - * - * @param string|bool|array|DateTime|ArrayAccess|\SplFileObject $value the value of the form parameter - * - * @return array [key => value] of formdata - */ - public static function toFormValue(string $key, $value) - { - if ($value instanceof \SplFileObject) { - return [$key => $value->getRealPath()]; - } elseif (is_array($value) || $value instanceof ArrayAccess) { - $flattened = []; - $result = []; - - self::flattenArray(json_decode(json_encode($value), true), $flattened); - - foreach ($flattened as $k => $v) { - $result["{$key}{$k}"] = self::toString($v); - } - - return $result; - } else { - return [$key => self::toString($value)]; - } - } - /** * Take value and turn it into a string suitable for inclusion in * the parameter. If it's a string, pass through unchanged @@ -626,81 +596,4 @@ public static function buildQuery(array $params, $encoding = PHP_QUERY_RFC3986): return $qs ? (string) substr($qs, 0, -1) : ''; } - - /** - * Flattens an array of Model object and generates an array compatible - * with formdata - a single-level array where the keys use bracket - * notation to signify nested data. - * - * @param \ArrayAccess|array $source - * - * credit: https://github.com/FranBar1966/FlatPHP - */ - private static function flattenArray( - $source, - array &$destination, - string $start = '' - ) { - $opt = [ - 'prefix' => '[', - 'suffix' => ']', - 'suffix-end' => true, - 'prefix-list' => '[', - 'suffix-list' => ']', - 'suffix-list-end' => true, - ]; - - if (!is_array($source)) { - $source = (array) $source; - } - - /** - * array_is_list only in PHP >= 8.1 - * - * credit: https://www.php.net/manual/en/function.array-is-list.php#127044 - */ - if (!function_exists('array_is_list')) { - function array_is_list(array $array) - { - $i = -1; - - foreach ($array as $k => $v) { - ++$i; - if ($k !== $i) { - return false; - } - } - - return true; - } - } - - if (array_is_list($source)) { - $currentPrefix = $opt['prefix-list']; - $currentSuffix = $opt['suffix-list']; - $currentSuffixEnd = $opt['suffix-list-end']; - } else { - $currentPrefix = $opt['prefix']; - $currentSuffix = $opt['suffix']; - $currentSuffixEnd = $opt['suffix-end']; - } - - $currentName = $start; - - foreach ($source as $key => $val) { - $currentName .= $currentPrefix.$key; - - if (is_array($val) && !empty($val)) { - $currentName .= "{$currentSuffix}"; - self::flattenArray($val, $destination, $currentName); - } else { - if ($currentSuffixEnd) { - $currentName .= $currentSuffix; - } - $destination[$currentName] = self::toString($val); - } - - $currentName = $start; - } - } } diff --git a/samples/client/petstore/php/OpenAPIClient-php/test/Model/PetWithFileTest.php b/samples/client/petstore/php/OpenAPIClient-php/test/Model/PetWithFileTest.php new file mode 100644 index 000000000000..ce0a73f294d0 --- /dev/null +++ b/samples/client/petstore/php/OpenAPIClient-php/test/Model/PetWithFileTest.php @@ -0,0 +1,153 @@ +prepare($data); + + $result = $formDataProcessor::flatten($formData); + + $this->assertEquals($expected, $result); + } + + public function providerFlatten(): iterable + { + $data = [ + 'id' => '1234', + 'name' => 'Spike', + 'photo_urls' => [ + 'https://example.com/picture_1.jpg', + 'https://example.com/picture_2.jpg', + ], + 'status' => Model\Pet::STATUS_AVAILABLE, + 'category' => [ + 'id' => '12345', + 'name' => 'Category_Name', + ], + 'tags' => [ + [ + 'id' => '12345', + 'name' => 'tag_1', + ], + [ + 'id' => '98765', + 'name' => 'tag_2', + ], + ], + ]; + + yield [ + 'data' => $data, + 'expected' => [ + 'id' => $data['id'], + 'name' => $data['name'], + 'photo_urls[0]' => $data['photo_urls'][0], + 'photo_urls[1]' => $data['photo_urls'][1], + 'status' => $data['status'], + 'category[id]' => (string) $data['category']['id'], + 'category[name]' => $data['category']['name'], + 'tags[0][id]' => (string) $data['tags'][0]['id'], + 'tags[0][name]' => $data['tags'][0]['name'], + 'tags[1][id]' => (string) $data['tags'][1]['id'], + 'tags[1][name]' => $data['tags'][1]['name'], + ], + ]; + + $category = (new Model\Category()) + ->setId($data['category']['id']) + ->setName($data['category']['name']); + + $tags_1 = (new Model\Tag()) + ->setId($data['tags'][0]['id']) + ->setName($data['tags'][0]['name']); + + $tags_2 = (new Model\Tag()) + ->setId($data['tags'][1]['id']) + ->setName($data['tags'][1]['name']); + + $tags = [ + $tags_1, + $tags_2, + ]; + + $pet = new Model\Pet([]); + $pet->setId($data['id']) + ->setName($data['name']) + ->setPhotoUrls($data['photo_urls']) + ->setStatus($data['status']) + ->setCategory($category) + ->setTags($tags); + + yield [ + 'data' => ['pet' => $pet], + 'expected' => [ + 'pet[id]' => $data['id'], + 'pet[name]' => $data['name'], + 'pet[photo_urls][0]' => $data['photo_urls'][0], + 'pet[photo_urls][1]' => $data['photo_urls'][1], + 'pet[status]' => $data['status'], + 'pet[category][id]' => (string) $data['category']['id'], + 'pet[category][name]' => $data['category']['name'], + 'pet[tags][0][id]' => (string) $data['tags'][0]['id'], + 'pet[tags][0][name]' => $data['tags'][0]['name'], + 'pet[tags][1][id]' => (string) $data['tags'][1]['id'], + 'pet[tags][1][name]' => $data['tags'][1]['name'], + ], + ]; + + yield [ + 'data' => ['nested' => ['pet' => $pet]], + 'expected' => [ + 'nested[pet][id]' => $data['id'], + 'nested[pet][name]' => $data['name'], + 'nested[pet][photo_urls][0]' => $data['photo_urls'][0], + 'nested[pet][photo_urls][1]' => $data['photo_urls'][1], + 'nested[pet][status]' => $data['status'], + 'nested[pet][category][id]' => (string) $data['category']['id'], + 'nested[pet][category][name]' => $data['category']['name'], + 'nested[pet][tags][0][id]' => (string) $data['tags'][0]['id'], + 'nested[pet][tags][0][name]' => $data['tags'][0]['name'], + 'nested[pet][tags][1][id]' => (string) $data['tags'][1]['id'], + 'nested[pet][tags][1][name]' => $data['tags'][1]['name'], + ], + ]; + + yield [ + 'data' => ['key' => new DateTime('2021-10-06T20:17:16')], + 'expected' => ['key' => '2021-10-06T20:17:16+00:00'], + ]; + + yield [ + 'data' => ['key' => true], + 'expected' => ['key' => 'true'], + ]; + + yield [ + 'data' => ['key' => false], + 'expected' => ['key' => 'false'], + ]; + + yield [ + 'data' => ['key' => 'some value'], + 'expected' => ['key' => 'some value'], + ]; + } + + public function testNullValueIgnored(): void + { + $data = [ + 'id' => '1234', + 'name' => 'Spike', + 'photo_urls' => null, + 'status' => null, + 'category' => null, + 'tags' => null, + ]; + + $expected = [ + 'id' => $data['id'], + 'name' => $data['name'], + ]; + + $formDataProcessor = new FormDataProcessor(); + $formData = $formDataProcessor->prepare($data); + + $result = $formDataProcessor::flatten($formData); + + $this->assertEquals($expected, $result); + } + + public function testHasFile(): void + { + $filepath = realpath(__DIR__ . '/../.openapi-generator/VERSION'); + $file = new SplFileObject($filepath); + + $pet = new Model\PetWithFile([]); + $pet->setId(123) + ->setName('Spike') + ->setFile($file); + + $formDataProcessor = new FormDataProcessor(); + + $this->assertFalse($formDataProcessor->has_file); + $formData = $formDataProcessor->prepare(['pet' => $pet]); + + $this->assertIsResource($formData['pet']['file'][0]); + $this->assertTrue($formDataProcessor->has_file); + } + + public function testHasFileNested(): void + { + $filepath = realpath(__DIR__ . '/../.openapi-generator/VERSION'); + $file = new SplFileObject($filepath); + + $pet = new Model\PetWithFile([]); + $pet->setId(123) + ->setName('Spike') + ->setFile($file); + + $formDataProcessor = new FormDataProcessor(); + + $this->assertFalse($formDataProcessor->has_file); + $formData = $formDataProcessor->prepare(['nested' => ['pet' => $pet]]); + + $this->assertIsResource($formData['nested']['pet']['file'][0]); + $this->assertTrue($formDataProcessor->has_file); + } + + public function testHasFileMultiple(): void + { + $filepath = realpath(__DIR__ . '/../.openapi-generator/VERSION'); + $file = new SplFileObject($filepath); + + $pet = new Model\PetWithFile([]); + $pet->setId(123) + ->setName('Spike') + ->setMultipleFiles([$file]); + + $formDataProcessor = new FormDataProcessor(); + + $this->assertFalse($formDataProcessor->has_file); + $formData = $formDataProcessor->prepare(['pet' => $pet]); + + $this->assertIsResource($formData['pet']['multiple_files'][0]); + $this->assertTrue($formDataProcessor->has_file); + } + + public function testHasFileMultipleNested(): void + { + $filepath = realpath(__DIR__ . '/../.openapi-generator/VERSION'); + $file = new SplFileObject($filepath); + + $pet = new Model\PetWithFile([]); + $pet->setId(123) + ->setName('Spike') + ->setMultipleFiles([$file]); + + $formDataProcessor = new FormDataProcessor(); + + $this->assertFalse($formDataProcessor->has_file); + $formData = $formDataProcessor->prepare(['nested' => ['pet' => $pet]]); + + $this->assertIsResource($formData['nested']['pet']['multiple_files'][0]); + $this->assertTrue($formDataProcessor->has_file); + } +} diff --git a/samples/client/petstore/php/OpenAPIClient-php/tests/ObjectSerializerTest.php b/samples/client/petstore/php/OpenAPIClient-php/tests/ObjectSerializerTest.php index 47b94f12f4fa..dc71cd108b85 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/tests/ObjectSerializerTest.php +++ b/samples/client/petstore/php/OpenAPIClient-php/tests/ObjectSerializerTest.php @@ -636,98 +636,4 @@ public function testArrayGivenAsObjectForDeserialize(): void $tag = $tags[0]; $this->assertInstanceOf(Tag::class, $tag); } - - /** - * @dataProvider providerToFormValue - */ - public function testToFormValue( - mixed $data, - mixed $expected, - ): void { - $result = ObjectSerializer::toFormValue('key', $data); - - $this->assertEquals($expected, $result); - } - - public function providerToFormValue(): iterable - { - yield [ - 'data' => new DateTime('2021-10-06T20:17:16'), - 'expected' => ['key' => '2021-10-06T20:17:16+00:00'], - ]; - - yield [ - 'data' => true, - 'expected' => ['key' => 'true'], - ]; - - yield [ - 'data' => false, - 'expected' => ['key' => 'false'], - ]; - - yield [ - 'data' => 'some value', - 'expected' => ['key' => 'some value'], - ]; - - $filepath = realpath(__DIR__ . '/../.openapi-generator/VERSION'); - $file = new \SplFileObject($filepath); - - yield [ - 'data' => $file, - 'expected' => ['key' => $filepath], - ]; - - $id = 1234; - $name = 'Spike'; - - $category = (new Model\Category()) - ->setId(12345) - ->setName("Category_Name"); - - $tags_1 = (new Model\Tag()) - ->setId(12345) - ->setName("tag_1"); - - $tags_2 = (new Model\Tag()) - ->setId(98765) - ->setName("tag_2"); - - $tags = [ - $tags_1, - $tags_2, - ]; - - $photo_urls = [ - "https://example.com/picture_1.jpg", - "https://example.com/picture_2.jpg", - ]; - $status = Model\Pet::STATUS_AVAILABLE; - - $pet = new Model\Pet([]); - $pet->setId($id) - ->setName($name) - ->setPhotoUrls($photo_urls) - ->setStatus($status) - ->setCategory($category) - ->setTags($tags); - - yield [ - 'data' => $pet, - 'expected' => [ - 'key[id]' => "{$id}", - 'key[name]' => $name, - 'key[photoUrls][0]' => $photo_urls[0], - 'key[photoUrls][1]' => $photo_urls[1], - 'key[status]' => $status, - 'key[category][id]' => "{$category->getId()}", - 'key[category][name]' => $category->getName(), - 'key[tags][0][id]' => "{$tags_1->getId()}", - 'key[tags][0][name]' => $tags_1->getName(), - 'key[tags][1][id]' => "{$tags_2->getId()}", - 'key[tags][1][name]' => $tags_2->getName(), - ], - ]; - } } diff --git a/samples/client/petstore/php/OpenAPIClient-php/tests/PetApiTest.php b/samples/client/petstore/php/OpenAPIClient-php/tests/PetApiTest.php index 463f36a78af0..bd1c2fb2a8a9 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/tests/PetApiTest.php +++ b/samples/client/petstore/php/OpenAPIClient-php/tests/PetApiTest.php @@ -417,8 +417,8 @@ public function testObjectInFormData() $contents = $request->getBody()->getContents(); $this->assertBodyContents('name', $name, $contents); - $this->assertBodyContents('photoUrls[0]', $photo_urls[0], $contents); - $this->assertBodyContents('photoUrls[1]', $photo_urls[1], $contents); + $this->assertBodyContents('photo_urls[0]', $photo_urls[0], $contents); + $this->assertBodyContents('photo_urls[1]', $photo_urls[1], $contents); $this->assertBodyContents('category[id]', $category->getId(), $contents); $this->assertBodyContents('category[name]', $category->getName(), $contents); $this->assertBodyContents('tags[0][id]', $tags[0]->getId(), $contents); diff --git a/samples/client/petstore/php/psr-18/.openapi-generator/FILES b/samples/client/petstore/php/psr-18/.openapi-generator/FILES index 37dca3df0af8..d1d6f3c8d55d 100644 --- a/samples/client/petstore/php/psr-18/.openapi-generator/FILES +++ b/samples/client/petstore/php/psr-18/.openapi-generator/FILES @@ -54,6 +54,7 @@ docs/Model/OuterEnumInteger.md docs/Model/OuterEnumIntegerDefaultValue.md docs/Model/OuterObjectWithEnumProperty.md docs/Model/Pet.md +docs/Model/PetWithFile.md docs/Model/PropertyNameMapping.md docs/Model/ReadOnlyFirst.md docs/Model/SingleRefType.md @@ -72,6 +73,7 @@ lib/Api/UserApi.php lib/ApiException.php lib/Configuration.php lib/DebugPlugin.php +lib/FormDataProcessor.php lib/HeaderSelector.php lib/Model/AdditionalPropertiesClass.php lib/Model/AllOfWithSingleRef.php @@ -118,6 +120,7 @@ lib/Model/OuterEnumInteger.php lib/Model/OuterEnumIntegerDefaultValue.php lib/Model/OuterObjectWithEnumProperty.php lib/Model/Pet.php +lib/Model/PetWithFile.php lib/Model/PropertyNameMapping.php lib/Model/ReadOnlyFirst.php lib/Model/SingleRefType.php diff --git a/samples/client/petstore/php/psr-18/README.md b/samples/client/petstore/php/psr-18/README.md index c77a0d15c041..813223f24e11 100644 --- a/samples/client/petstore/php/psr-18/README.md +++ b/samples/client/petstore/php/psr-18/README.md @@ -124,6 +124,7 @@ Class | Method | HTTP request | Description *PetApi* | [**uploadFile**](docs/Api/PetApi.md#uploadfile) | **POST** /pet/{petId}/uploadImage | uploads an image *PetApi* | [**uploadFileWithRequiredFile**](docs/Api/PetApi.md#uploadfilewithrequiredfile) | **POST** /fake/{petId}/uploadImageWithRequiredFile | uploads an image (required) *PetApi* | [**uploadImageFullFormData**](docs/Api/PetApi.md#uploadimagefullformdata) | **POST** /pet/{petId}/uploadImageFullFormData | uploads an image attached to a Pet object as formdata +*PetApi* | [**uploadImageFullFormDataNested**](docs/Api/PetApi.md#uploadimagefullformdatanested) | **POST** /pet/{petId}/uploadImageFullFormDataNested | uploads an image attached to a Pet object as formdata *StoreApi* | [**deleteOrder**](docs/Api/StoreApi.md#deleteorder) | **DELETE** /store/order/{order_id} | Delete purchase order by ID *StoreApi* | [**getInventory**](docs/Api/StoreApi.md#getinventory) | **GET** /store/inventory | Returns pet inventories by status *StoreApi* | [**getOrderById**](docs/Api/StoreApi.md#getorderbyid) | **GET** /store/order/{order_id} | Find purchase order by ID @@ -183,6 +184,7 @@ Class | Method | HTTP request | Description - [OuterEnumIntegerDefaultValue](docs/Model/OuterEnumIntegerDefaultValue.md) - [OuterObjectWithEnumProperty](docs/Model/OuterObjectWithEnumProperty.md) - [Pet](docs/Model/Pet.md) +- [PetWithFile](docs/Model/PetWithFile.md) - [PropertyNameMapping](docs/Model/PropertyNameMapping.md) - [ReadOnlyFirst](docs/Model/ReadOnlyFirst.md) - [SingleRefType](docs/Model/SingleRefType.md) diff --git a/samples/client/petstore/php/psr-18/docs/Api/PetApi.md b/samples/client/petstore/php/psr-18/docs/Api/PetApi.md index 1b5a4c942fd5..c893be3b0e0b 100644 --- a/samples/client/petstore/php/psr-18/docs/Api/PetApi.md +++ b/samples/client/petstore/php/psr-18/docs/Api/PetApi.md @@ -14,6 +14,7 @@ Method | HTTP request | Description [**uploadFile()**](PetApi.md#uploadFile) | **POST** /pet/{petId}/uploadImage | uploads an image [**uploadFileWithRequiredFile()**](PetApi.md#uploadFileWithRequiredFile) | **POST** /fake/{petId}/uploadImageWithRequiredFile | uploads an image (required) [**uploadImageFullFormData()**](PetApi.md#uploadImageFullFormData) | **POST** /pet/{petId}/uploadImageFullFormData | uploads an image attached to a Pet object as formdata +[**uploadImageFullFormDataNested()**](PetApi.md#uploadImageFullFormDataNested) | **POST** /pet/{petId}/uploadImageFullFormDataNested | uploads an image attached to a Pet object as formdata ## `addPet()` @@ -643,3 +644,65 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../../README.md#endpoints) [[Back to Model list]](../../README.md#models) [[Back to README]](../../README.md) + +## `uploadImageFullFormDataNested()` + +```php +uploadImageFullFormDataNested($pet_id, $pet): \OpenAPI\Client\Model\ApiResponse +``` + +uploads an image attached to a Pet object as formdata + + + +### Example + +```php +setAccessToken('YOUR_ACCESS_TOKEN'); + + +$apiInstance = new OpenAPI\Client\Api\PetApi( + // If you want use custom http client, pass your client which implements `Psr\Http\Client\ClientInterface`. + // This is optional, `Psr18ClientDiscovery` will be used to find http client. For instance `GuzzleHttp\Client` implements that interface + new GuzzleHttp\Client(), + $config +); +$pet_id = 56; // int | ID of pet to update +$pet = new \OpenAPI\Client\Model\PetWithFile(); // \OpenAPI\Client\Model\PetWithFile + +try { + $result = $apiInstance->uploadImageFullFormDataNested($pet_id, $pet); + print_r($result); +} catch (Exception $e) { + echo 'Exception when calling PetApi->uploadImageFullFormDataNested: ', $e->getMessage(), PHP_EOL; +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **pet_id** | **int**| ID of pet to update | + **pet** | [**\OpenAPI\Client\Model\PetWithFile**](../Model/PetWithFile.md)| | [optional] + +### Return type + +[**\OpenAPI\Client\Model\ApiResponse**](../Model/ApiResponse.md) + +### Authorization + +[petstore_auth](../../README.md#petstore_auth) + +### HTTP request headers + +- **Content-Type**: `multipart/form-data` +- **Accept**: `application/json` + +[[Back to top]](#) [[Back to API list]](../../README.md#endpoints) +[[Back to Model list]](../../README.md#models) +[[Back to README]](../../README.md) diff --git a/samples/client/petstore/php/psr-18/docs/Model/PetWithFile.md b/samples/client/petstore/php/psr-18/docs/Model/PetWithFile.md new file mode 100644 index 000000000000..520cd0b8725a --- /dev/null +++ b/samples/client/petstore/php/psr-18/docs/Model/PetWithFile.md @@ -0,0 +1,16 @@ +# # PetWithFile + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **int** | | [optional] +**category** | [**\OpenAPI\Client\Model\Category**](Category.md) | | [optional] +**name** | **string** | | +**photo_urls** | **string[]** | | +**tags** | [**\OpenAPI\Client\Model\Tag[]**](Tag.md) | | [optional] +**status** | **string** | pet status in the store | [optional] +**file** | **\SplFileObject** | file to upload | [optional] +**multiple_files** | **\SplFileObject[]** | | [optional] + +[[Back to Model list]](../../README.md#models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md) diff --git a/samples/client/petstore/php/psr-18/lib/Api/AnotherFakeApi.php b/samples/client/petstore/php/psr-18/lib/Api/AnotherFakeApi.php index 62a8fceab5f1..3e06d99de983 100644 --- a/samples/client/petstore/php/psr-18/lib/Api/AnotherFakeApi.php +++ b/samples/client/petstore/php/psr-18/lib/Api/AnotherFakeApi.php @@ -43,6 +43,7 @@ use OpenAPI\Client\Configuration; use OpenAPI\Client\DebugPlugin; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; diff --git a/samples/client/petstore/php/psr-18/lib/Api/DefaultApi.php b/samples/client/petstore/php/psr-18/lib/Api/DefaultApi.php index 888725830578..5db97c39e741 100644 --- a/samples/client/petstore/php/psr-18/lib/Api/DefaultApi.php +++ b/samples/client/petstore/php/psr-18/lib/Api/DefaultApi.php @@ -43,6 +43,7 @@ use OpenAPI\Client\Configuration; use OpenAPI\Client\DebugPlugin; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; diff --git a/samples/client/petstore/php/psr-18/lib/Api/FakeApi.php b/samples/client/petstore/php/psr-18/lib/Api/FakeApi.php index 02e1f928ff10..4cfc2e741dc1 100644 --- a/samples/client/petstore/php/psr-18/lib/Api/FakeApi.php +++ b/samples/client/petstore/php/psr-18/lib/Api/FakeApi.php @@ -43,6 +43,7 @@ use OpenAPI\Client\Configuration; use OpenAPI\Client\DebugPlugin; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; @@ -5394,69 +5395,27 @@ public function testEndpointParametersRequest($number, $double, $pattern_without // form params - if ($integer !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('integer', $integer)); - } - // form params - if ($int32 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('int32', $int32)); - } - // form params - if ($int64 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('int64', $int64)); - } - // form params - if ($number !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('number', $number)); - } - // form params - if ($float !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('float', $float)); - } - // form params - if ($double !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('double', $double)); - } - // form params - if ($string !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('string', $string)); - } - // form params - if ($pattern_without_delimiter !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('pattern_without_delimiter', $pattern_without_delimiter)); - } - // form params - if ($byte !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('byte', $byte)); - } - // form params - if ($binary !== null) { - $multipart = true; - $formParams['binary'] = []; - $paramFiles = is_array($binary) ? $binary : [$binary]; - foreach ($paramFiles as $paramFile) { - $formParams['binary'][] = \GuzzleHttp\Psr7\try_fopen( - ObjectSerializer::toFormValue('binary', $paramFile)['binary'], - 'rb' - ); - } - } - // form params - if ($date !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('date', $date)); - } - // form params - if ($date_time !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('dateTime', $date_time)); - } - // form params - if ($password !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('password', $password)); - } - // form params - if ($callback !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('callback', $callback)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'integer' => $integer, + 'int32' => $int32, + 'int64' => $int64, + 'number' => $number, + 'float' => $float, + 'double' => $double, + 'string' => $string, + 'pattern_without_delimiter' => $pattern_without_delimiter, + 'byte' => $byte, + 'binary' => $binary, + 'date' => $date, + 'date_time' => $date_time, + 'password' => $password, + 'callback' => $callback, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], @@ -5765,13 +5724,15 @@ public function testEnumParametersRequest($enum_header_string_array = null, $enu // form params - if ($enum_form_string_array !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('enum_form_string_array', $enum_form_string_array)); - } - // form params - if ($enum_form_string !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('enum_form_string', $enum_form_string)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'enum_form_string_array' => $enum_form_string_array, + 'enum_form_string' => $enum_form_string, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], @@ -6719,13 +6680,15 @@ public function testJsonFormDataRequest($param, $param2) // form params - if ($param !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('param', $param)); - } - // form params - if ($param2 !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('param2', $param2)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'param' => $param, + 'param2' => $param2, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], diff --git a/samples/client/petstore/php/psr-18/lib/Api/FakeClassnameTags123Api.php b/samples/client/petstore/php/psr-18/lib/Api/FakeClassnameTags123Api.php index 100429551e05..30264c8f199a 100644 --- a/samples/client/petstore/php/psr-18/lib/Api/FakeClassnameTags123Api.php +++ b/samples/client/petstore/php/psr-18/lib/Api/FakeClassnameTags123Api.php @@ -43,6 +43,7 @@ use OpenAPI\Client\Configuration; use OpenAPI\Client\DebugPlugin; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; diff --git a/samples/client/petstore/php/psr-18/lib/Api/PetApi.php b/samples/client/petstore/php/psr-18/lib/Api/PetApi.php index 62abe18f2bd1..8f623f5eb0f2 100644 --- a/samples/client/petstore/php/psr-18/lib/Api/PetApi.php +++ b/samples/client/petstore/php/psr-18/lib/Api/PetApi.php @@ -43,6 +43,7 @@ use OpenAPI\Client\Configuration; use OpenAPI\Client\DebugPlugin; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; @@ -1838,13 +1839,15 @@ public function updatePetWithFormRequest($pet_id, $name = null, $status = null) } // form params - if ($name !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('name', $name)); - } - // form params - if ($status !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('status', $status)); - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'name' => $name, + 'status' => $status, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( [], @@ -2118,21 +2121,15 @@ public function uploadFileRequest($pet_id, $additional_metadata = null, $file = } // form params - if ($additional_metadata !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('additionalMetadata', $additional_metadata)); - } - // form params - if ($file !== null) { - $multipart = true; - $formParams['file'] = []; - $paramFiles = is_array($file) ? $file : [$file]; - foreach ($paramFiles as $paramFile) { - $formParams['file'][] = \GuzzleHttp\Psr7\try_fopen( - ObjectSerializer::toFormValue('file', $paramFile)['file'], - 'rb' - ); - } - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'additional_metadata' => $additional_metadata, + 'file' => $file, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['application/json'], @@ -2412,21 +2409,15 @@ public function uploadFileWithRequiredFileRequest($pet_id, $required_file, $addi } // form params - if ($additional_metadata !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('additionalMetadata', $additional_metadata)); - } - // form params - if ($required_file !== null) { - $multipart = true; - $formParams['requiredFile'] = []; - $paramFiles = is_array($required_file) ? $required_file : [$required_file]; - foreach ($paramFiles as $paramFile) { - $formParams['requiredFile'][] = \GuzzleHttp\Psr7\try_fopen( - ObjectSerializer::toFormValue('requiredFile', $paramFile)['requiredFile'], - 'rb' - ); - } - } + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'additional_metadata' => $additional_metadata, + 'required_file' => $required_file, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['application/json'], @@ -2743,53 +2734,297 @@ public function uploadImageFullFormDataRequest($pet_id, $name, $photo_urls, $id } // form params - if ($id !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('id', $id)); - } - // form params - if ($category !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('category', $category)); - } - // form params - if ($name !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('name', $name)); - } - // form params - if ($photo_urls !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('photoUrls', $photo_urls)); + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'id' => $id, + 'category' => $category, + 'name' => $name, + 'photo_urls' => $photo_urls, + 'tags' => $tags, + 'status' => $status, + 'file' => $file, + 'multiple_files' => $multiple_files, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; + + $headers = $this->headerSelector->selectHeaders( + ['application/json'], + 'multipart/form-data', + $multipart + ); + + // for model (json/xml) + if (count($formParams) > 0) { + if ($multipart) { + $multipartContents = []; + foreach ($formParams as $formParamName => $formParamValue) { + $formParamValueItems = is_array($formParamValue) ? $formParamValue : [$formParamValue]; + foreach ($formParamValueItems as $formParamValueItem) { + $multipartContents[] = [ + 'name' => $formParamName, + 'contents' => $formParamValueItem + ]; + } + } + // for HTTP post (form) + $httpBody = new MultipartStream($multipartContents); + + } elseif ($this->headerSelector->isJsonMime($headers['Content-Type'])) { + $httpBody = json_encode($formParams); + + } else { + // for HTTP post (form) + $httpBody = ObjectSerializer::buildQuery($formParams); + } } - // form params - if ($tags !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('tags', $tags)); + + // this endpoint requires OAuth (access token) + if ($this->config->getAccessToken() !== null) { + $headers['Authorization'] = 'Bearer ' . $this->config->getAccessToken(); } - // form params - if ($status !== null) { - $formParams = array_merge($formParams, ObjectSerializer::toFormValue('status', $status)); + + $defaultHeaders = []; + if ($this->config->getUserAgent()) { + $defaultHeaders['User-Agent'] = $this->config->getUserAgent(); } - // form params - if ($file !== null) { - $multipart = true; - $formParams['file'] = []; - $paramFiles = is_array($file) ? $file : [$file]; - foreach ($paramFiles as $paramFile) { - $formParams['file'][] = \GuzzleHttp\Psr7\try_fopen( - ObjectSerializer::toFormValue('file', $paramFile)['file'], - 'rb' + + $headers = array_merge( + $defaultHeaders, + $headerParams, + $headers + ); + + $operationHost = $this->config->getHost(); + + $uri = $this->createUri($operationHost, $resourcePath, $queryParams); + + return $this->createRequest('POST', $uri, $headers, $httpBody); + } + + /** + * Operation uploadImageFullFormDataNested + * + * uploads an image attached to a Pet object as formdata + * + * @param int $pet_id ID of pet to update (required) + * @param \OpenAPI\Client\Model\PetWithFile $pet pet (optional) + * + * @throws \OpenAPI\Client\ApiException on non-2xx response + * @throws \InvalidArgumentException + * @return \OpenAPI\Client\Model\ApiResponse + */ + public function uploadImageFullFormDataNested($pet_id, $pet = null) + { + list($response) = $this->uploadImageFullFormDataNestedWithHttpInfo($pet_id, $pet); + return $response; + } + + /** + * Operation uploadImageFullFormDataNestedWithHttpInfo + * + * uploads an image attached to a Pet object as formdata + * + * @param int $pet_id ID of pet to update (required) + * @param \OpenAPI\Client\Model\PetWithFile $pet (optional) + * + * @throws \OpenAPI\Client\ApiException on non-2xx response + * @throws \InvalidArgumentException + * @return array of \OpenAPI\Client\Model\ApiResponse, HTTP status code, HTTP response headers (array of strings) + */ + public function uploadImageFullFormDataNestedWithHttpInfo($pet_id, $pet = null) + { + $request = $this->uploadImageFullFormDataNestedRequest($pet_id, $pet); + + try { + try { + $response = $this->httpClient->sendRequest($request); + } catch (HttpException $e) { + $response = $e->getResponse(); + throw new ApiException( + sprintf( + '[%d] Error connecting to the API (%s)', + $response->getStatusCode(), + (string) $request->getUri() + ), + $request, + $response, + $e + ); + } catch (ClientExceptionInterface $e) { + throw new ApiException( + "[{$e->getCode()}] {$e->getMessage()}", + $request, + null, + $e ); } - } - // form params - if ($multiple_files !== null) { - $multipart = true; - $formParams['multiple_files'] = []; - $paramFiles = is_array($multiple_files) ? $multiple_files : [$multiple_files]; - foreach ($paramFiles as $paramFile) { - $formParams['multiple_files'][] = \GuzzleHttp\Psr7\try_fopen( - ObjectSerializer::toFormValue('multiple_files', $paramFile)['multiple_files'], - 'rb' + + $statusCode = $response->getStatusCode(); + + + switch($statusCode) { + case 200: + return $this->handleResponseWithDataType( + '\OpenAPI\Client\Model\ApiResponse', + $request, + $response, + ); + } + + + + if ($statusCode < 200 || $statusCode > 299) { + throw new ApiException( + sprintf( + '[%d] Error connecting to the API (%s)', + $statusCode, + (string) $request->getUri() + ), + $statusCode, + $response->getHeaders(), + (string) $response->getBody() ); } + + return $this->handleResponseWithDataType( + '\OpenAPI\Client\Model\ApiResponse', + $request, + $response, + ); + } catch (ApiException $e) { + switch ($e->getCode()) { + case 200: + $data = ObjectSerializer::deserialize( + $e->getResponseBody(), + '\OpenAPI\Client\Model\ApiResponse', + $e->getResponseHeaders() + ); + $e->setResponseObject($data); + throw $e; + } + + + throw $e; } + } + + /** + * Operation uploadImageFullFormDataNestedAsync + * + * uploads an image attached to a Pet object as formdata + * + * @param int $pet_id ID of pet to update (required) + * @param \OpenAPI\Client\Model\PetWithFile $pet (optional) + * + * @throws \InvalidArgumentException + * @return Promise + */ + public function uploadImageFullFormDataNestedAsync($pet_id, $pet = null) + { + return $this->uploadImageFullFormDataNestedAsyncWithHttpInfo($pet_id, $pet) + ->then( + function ($response) { + return $response[0]; + } + ); + } + + /** + * Operation uploadImageFullFormDataNestedAsyncWithHttpInfo + * + * uploads an image attached to a Pet object as formdata + * + * @param int $pet_id ID of pet to update (required) + * @param \OpenAPI\Client\Model\PetWithFile $pet (optional) + * + * @throws \InvalidArgumentException + * @return Promise + */ + public function uploadImageFullFormDataNestedAsyncWithHttpInfo($pet_id, $pet = null) + { + $returnType = '\OpenAPI\Client\Model\ApiResponse'; + $request = $this->uploadImageFullFormDataNestedRequest($pet_id, $pet); + + return $this->httpAsyncClient->sendAsyncRequest($request) + ->then( + function ($response) use ($returnType) { + if ($returnType === '\SplFileObject') { + $content = $response->getBody(); //stream goes to serializer + } else { + $content = (string) $response->getBody(); + } + + return [ + ObjectSerializer::deserialize($content, $returnType, []), + $response->getStatusCode(), + $response->getHeaders() + ]; + }, + function (HttpException $exception) { + $response = $exception->getResponse(); + $statusCode = $response->getStatusCode(); + throw new ApiException( + sprintf( + '[%d] Error connecting to the API (%s)', + $statusCode, + $exception->getRequest()->getUri() + ), + $exception->getRequest(), + $exception->getResponse(), + $exception + ); + } + ); + } + + /** + * Create request for operation 'uploadImageFullFormDataNested' + * + * @param int $pet_id ID of pet to update (required) + * @param \OpenAPI\Client\Model\PetWithFile $pet (optional) + * + * @throws \InvalidArgumentException + * @return RequestInterface + */ + public function uploadImageFullFormDataNestedRequest($pet_id, $pet = null) + { + // verify the required parameter 'pet_id' is set + if ($pet_id === null || (is_array($pet_id) && count($pet_id) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $pet_id when calling uploadImageFullFormDataNested' + ); + } + + $resourcePath = '/pet/{petId}/uploadImageFullFormDataNested'; + $formParams = []; + $queryParams = []; + $headerParams = []; + $httpBody = null; + $multipart = false; + + + + // path params + if ($pet_id !== null) { + $resourcePath = str_replace( + '{' . 'petId' . '}', + ObjectSerializer::toPathValue($pet_id), + $resourcePath + ); + } + + // form params + $formDataProcessor = new FormDataProcessor(); + + $formData = $formDataProcessor->prepare([ + 'pet' => $pet, + ]); + + $formParams = $formDataProcessor->flatten($formData); + $multipart = $formDataProcessor->has_file; $headers = $this->headerSelector->selectHeaders( ['application/json'], diff --git a/samples/client/petstore/php/psr-18/lib/Api/StoreApi.php b/samples/client/petstore/php/psr-18/lib/Api/StoreApi.php index 387bd9864a30..ced4b55f6f6b 100644 --- a/samples/client/petstore/php/psr-18/lib/Api/StoreApi.php +++ b/samples/client/petstore/php/psr-18/lib/Api/StoreApi.php @@ -43,6 +43,7 @@ use OpenAPI\Client\Configuration; use OpenAPI\Client\DebugPlugin; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; diff --git a/samples/client/petstore/php/psr-18/lib/Api/UserApi.php b/samples/client/petstore/php/psr-18/lib/Api/UserApi.php index e0c744726ed9..6e7edac2d9fb 100644 --- a/samples/client/petstore/php/psr-18/lib/Api/UserApi.php +++ b/samples/client/petstore/php/psr-18/lib/Api/UserApi.php @@ -43,6 +43,7 @@ use OpenAPI\Client\Configuration; use OpenAPI\Client\DebugPlugin; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; diff --git a/samples/client/petstore/php/psr-18/lib/FormDataProcessor.php b/samples/client/petstore/php/psr-18/lib/FormDataProcessor.php new file mode 100644 index 000000000000..c416f584e6aa --- /dev/null +++ b/samples/client/petstore/php/psr-18/lib/FormDataProcessor.php @@ -0,0 +1,242 @@ + $values the value of the form parameter + * + * @return array [key => value] of formdata + */ + public function prepare(array $values): array + { + $this->has_file = false; + $result = []; + + foreach ($values as $k => $v) { + if ($v === null) { + continue; + } + + $result[$k] = $this->makeFormSafe($v); + } + + return $result; + } + + /** + * Flattens a multi-level array of data and generates a single-level array + * compatible with formdata - a single-level array where the keys use bracket + * notation to signify nested data. + * + * credit: https://github.com/FranBar1966/FlatPHP + */ + public static function flatten(array $source, string $start = ''): array + { + $opt = [ + 'prefix' => '[', + 'suffix' => ']', + 'suffix-end' => true, + 'prefix-list' => '[', + 'suffix-list' => ']', + 'suffix-list-end' => true, + ]; + + if ($start === '') { + $currentPrefix = ''; + $currentSuffix = ''; + $currentSuffixEnd = false; + } elseif (array_is_list($source)) { + $currentPrefix = $opt['prefix-list']; + $currentSuffix = $opt['suffix-list']; + $currentSuffixEnd = $opt['suffix-list-end']; + } else { + $currentPrefix = $opt['prefix']; + $currentSuffix = $opt['suffix']; + $currentSuffixEnd = $opt['suffix-end']; + } + + $currentName = $start; + $result = []; + + foreach ($source as $key => $val) { + $currentName .= $currentPrefix.$key; + + if (is_array($val) && !empty($val)) { + $currentName .= $currentSuffix; + $result += self::flatten($val, $currentName); + } else { + if ($currentSuffixEnd) { + $currentName .= $currentSuffix; + } + + $result[$currentName] = ObjectSerializer::toString($val); + } + + $currentName = $start; + } + + return $result; + } + + /** + * formdata must be limited to scalars or arrays of scalar values, + * or a resource for a file upload. Here we iterate through all available + * data and identify how to handle each scenario + */ + protected function makeFormSafe($value) + { + if ($value instanceof SplFileObject) { + return $this->processFiles([$value])[0]; + } + + if (is_resource($value)) { + $this->has_file = true; + + return $value; + } + + if ($value instanceof ModelInterface) { + return $this->processModel($value); + } + + if (is_array($value) || (is_object($value) && !$value instanceof \DateTimeInterface)) { + $data = []; + + foreach ($value as $k => $v) { + $data[$k] = $this->makeFormSafe($v); + } + + return $data; + } + + return ObjectSerializer::toString($value); + } + + /** + * We are able to handle nested ModelInterface. We do not simply call + * json_decode(json_encode()) because any given model may have binary data + * or other data that cannot be serialized to a JSON string + */ + protected function processModel(ModelInterface $model): array + { + $result = []; + + foreach ($model::openAPITypes() as $name => $type) { + $value = $model->offsetGet($name); + + if ($value === null) { + continue; + } + + if (strpos($type, '\SplFileObject') !== false) { + $file = is_array($value) ? $value : [$value]; + $result[$name] = $this->processFiles($file); + + continue; + } + + if ($value instanceof ModelInterface) { + $result[$name] = $this->processModel($value); + + continue; + } + + if (is_array($value) || is_object($value)) { + $result[$name] = $this->makeFormSafe($value); + + continue; + } + + $result[$name] = ObjectSerializer::toString($value); + } + + return $result; + } + + /** + * Handle file data + */ + protected function processFiles(array $files): array + { + $this->has_file = true; + + $result = []; + + foreach ($files as $i => $file) { + if (is_array($file)) { + $result[$i] = $this->processFiles($file); + + continue; + } + + if ($file instanceof StreamInterface) { + $result[$i] = $file; + + continue; + } + + if ($file instanceof SplFileObject) { + $result[$i] = $this->tryFopen($file); + } + } + + return $result; + } + + private function tryFopen(SplFileObject $file) + { + return Utils::tryFopen($file->getRealPath(), 'rb'); + } +} diff --git a/samples/client/petstore/php/psr-18/lib/Model/PetWithFile.php b/samples/client/petstore/php/psr-18/lib/Model/PetWithFile.php new file mode 100644 index 000000000000..3e91dabb4b55 --- /dev/null +++ b/samples/client/petstore/php/psr-18/lib/Model/PetWithFile.php @@ -0,0 +1,691 @@ + + */ +class PetWithFile implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'PetWithFile'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'id' => 'int', + 'category' => '\OpenAPI\Client\Model\Category', + 'name' => 'string', + 'photo_urls' => 'string[]', + 'tags' => '\OpenAPI\Client\Model\Tag[]', + 'status' => 'string', + 'file' => '\SplFileObject', + 'multiple_files' => '\SplFileObject[]' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'id' => 'int64', + 'category' => null, + 'name' => null, + 'photo_urls' => null, + 'tags' => null, + 'status' => null, + 'file' => 'binary', + 'multiple_files' => 'binary' + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'id' => false, + 'category' => false, + 'name' => false, + 'photo_urls' => false, + 'tags' => false, + 'status' => false, + 'file' => false, + 'multiple_files' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'id' => 'id', + 'category' => 'category', + 'name' => 'name', + 'photo_urls' => 'photoUrls', + 'tags' => 'tags', + 'status' => 'status', + 'file' => 'file', + 'multiple_files' => 'multiple_files' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'id' => 'setId', + 'category' => 'setCategory', + 'name' => 'setName', + 'photo_urls' => 'setPhotoUrls', + 'tags' => 'setTags', + 'status' => 'setStatus', + 'file' => 'setFile', + 'multiple_files' => 'setMultipleFiles' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'id' => 'getId', + 'category' => 'getCategory', + 'name' => 'getName', + 'photo_urls' => 'getPhotoUrls', + 'tags' => 'getTags', + 'status' => 'getStatus', + 'file' => 'getFile', + 'multiple_files' => 'getMultipleFiles' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + public const STATUS_AVAILABLE = 'available'; + public const STATUS_PENDING = 'pending'; + public const STATUS_SOLD = 'sold'; + + /** + * Gets allowable values of the enum + * + * @return string[] + */ + public function getStatusAllowableValues() + { + return [ + self::STATUS_AVAILABLE, + self::STATUS_PENDING, + self::STATUS_SOLD, + ]; + } + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('id', $data ?? [], null); + $this->setIfExists('category', $data ?? [], null); + $this->setIfExists('name', $data ?? [], null); + $this->setIfExists('photo_urls', $data ?? [], null); + $this->setIfExists('tags', $data ?? [], null); + $this->setIfExists('status', $data ?? [], null); + $this->setIfExists('file', $data ?? [], null); + $this->setIfExists('multiple_files', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['name'] === null) { + $invalidProperties[] = "'name' can't be null"; + } + if ($this->container['photo_urls'] === null) { + $invalidProperties[] = "'photo_urls' can't be null"; + } + $allowedValues = $this->getStatusAllowableValues(); + if (!is_null($this->container['status']) && !in_array($this->container['status'], $allowedValues, true)) { + $invalidProperties[] = sprintf( + "invalid value '%s' for 'status', must be one of '%s'", + $this->container['status'], + implode("', '", $allowedValues) + ); + } + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets id + * + * @return int|null + */ + public function getId() + { + return $this->container['id']; + } + + /** + * Sets id + * + * @param int|null $id id + * + * @return self + */ + public function setId($id) + { + if (is_null($id)) { + throw new \InvalidArgumentException('non-nullable id cannot be null'); + } + $this->container['id'] = $id; + + return $this; + } + + /** + * Gets category + * + * @return \OpenAPI\Client\Model\Category|null + */ + public function getCategory() + { + return $this->container['category']; + } + + /** + * Sets category + * + * @param \OpenAPI\Client\Model\Category|null $category category + * + * @return self + */ + public function setCategory($category) + { + if (is_null($category)) { + throw new \InvalidArgumentException('non-nullable category cannot be null'); + } + $this->container['category'] = $category; + + return $this; + } + + /** + * Gets name + * + * @return string + */ + public function getName() + { + return $this->container['name']; + } + + /** + * Sets name + * + * @param string $name name + * + * @return self + */ + public function setName($name) + { + if (is_null($name)) { + throw new \InvalidArgumentException('non-nullable name cannot be null'); + } + $this->container['name'] = $name; + + return $this; + } + + /** + * Gets photo_urls + * + * @return string[] + */ + public function getPhotoUrls() + { + return $this->container['photo_urls']; + } + + /** + * Sets photo_urls + * + * @param string[] $photo_urls photo_urls + * + * @return self + */ + public function setPhotoUrls($photo_urls) + { + if (is_null($photo_urls)) { + throw new \InvalidArgumentException('non-nullable photo_urls cannot be null'); + } + + + $this->container['photo_urls'] = $photo_urls; + + return $this; + } + + /** + * Gets tags + * + * @return \OpenAPI\Client\Model\Tag[]|null + */ + public function getTags() + { + return $this->container['tags']; + } + + /** + * Sets tags + * + * @param \OpenAPI\Client\Model\Tag[]|null $tags tags + * + * @return self + */ + public function setTags($tags) + { + if (is_null($tags)) { + throw new \InvalidArgumentException('non-nullable tags cannot be null'); + } + $this->container['tags'] = $tags; + + return $this; + } + + /** + * Gets status + * + * @return string|null + */ + public function getStatus() + { + return $this->container['status']; + } + + /** + * Sets status + * + * @param string|null $status pet status in the store + * + * @return self + */ + public function setStatus($status) + { + if (is_null($status)) { + throw new \InvalidArgumentException('non-nullable status cannot be null'); + } + $allowedValues = $this->getStatusAllowableValues(); + if (!in_array($status, $allowedValues, true)) { + throw new \InvalidArgumentException( + sprintf( + "Invalid value '%s' for 'status', must be one of '%s'", + $status, + implode("', '", $allowedValues) + ) + ); + } + $this->container['status'] = $status; + + return $this; + } + + /** + * Gets file + * + * @return \SplFileObject|null + */ + public function getFile() + { + return $this->container['file']; + } + + /** + * Sets file + * + * @param \SplFileObject|null $file file to upload + * + * @return self + */ + public function setFile($file) + { + if (is_null($file)) { + throw new \InvalidArgumentException('non-nullable file cannot be null'); + } + $this->container['file'] = $file; + + return $this; + } + + /** + * Gets multiple_files + * + * @return \SplFileObject[]|null + */ + public function getMultipleFiles() + { + return $this->container['multiple_files']; + } + + /** + * Sets multiple_files + * + * @param \SplFileObject[]|null $multiple_files multiple_files + * + * @return self + */ + public function setMultipleFiles($multiple_files) + { + if (is_null($multiple_files)) { + throw new \InvalidArgumentException('non-nullable multiple_files cannot be null'); + } + $this->container['multiple_files'] = $multiple_files; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer $offset Offset + * + * @return boolean + */ + public function offsetExists($offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer $offset Offset + * + * @return void + */ + public function offsetUnset($offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/samples/client/petstore/php/psr-18/lib/ObjectSerializer.php b/samples/client/petstore/php/psr-18/lib/ObjectSerializer.php index a144eb7641b1..daa7d49244ce 100644 --- a/samples/client/petstore/php/psr-18/lib/ObjectSerializer.php +++ b/samples/client/petstore/php/psr-18/lib/ObjectSerializer.php @@ -28,7 +28,6 @@ namespace OpenAPI\Client; -use ArrayAccess; use GuzzleHttp\Psr7\Utils; use OpenAPI\Client\Model\ModelInterface; @@ -324,35 +323,6 @@ public static function toHeaderValue($value) return self::toString($value); } - /** - * Take value and turn it into an array suitable for inclusion in - * the http body (form parameter). If it's a string, pass through unchanged - * If it's a datetime object, format it in ISO8601 - * - * @param string|bool|array|DateTime|ArrayAccess|\SplFileObject $value the value of the form parameter - * - * @return array [key => value] of formdata - */ - public static function toFormValue(string $key, $value) - { - if ($value instanceof \SplFileObject) { - return [$key => $value->getRealPath()]; - } elseif (is_array($value) || $value instanceof ArrayAccess) { - $flattened = []; - $result = []; - - self::flattenArray(json_decode(json_encode($value), true), $flattened); - - foreach ($flattened as $k => $v) { - $result["{$key}{$k}"] = self::toString($v); - } - - return $result; - } else { - return [$key => self::toString($value)]; - } - } - /** * Take value and turn it into a string suitable for inclusion in * the parameter. If it's a string, pass through unchanged @@ -626,81 +596,4 @@ public static function buildQuery(array $params, $encoding = PHP_QUERY_RFC3986): return $qs ? (string) substr($qs, 0, -1) : ''; } - - /** - * Flattens an array of Model object and generates an array compatible - * with formdata - a single-level array where the keys use bracket - * notation to signify nested data. - * - * @param \ArrayAccess|array $source - * - * credit: https://github.com/FranBar1966/FlatPHP - */ - private static function flattenArray( - $source, - array &$destination, - string $start = '' - ) { - $opt = [ - 'prefix' => '[', - 'suffix' => ']', - 'suffix-end' => true, - 'prefix-list' => '[', - 'suffix-list' => ']', - 'suffix-list-end' => true, - ]; - - if (!is_array($source)) { - $source = (array) $source; - } - - /** - * array_is_list only in PHP >= 8.1 - * - * credit: https://www.php.net/manual/en/function.array-is-list.php#127044 - */ - if (!function_exists('array_is_list')) { - function array_is_list(array $array) - { - $i = -1; - - foreach ($array as $k => $v) { - ++$i; - if ($k !== $i) { - return false; - } - } - - return true; - } - } - - if (array_is_list($source)) { - $currentPrefix = $opt['prefix-list']; - $currentSuffix = $opt['suffix-list']; - $currentSuffixEnd = $opt['suffix-list-end']; - } else { - $currentPrefix = $opt['prefix']; - $currentSuffix = $opt['suffix']; - $currentSuffixEnd = $opt['suffix-end']; - } - - $currentName = $start; - - foreach ($source as $key => $val) { - $currentName .= $currentPrefix.$key; - - if (is_array($val) && !empty($val)) { - $currentName .= "{$currentSuffix}"; - self::flattenArray($val, $destination, $currentName); - } else { - if ($currentSuffixEnd) { - $currentName .= $currentSuffix; - } - $destination[$currentName] = self::toString($val); - } - - $currentName = $start; - } - } } diff --git a/samples/client/petstore/php/psr-18/test/Model/PetWithFileTest.php b/samples/client/petstore/php/psr-18/test/Model/PetWithFileTest.php new file mode 100644 index 000000000000..ce0a73f294d0 --- /dev/null +++ b/samples/client/petstore/php/psr-18/test/Model/PetWithFileTest.php @@ -0,0 +1,153 @@ +