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 96c394a31bb0..2161f88a8864 100644 --- a/modules/openapi-generator/src/main/resources/php-nextgen/api.mustache +++ b/modules/openapi-generator/src/main/resources/php-nextgen/api.mustache @@ -29,6 +29,7 @@ use GuzzleHttp\Promise\PromiseInterface; use {{invokerPackage}}\ApiException; use {{invokerPackage}}\Configuration; use {{invokerPackage}}\HeaderSelector; +use {{invokerPackage}}\FormDataProcessor; use {{invokerPackage}}\ObjectSerializer; /** @@ -749,25 +750,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 9acfcd42797f..f9db88143cd0 100644 --- a/modules/openapi-generator/src/main/resources/php/api.mustache +++ b/modules/openapi-generator/src/main/resources/php/api.mustache @@ -27,6 +27,7 @@ use GuzzleHttp\Psr7\Request; use GuzzleHttp\RequestOptions; use {{invokerPackage}}\ApiException; use {{invokerPackage}}\Configuration; +use {{invokerPackage}}\FormDataProcessor; use {{invokerPackage}}\HeaderSelector; use {{invokerPackage}}\ObjectSerializer; @@ -669,23 +670,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 16e76da9074c..1b47bd197269 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; @@ -585,23 +586,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 e971a99dc261..d3492997de57 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 2ab4f17c90c4..969961bd61a2 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 @@ -39,6 +39,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 78c0645f1ad0..dd55eba1fb0e 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 @@ -39,6 +39,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -1026,19 +1027,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', ], @@ -1349,19 +1345,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 193033455ace..39aadcbd1e18 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 @@ -39,6 +39,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -407,17 +408,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', ], @@ -734,9 +734,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', ], @@ -1102,29 +1107,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 aa4c1fcbdc9a..0ae433ed4eb6 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 @@ -39,6 +39,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 5fb253899d02..1e5785253fce 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 @@ -39,6 +39,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 12169d7ef19b..5fcca472ffe3 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 @@ -39,6 +39,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 2ab4f17c90c4..969961bd61a2 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/AuthApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/AuthApi.php @@ -39,6 +39,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 c2459f1777eb..4e0ad3a30218 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/BodyApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/BodyApi.php @@ -39,6 +39,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -1026,19 +1027,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', ], @@ -1349,19 +1345,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 193033455ace..39aadcbd1e18 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/FormApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/FormApi.php @@ -39,6 +39,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -407,17 +408,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', ], @@ -734,9 +734,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', ], @@ -1102,29 +1107,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 aa4c1fcbdc9a..0ae433ed4eb6 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/HeaderApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/HeaderApi.php @@ -39,6 +39,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 5fb253899d02..1e5785253fce 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/PathApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/PathApi.php @@ -39,6 +39,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 12169d7ef19b..5fcca472ffe3 100644 --- a/samples/client/echo_api/php-nextgen/src/Api/QueryApi.php +++ b/samples/client/echo_api/php-nextgen/src/Api/QueryApi.php @@ -39,6 +39,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 ebd39c9d9de9..2f93e8839383 100644 --- a/samples/client/petstore/php-nextgen/OpenAPIClient-php/.openapi-generator/FILES +++ b/samples/client/petstore/php-nextgen/OpenAPIClient-php/.openapi-generator/FILES @@ -73,6 +73,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 c69a68e49f40..449c06e5bab6 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 @@ -38,6 +38,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 aeff9f7c541a..1a3e901629ef 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 @@ -38,6 +38,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 07ed43c9eaaf..efd61f135b88 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 @@ -38,6 +38,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -4584,71 +4585,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( [], @@ -5016,13 +4973,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( [], @@ -6054,13 +6013,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 2324371fccd7..77ad025812d6 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 @@ -38,6 +38,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 564d43a7150e..f419abd77e04 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 @@ -38,6 +38,7 @@ use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; use OpenAPI\Client\HeaderSelector; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\ObjectSerializer; /** @@ -2363,13 +2364,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( [], @@ -2720,23 +2723,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', ], @@ -3093,23 +3088,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 705302d06141..dae41b6fc425 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 @@ -38,6 +38,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 97e46e3bf84d..d08d537c9828 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 @@ -38,6 +38,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 163723370d10..a7ab9665e70e 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/.openapi-generator/FILES +++ b/samples/client/petstore/php/OpenAPIClient-php/.openapi-generator/FILES @@ -53,6 +53,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 @@ -70,6 +71,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 @@ -115,6 +117,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 81f5c4bf3639..12973a780517 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/README.md +++ b/samples/client/petstore/php/OpenAPIClient-php/README.md @@ -107,6 +107,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 @@ -165,6 +166,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 d97d060f866c..259b0d80a0dd 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/AnotherFakeApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/AnotherFakeApi.php @@ -36,6 +36,7 @@ use GuzzleHttp\RequestOptions; 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 2a3996d403cb..d597a9866dd0 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/DefaultApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/DefaultApi.php @@ -36,6 +36,7 @@ use GuzzleHttp\RequestOptions; 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 f1349de63575..bebd31f22868 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeApi.php @@ -36,6 +36,7 @@ use GuzzleHttp\RequestOptions; use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\HeaderSelector; use OpenAPI\Client\ObjectSerializer; @@ -4566,69 +4567,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( [], @@ -4941,13 +4900,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( [], @@ -5919,13 +5880,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 d2215d53885d..7064d169e3ee 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeClassnameTags123Api.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/FakeClassnameTags123Api.php @@ -36,6 +36,7 @@ use GuzzleHttp\RequestOptions; 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 3f3f7049302a..551a6615d77e 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/PetApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/PetApi.php @@ -36,6 +36,7 @@ use GuzzleHttp\RequestOptions; use OpenAPI\Client\ApiException; use OpenAPI\Client\Configuration; +use OpenAPI\Client\FormDataProcessor; use OpenAPI\Client\HeaderSelector; use OpenAPI\Client\ObjectSerializer; @@ -103,6 +104,9 @@ class PetApi 'uploadImageFullFormData' => [ 'multipart/form-data', ], + 'uploadImageFullFormDataNested' => [ + 'multipart/form-data', + ], ]; /** @@ -2226,13 +2230,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( [], @@ -2558,21 +2564,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( @@ -2905,21 +2905,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( @@ -3294,54 +3288,350 @@ 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: + if ('\OpenAPI\Client\Model\ApiResponse' === '\SplFileObject') { + $content = $response->getBody(); //stream goes to serializer + } else { + $content = (string) $response->getBody(); + if ('\OpenAPI\Client\Model\ApiResponse' !== 'string') { + try { + $content = json_decode($content, false, 512, JSON_THROW_ON_ERROR); + } catch (\JsonException $exception) { + throw new ApiException( + sprintf( + 'Error JSON decoding server response (%s)', + $request->getUri() + ), + $statusCode, + $response->getHeaders(), + $content + ); + } + } + } + + return [ + ObjectSerializer::deserialize($content, '\OpenAPI\Client\Model\ApiResponse', []), + $response->getStatusCode(), + $response->getHeaders() + ]; + } + + 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() ); } + + $returnType = '\OpenAPI\Client\Model\ApiResponse'; + if ($returnType === '\SplFileObject') { + $content = $response->getBody(); //stream goes to serializer + } else { + $content = (string) $response->getBody(); + if ($returnType !== 'string') { + try { + $content = json_decode($content, false, 512, JSON_THROW_ON_ERROR); + } catch (\JsonException $exception) { + throw new ApiException( + sprintf( + 'Error JSON decoding server response (%s)', + $request->getUri() + ), + $statusCode, + $response->getHeaders(), + $content + ); + } + } + } + + return [ + ObjectSerializer::deserialize($content, $returnType, []), + $response->getStatusCode(), + $response->getHeaders() + ]; + + } catch (ApiException $e) { + switch ($e->getCode()) { + case 200: + $data = ObjectSerializer::deserialize( + $e->getResponseBody(), + '\OpenAPI\Client\Model\ApiResponse', + $e->getResponseHeaders() + ); + $e->setResponseObject($data); + break; + } + 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 25196cc8e363..5a9e8d9389a3 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/StoreApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/StoreApi.php @@ -36,6 +36,7 @@ use GuzzleHttp\RequestOptions; 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 580ea06e9c4a..2c6d92855fe2 100644 --- a/samples/client/petstore/php/OpenAPIClient-php/lib/Api/UserApi.php +++ b/samples/client/petstore/php/OpenAPIClient-php/lib/Api/UserApi.php @@ -36,6 +36,7 @@ use GuzzleHttp\RequestOptions; 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/psr-18/.openapi-generator/FILES b/samples/client/petstore/php/psr-18/.openapi-generator/FILES index 26dfe36c4c49..3f5736b9a351 100644 --- a/samples/client/petstore/php/psr-18/.openapi-generator/FILES +++ b/samples/client/petstore/php/psr-18/.openapi-generator/FILES @@ -53,6 +53,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/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 @@ -116,6 +118,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 1c8d4f2541ce..c29839b3ba01 100644 --- a/samples/client/petstore/php/psr-18/README.md +++ b/samples/client/petstore/php/psr-18/README.md @@ -119,6 +119,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 @@ -177,6 +178,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 7e5e1afbfc2c..af63b05a491f 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 005d6ffbafed..11fbdc8d0c4b 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 360a3b16f4c8..2500330fb6e6 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; @@ -4001,69 +4002,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( [], @@ -4370,13 +4329,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( [], @@ -5316,13 +5277,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 a0acbf6a5326..275def167e16 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 29b35822dba0..84dc80a9011c 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; @@ -1817,13 +1818,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( [], @@ -2093,21 +2096,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'], @@ -2383,21 +2380,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'], @@ -2710,54 +2701,294 @@ 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 ); - } - } - // 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' + } catch (ClientExceptionInterface $e) { + throw new ApiException( + "[{$e->getCode()}] {$e->getMessage()}", + $request, + null, + $e ); } + + $statusCode = $response->getStatusCode(); + + switch($statusCode) { + case 200: + if ('\OpenAPI\Client\Model\ApiResponse' === '\SplFileObject') { + $content = $response->getBody(); //stream goes to serializer + } else { + $content = (string) $response->getBody(); + } + + return [ + ObjectSerializer::deserialize($content, '\OpenAPI\Client\Model\ApiResponse', []), + $response->getStatusCode(), + $response->getHeaders() + ]; + } + + $returnType = '\OpenAPI\Client\Model\ApiResponse'; + if ($returnType === '\SplFileObject') { + $content = $response->getBody(); //stream goes to serializer + } else { + $content = (string) $response->getBody(); + } + + return [ + ObjectSerializer::deserialize($content, $returnType, []), + $response->getStatusCode(), + $response->getHeaders() + ]; + + } catch (ApiException $e) { + switch ($e->getCode()) { + case 200: + $data = ObjectSerializer::deserialize( + $e->getResponseBody(), + '\OpenAPI\Client\Model\ApiResponse', + $e->getResponseHeaders() + ); + $e->setResponseObject($data); + break; + } + 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'], 'multipart/form-data', 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 9f41d0568f2d..7d49d82294b1 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 7e001b952306..3179c9fa3640 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 @@ +