Skip to content

Commit a6355ab

Browse files
committed
[PHP] - Add FormDataProcessor to handle nested ModelInterface data
1 parent 22b6787 commit a6355ab

4 files changed

Lines changed: 248 additions & 123 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpClientCodegen.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public void processOpts() {
117117

118118
supportingFiles.add(new SupportingFile("ApiException.mustache", toSrcPath(invokerPackage, srcBasePath), "ApiException.php"));
119119
supportingFiles.add(new SupportingFile("Configuration.mustache", toSrcPath(invokerPackage, srcBasePath), "Configuration.php"));
120+
supportingFiles.add(new SupportingFile("FormDataProcessor.mustache", toSrcPath(invokerPackage, srcBasePath), "FormDataProcessor.php"));
120121
supportingFiles.add(new SupportingFile("ObjectSerializer.mustache", toSrcPath(invokerPackage, srcBasePath), "ObjectSerializer.php"));
121122
supportingFiles.add(new SupportingFile("ModelInterface.mustache", toSrcPath(modelPackage, srcBasePath), "ModelInterface.php"));
122123
supportingFiles.add(new SupportingFile("HeaderSelector.mustache", toSrcPath(invokerPackage, srcBasePath), "HeaderSelector.php"));
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
<?php
2+
/**
3+
* ObjectSerializer
4+
*
5+
* PHP version 7.4
6+
*
7+
* @category Class
8+
* @package {{invokerPackage}}
9+
* @author OpenAPI Generator team
10+
* @link https://openapi-generator.tech
11+
*/
12+
13+
{{>partial_header}}
14+
/**
15+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
16+
* https://openapi-generator.tech
17+
* Do not edit the class manually.
18+
*/
19+
20+
namespace {{invokerPackage}};
21+
22+
use ArrayAccess;
23+
use DateTime;
24+
use GuzzleHttp\Psr7\Utils;
25+
use Psr\Http\Message\StreamInterface;
26+
use SplFileObject;
27+
use {{modelPackage}}\ModelInterface;
28+
29+
/**
30+
* FormDataProcessor Class Doc Comment
31+
*
32+
* @category Class
33+
* @package {{invokerPackage}}
34+
* @author OpenAPI Generator team
35+
* @link https://openapi-generator.tech
36+
*/
37+
class FormDataProcessor
38+
{
39+
/**
40+
* Tags whether payload passed to ::prepare() contains one or more
41+
* SplFileObject or stream values.
42+
*/
43+
public bool $has_file = false;
44+
45+
/**
46+
* Take value and turn it into an array suitable for inclusion in
47+
* the http body (form parameter). If it's a string, pass through unchanged
48+
* If it's a datetime object, format it in ISO8601
49+
*
50+
* @param string|bool|array|DateTime|ArrayAccess|SplFileObject $values the value of the form parameter
51+
*
52+
* @return array [key => value] of formdata
53+
*/
54+
public function prepare(array $values): array
55+
{
56+
$this->has_file = false;
57+
$result = [];
58+
59+
foreach ($values as $k => $v) {
60+
if ($v === null) {
61+
continue;
62+
}
63+
64+
$result[$k] = $this->makeFormSafe($v);
65+
}
66+
67+
return $result;
68+
}
69+
70+
/**
71+
* Flattens a multi-level array of data and generates a single-level array
72+
* compatible with formdata - a single-level array where the keys use bracket
73+
* notation to signify nested data.
74+
*
75+
* credit: https://github.com/FranBar1966/FlatPHP
76+
*/
77+
public static function flatten(array $source, string $start = ''): array
78+
{
79+
$opt = [
80+
'prefix' => '[',
81+
'suffix' => ']',
82+
'suffix-end' => true,
83+
'prefix-list' => '[',
84+
'suffix-list' => ']',
85+
'suffix-list-end' => true,
86+
];
87+
88+
if ($start === '') {
89+
$currentPrefix = '';
90+
$currentSuffix = '';
91+
$currentSuffixEnd = false;
92+
} elseif (array_is_list($source)) {
93+
$currentPrefix = $opt['prefix-list'];
94+
$currentSuffix = $opt['suffix-list'];
95+
$currentSuffixEnd = $opt['suffix-list-end'];
96+
} else {
97+
$currentPrefix = $opt['prefix'];
98+
$currentSuffix = $opt['suffix'];
99+
$currentSuffixEnd = $opt['suffix-end'];
100+
}
101+
102+
$currentName = $start;
103+
$result = [];
104+
105+
foreach ($source as $key => $val) {
106+
$currentName .= $currentPrefix.$key;
107+
108+
if (is_array($val) && !empty($val)) {
109+
$currentName .= $currentSuffix;
110+
$result += self::flatten($val, $currentName);
111+
} else {
112+
if ($currentSuffixEnd) {
113+
$currentName .= $currentSuffix;
114+
}
115+
116+
$result[$currentName] = ObjectSerializer::toString($val);
117+
}
118+
119+
$currentName = $start;
120+
}
121+
122+
return $result;
123+
}
124+
125+
/**
126+
* formdata must be limited to scalars or arrays of scalar values,
127+
* or a resource for a file upload. Here we iterate through all available
128+
* data and identify how to handle each scenario
129+
*/
130+
protected function makeFormSafe($value)
131+
{
132+
if ($value instanceof SplFileObject) {
133+
return $this->processFiles([$value])[0];
134+
}
135+
136+
if (is_resource($value)) {
137+
$this->has_file = true;
138+
139+
return $value;
140+
}
141+
142+
if ($value instanceof ModelInterface) {
143+
return $this->processModel($value);
144+
}
145+
146+
if (is_array($value) || is_object($value)) {
147+
$data = [];
148+
149+
foreach ($value as $k => $v) {
150+
$data[$k] = $this->makeFormSafe($v);
151+
}
152+
153+
return $data;
154+
}
155+
156+
return ObjectSerializer::toString($value);
157+
}
158+
159+
/**
160+
* We are able to handle nested ModelInterface. We do not simply call
161+
* json_decode(json_encode()) because any given model may have binary data
162+
* or other data that cannot be serialized to a JSON string
163+
*/
164+
protected function processModel(ModelInterface $model): array
165+
{
166+
$result = [];
167+
168+
foreach ($model::openAPITypes() as $name => $type) {
169+
$value = $model->offsetGet($name);
170+
171+
if ($value === null) {
172+
continue;
173+
}
174+
175+
if (strpos($type, '\SplFileObject') !== false) {
176+
$file = is_array($value) ? $value : [$value];
177+
$result[$name] = $this->processFiles($file);
178+
179+
continue;
180+
}
181+
182+
if ($value instanceof ModelInterface) {
183+
$result[$name] = $this->processModel($value);
184+
185+
continue;
186+
}
187+
188+
if (is_array($value) || is_object($value)) {
189+
$result[$name] = $this->makeFormSafe($value);
190+
191+
continue;
192+
}
193+
194+
$result[$name] = ObjectSerializer::toString($value);
195+
}
196+
197+
return $result;
198+
}
199+
200+
/**
201+
* Handle file data
202+
*/
203+
protected function processFiles(array $files): array
204+
{
205+
$this->has_file = true;
206+
207+
$result = [];
208+
209+
foreach ($files as $i => $file) {
210+
if (is_array($file)) {
211+
$result[$i] = $this->processFiles($file);
212+
213+
continue;
214+
}
215+
216+
if ($file instanceof StreamInterface) {
217+
$result[$i] = $file;
218+
219+
continue;
220+
}
221+
222+
if ($file instanceof SplFileObject) {
223+
$result[$i] = $this->tryFopen($file);
224+
}
225+
}
226+
227+
return $result;
228+
}
229+
230+
private function tryFopen(SplFileObject $file)
231+
{
232+
return Utils::tryFopen($file->getRealPath(), 'rb');
233+
}
234+
}

modules/openapi-generator/src/main/resources/php/ObjectSerializer.mustache

Lines changed: 0 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
namespace {{invokerPackage}};
2121

22-
use ArrayAccess;
2322
use GuzzleHttp\Psr7\Utils;
2423
use {{modelPackage}}\ModelInterface;
2524

@@ -315,35 +314,6 @@ class ObjectSerializer
315314
return self::toString($value);
316315
}
317316
318-
/**
319-
* Take value and turn it into an array suitable for inclusion in
320-
* the http body (form parameter). If it's a string, pass through unchanged
321-
* If it's a datetime object, format it in ISO8601
322-
*
323-
* @param string|bool|array|DateTime|ArrayAccess|\SplFileObject $value the value of the form parameter
324-
*
325-
* @return array [key => value] of formdata
326-
*/
327-
public static function toFormValue(string $key, mixed $value)
328-
{
329-
if ($value instanceof \SplFileObject) {
330-
return [$key => $value->getRealPath()];
331-
} elseif (is_array($value) || $value instanceof ArrayAccess) {
332-
$flattened = [];
333-
$result = [];
334-
335-
self::flattenArray(json_decode(json_encode($value), true), $flattened);
336-
337-
foreach ($flattened as $k => $v) {
338-
$result["{$key}{$k}"] = self::toString($v);
339-
}
340-
341-
return $result;
342-
} else {
343-
return [$key => self::toString($value)];
344-
}
345-
}
346-
347317
/**
348318
* Take value and turn it into a string suitable for inclusion in
349319
* the parameter. If it's a string, pass through unchanged
@@ -617,81 +587,4 @@ class ObjectSerializer
617587

618588
return $qs ? (string) substr($qs, 0, -1) : '';
619589
}
620-
621-
/**
622-
* Flattens an array of Model object and generates an array compatible
623-
* with formdata - a single-level array where the keys use bracket
624-
* notation to signify nested data.
625-
*
626-
* @param \ArrayAccess|array $source
627-
*
628-
* credit: https://github.com/FranBar1966/FlatPHP
629-
*/
630-
private static function flattenArray(
631-
mixed $source,
632-
array &$destination,
633-
string $start = '',
634-
) {
635-
$opt = [
636-
'prefix' => '[',
637-
'suffix' => ']',
638-
'suffix-end' => true,
639-
'prefix-list' => '[',
640-
'suffix-list' => ']',
641-
'suffix-list-end' => true,
642-
];
643-
644-
if (!is_array($source)) {
645-
$source = (array) $source;
646-
}
647-
648-
/**
649-
* array_is_list only in PHP >= 8.1
650-
*
651-
* credit: https://www.php.net/manual/en/function.array-is-list.php#127044
652-
*/
653-
if (!function_exists('array_is_list')) {
654-
function array_is_list(array $array)
655-
{
656-
$i = -1;
657-
658-
foreach ($array as $k => $v) {
659-
++$i;
660-
if ($k !== $i) {
661-
return false;
662-
}
663-
}
664-
665-
return true;
666-
}
667-
}
668-
669-
if (array_is_list($source)) {
670-
$currentPrefix = $opt['prefix-list'];
671-
$currentSuffix = $opt['suffix-list'];
672-
$currentSuffixEnd = $opt['suffix-list-end'];
673-
} else {
674-
$currentPrefix = $opt['prefix'];
675-
$currentSuffix = $opt['suffix'];
676-
$currentSuffixEnd = $opt['suffix-end'];
677-
}
678-
679-
$currentName = $start;
680-
681-
foreach ($source as $key => $val) {
682-
$currentName .= $currentPrefix.$key;
683-
684-
if (is_array($val) && !empty($val)) {
685-
$currentName .= "{$currentSuffix}";
686-
self::flattenArray($val, $destination, $currentName);
687-
} else {
688-
if ($currentSuffixEnd) {
689-
$currentName .= $currentSuffix;
690-
}
691-
$destination[$currentName] = self::toString($val);
692-
}
693-
694-
$currentName = $start;
695-
}
696-
}
697590
}

0 commit comments

Comments
 (0)