Skip to content

Commit f514c62

Browse files
Merge pull request #940 from appwrite/feat-php-multiparts
feat: php multiparts
2 parents b3f8668 + 1092817 commit f514c62

File tree

14 files changed

+251
-152
lines changed

14 files changed

+251
-152
lines changed

src/SDK/Language/PHP.php

Lines changed: 80 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -137,110 +137,110 @@ public function getFiles(): array
137137
{
138138
return [
139139
[
140-
'scope' => 'default',
141-
'destination' => 'README.md',
142-
'template' => 'php/README.md.twig',
140+
'scope' => 'default',
141+
'destination' => 'README.md',
142+
'template' => 'php/README.md.twig',
143143
//'block' => 'default',
144144
],
145145
[
146-
'scope' => 'default',
147-
'destination' => 'CHANGELOG.md',
148-
'template' => 'php/CHANGELOG.md.twig',
146+
'scope' => 'default',
147+
'destination' => 'CHANGELOG.md',
148+
'template' => 'php/CHANGELOG.md.twig',
149149
],
150150
[
151-
'scope' => 'default',
152-
'destination' => 'LICENSE',
153-
'template' => 'php/LICENSE.twig',
151+
'scope' => 'default',
152+
'destination' => 'LICENSE',
153+
'template' => 'php/LICENSE.twig',
154154
],
155155
[
156-
'scope' => 'default',
157-
'destination' => 'composer.json',
158-
'template' => 'php/composer.json.twig',
156+
'scope' => 'default',
157+
'destination' => 'composer.json',
158+
'template' => 'php/composer.json.twig',
159159
],
160160
[
161-
'scope' => 'service',
162-
'destination' => 'docs/{{service.name | caseLower}}.md',
163-
'template' => 'php/docs/service.md.twig',
161+
'scope' => 'service',
162+
'destination' => 'docs/{{service.name | caseLower}}.md',
163+
'template' => 'php/docs/service.md.twig',
164164
],
165165
[
166-
'scope' => 'method',
167-
'destination' => 'docs/examples/{{service.name | caseLower}}/{{method.name | caseDash}}.md',
168-
'template' => 'php/docs/example.md.twig',
166+
'scope' => 'method',
167+
'destination' => 'docs/examples/{{service.name | caseLower}}/{{method.name | caseDash}}.md',
168+
'template' => 'php/docs/example.md.twig',
169169
],
170170
[
171-
'scope' => 'default',
172-
'destination' => 'src/{{ spec.title | caseUcfirst}}/Client.php',
173-
'template' => 'php/src/Client.php.twig',
171+
'scope' => 'default',
172+
'destination' => 'src/{{ spec.title | caseUcfirst}}/Client.php',
173+
'template' => 'php/src/Client.php.twig',
174174
],
175175
[
176-
'scope' => 'default',
177-
'destination' => 'src/{{ spec.title | caseUcfirst}}/Permission.php',
178-
'template' => 'php/src/Permission.php.twig',
176+
'scope' => 'default',
177+
'destination' => 'src/{{ spec.title | caseUcfirst}}/Permission.php',
178+
'template' => 'php/src/Permission.php.twig',
179179
],
180180
[
181-
'scope' => 'default',
182-
'destination' => 'tests/{{ spec.title | caseUcfirst}}/PermissionTest.php',
183-
'template' => 'php/tests/PermissionTest.php.twig',
181+
'scope' => 'default',
182+
'destination' => 'tests/{{ spec.title | caseUcfirst}}/PermissionTest.php',
183+
'template' => 'php/tests/PermissionTest.php.twig',
184184
],
185185
[
186-
'scope' => 'default',
187-
'destination' => 'src/{{ spec.title | caseUcfirst}}/Role.php',
188-
'template' => 'php/src/Role.php.twig',
186+
'scope' => 'default',
187+
'destination' => 'src/{{ spec.title | caseUcfirst}}/Role.php',
188+
'template' => 'php/src/Role.php.twig',
189189
],
190190
[
191-
'scope' => 'default',
192-
'destination' => 'tests/{{ spec.title | caseUcfirst}}/RoleTest.php',
193-
'template' => 'php/tests/RoleTest.php.twig',
191+
'scope' => 'default',
192+
'destination' => 'tests/{{ spec.title | caseUcfirst}}/RoleTest.php',
193+
'template' => 'php/tests/RoleTest.php.twig',
194194
],
195195
[
196-
'scope' => 'default',
197-
'destination' => 'src/{{ spec.title | caseUcfirst}}/ID.php',
198-
'template' => 'php/src/ID.php.twig',
196+
'scope' => 'default',
197+
'destination' => 'src/{{ spec.title | caseUcfirst}}/ID.php',
198+
'template' => 'php/src/ID.php.twig',
199199
],
200200
[
201-
'scope' => 'default',
202-
'destination' => 'tests/{{ spec.title | caseUcfirst}}/IDTest.php',
203-
'template' => 'php/tests/IDTest.php.twig',
201+
'scope' => 'default',
202+
'destination' => 'tests/{{ spec.title | caseUcfirst}}/IDTest.php',
203+
'template' => 'php/tests/IDTest.php.twig',
204204
],
205205
[
206-
'scope' => 'default',
207-
'destination' => 'src/{{ spec.title | caseUcfirst}}/Query.php',
208-
'template' => 'php/src/Query.php.twig',
206+
'scope' => 'default',
207+
'destination' => 'src/{{ spec.title | caseUcfirst}}/Query.php',
208+
'template' => 'php/src/Query.php.twig',
209209
],
210210
[
211-
'scope' => 'default',
212-
'destination' => 'tests/{{ spec.title | caseUcfirst}}/QueryTest.php',
213-
'template' => 'php/tests/QueryTest.php.twig',
211+
'scope' => 'default',
212+
'destination' => 'tests/{{ spec.title | caseUcfirst}}/QueryTest.php',
213+
'template' => 'php/tests/QueryTest.php.twig',
214214
],
215215
[
216-
'scope' => 'default',
217-
'destination' => 'src/{{ spec.title | caseUcfirst}}/InputFile.php',
218-
'template' => 'php/src/InputFile.php.twig',
216+
'scope' => 'default',
217+
'destination' => 'src/{{ spec.title | caseUcfirst}}/Payload.php',
218+
'template' => 'php/src/Payload.php.twig',
219219
],
220220
[
221-
'scope' => 'default',
222-
'destination' => 'src/{{ spec.title | caseUcfirst}}/{{ spec.title | caseUcfirst}}Exception.php',
223-
'template' => 'php/src/Exception.php.twig',
221+
'scope' => 'default',
222+
'destination' => 'src/{{ spec.title | caseUcfirst}}/{{ spec.title | caseUcfirst}}Exception.php',
223+
'template' => 'php/src/Exception.php.twig',
224224
],
225225
[
226-
'scope' => 'default',
227-
'destination' => '/src/{{ spec.title | caseUcfirst}}/Service.php',
228-
'template' => 'php/src/Service.php.twig',
226+
'scope' => 'default',
227+
'destination' => '/src/{{ spec.title | caseUcfirst}}/Service.php',
228+
'template' => 'php/src/Service.php.twig',
229229
],
230230
[
231-
'scope' => 'service',
232-
'destination' => '/src/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}.php',
233-
'template' => 'php/src/Services/Service.php.twig',
231+
'scope' => 'service',
232+
'destination' => '/src/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}.php',
233+
'template' => 'php/src/Services/Service.php.twig',
234234
],
235235
[
236-
'scope' => 'service',
237-
'destination' => '/tests/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}Test.php',
238-
'template' => 'php/tests/Services/ServiceTest.php.twig',
236+
'scope' => 'service',
237+
'destination' => '/tests/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}Test.php',
238+
'template' => 'php/tests/Services/ServiceTest.php.twig',
239239
],
240240
[
241-
'scope' => 'enum',
242-
'destination' => '/src/{{ spec.title | caseUcfirst}}/Enums/{{ enum.name | caseUcfirst }}.php',
243-
'template' => 'php/src/Enums/Enum.php.twig',
241+
'scope' => 'enum',
242+
'destination' => '/src/{{ spec.title | caseUcfirst}}/Enums/{{ enum.name | caseUcfirst }}.php',
243+
'template' => 'php/src/Enums/Enum.php.twig',
244244
],
245245
];
246246
}
@@ -258,14 +258,17 @@ public function getTypeName(array $parameter, array $spec = []): string
258258
if (!empty($parameter['enumValues'])) {
259259
return \ucfirst($parameter['name']);
260260
}
261+
262+
261263
return match ($parameter['type']) {
262264
self::TYPE_STRING => 'string',
263265
self::TYPE_BOOLEAN => 'bool',
264266
self::TYPE_NUMBER => 'float',
265267
self::TYPE_INTEGER => 'int',
266268
self::TYPE_ARRAY,
267269
self::TYPE_OBJECT => 'array',
268-
self::TYPE_FILE => 'InputFile',
270+
self::TYPE_FILE,
271+
self::TYPE_PAYLOAD => 'Payload',
269272
default => $parameter['type'],
270273
};
271274
}
@@ -276,9 +279,9 @@ public function getTypeName(array $parameter, array $spec = []): string
276279
*/
277280
public function getParamDefault(array $param): string
278281
{
279-
$type = $param['type'] ?? '';
280-
$default = $param['default'] ?? '';
281-
$required = $param['required'] ?? '';
282+
$type = $param['type'] ?? '';
283+
$default = $param['default'] ?? '';
284+
$required = $param['required'] ?? '';
282285

283286
if ($required) {
284287
return '';
@@ -329,8 +332,8 @@ public function getParamDefault(array $param): string
329332
*/
330333
public function getParamExample(array $param): string
331334
{
332-
$type = $param['type'] ?? '';
333-
$example = $param['example'] ?? '';
335+
$type = $param['type'] ?? '';
336+
$example = $param['example'] ?? '';
334337

335338
$output = '';
336339

@@ -348,8 +351,11 @@ public function getParamExample(array $param): string
348351
case self::TYPE_OBJECT:
349352
$output .= '[]';
350353
break;
354+
case self::TYPE_PAYLOAD:
355+
$output .= "Payload::fromString('<BODY>')";
356+
break;
351357
case self::TYPE_FILE:
352-
$output .= "InputFile::withPath('file.png')";
358+
$output .= "Payload::fromFile('file.png')";
353359
break;
354360
}
355361
} else {
@@ -368,8 +374,11 @@ public function getParamExample(array $param): string
368374
case self::TYPE_STRING:
369375
$output .= "'{$example}'";
370376
break;
377+
case self::TYPE_PAYLOAD:
378+
$output .= "Payload::fromJson([])";
379+
break;
371380
case self::TYPE_FILE:
372-
$output .= "InputFile::withPath('file.png')";
381+
$output .= "Payload::fromFile('file.png')";
373382
break;
374383
}
375384
}

templates/php/base/params.twig

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
{% if not parameter.required and not parameter.nullable %}
55

66
if (!is_null(${{ parameter.name | caseCamel | escapeKeyword }})) {
7-
$apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};
7+
{%~ if param.type == 'payload' %}
8+
$apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }}->toBinary();
9+
{%~ else %}
10+
$apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};
11+
{%~ endif %}
812
}
913
{% else %}
1014
$apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};

templates/php/base/requests/api.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
$apiHeaders,
55
$apiParams{% if method.type == 'webAuth' -%}, 'location'{% endif %}
66

7-
);
7+
);

templates/php/base/requests/file.twig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
fseek($handle, $start);
6666
$chunk = @fread($handle, Client::CHUNK_SIZE);
6767
} else {
68-
$chunk = substr($file->getData(), $start, Client::CHUNK_SIZE);
68+
$chunk = substr(${{parameter.name}}->getData(), $start, Client::CHUNK_SIZE);
6969
}
7070
$apiParams['{{ parameter.name }}'] = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode($chunk), $mimeType, $postedName);
7171
$apiHeaders['content-range'] = 'bytes ' . ($counter * Client::CHUNK_SIZE) . '-' . min(((($counter * Client::CHUNK_SIZE) + Client::CHUNK_SIZE) - 1), $size - 1) . '/' . $size;
@@ -84,7 +84,7 @@
8484
'progress' => min(((($counter * Client::CHUNK_SIZE) + Client::CHUNK_SIZE)), $size) / $size * 100,
8585
'sizeUploaded' => min($counter * Client::CHUNK_SIZE),
8686
'chunksTotal' => $response['chunksTotal'],
87-
'chunksUploaded' => $response['chunksUploaded'],
87+
'chunksUploaded' => $response['chunksUploaded'],
8888
]);
8989
}
9090
}
@@ -93,4 +93,4 @@
9393
}
9494
return $response;
9595
{% endif %}
96-
{% endfor %}
96+
{% endfor %}

templates/php/docs/example.md.twig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?php
22
33
use {{ spec.title | caseUcfirst }}\Client;
4-
{% if method.parameters.all | filter((param) => param.type == 'file') | length > 0 %}
5-
use {{ spec.title | caseUcfirst }}\InputFile;
4+
{% if method.parameters.all | filter((param) => param.type == 'file' or param.type == 'payload') | length > 0 %}
5+
use {{ spec.title | caseUcfirst }}\Payload;
66
{% endif %}
77
use {{ spec.title | caseUcfirst }}\Services\{{ service.name | caseUcfirst }};
88
{% set added = [] %}

templates/php/src/Client.php.twig

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class Client
5151
{
5252
{% for key,header in spec.global.defaultHeaders %}
5353
$this->headers['{{key}}'] = '{{header}}';
54-
{% endfor %}
54+
{% endfor %}
5555
}
5656
5757
{% for header in spec.global.headers %}
@@ -104,7 +104,7 @@ class Client
104104
public function addHeader(string $key, string $value): Client
105105
{
106106
$this->headers[strtolower($key)] = $value;
107-
107+
108108
return $this;
109109
}
110110
@@ -138,6 +138,7 @@ class Client
138138
break;
139139
140140
case 'multipart/form-data':
141+
$headers['accept'] = 'multipart/form-data';
141142
$query = $this->flatten($params);
142143
break;
143144
@@ -189,17 +190,23 @@ class Client
189190
echo 'Warning: ' . $warning . PHP_EOL;
190191
}
191192
}
192-
193+
193194
switch(substr($contentType, 0, strpos($contentType, ';'))) {
194195
case 'application/json':
195196
$responseBody = json_decode($responseBody, true);
196197
break;
197198
}
198-
199+
if (str_contains($contentType, 'multipart/form-data')) {
200+
$matches = [];
201+
preg_match('/(?<boundary>[-]+[\w]+)--/m', $responseBody, $matches);
202+
if (isset($matches['boundary'])) {
203+
$responseBody = self::handleFormData($matches['boundary'], $responseBody);
204+
}
205+
}
199206
if (curl_errno($ch)) {
200207
throw new {{spec.title | caseUcfirst}}Exception(curl_error($ch), $responseStatus, $responseBody['type'] ?? '', $responseBody);
201208
}
202-
209+
203210
curl_close($ch);
204211
205212
if($responseStatus >= 400) {
@@ -240,4 +247,45 @@ class Client
240247
241248
return $output;
242249
}
250+
251+
public static function handleFormData(string $boundary, mixed $responseBody)
252+
{
253+
$parts = explode($boundary, $responseBody);
254+
$data = [];
255+
foreach ($parts as $part) {
256+
$lines = array_values(array_filter(explode("\r\n", $part)));
257+
$matches = [];
258+
$matched = preg_match('/name="?(?<name>\w+)/s', $part, $matches);
259+
if ($matched) {
260+
array_shift($lines);
261+
if(isset($lines[0]) && $lines[0] === 'Content-Type: application/json'){
262+
array_shift($lines);
263+
$json = json_decode(implode($lines), true);
264+
265+
if (count($json) > 0 && isset($json[0]['name']) && isset($json[0]['value'])) {
266+
$json = array_combine(
267+
array_map(fn($header) => $header['name'], $json),
268+
array_map(fn($header) => $header['value'], $json)
269+
);
270+
}
271+
272+
$data[$matches['name']] = $json;
273+
continue;
274+
}
275+
$data[$matches['name']] = implode("\r\n",$lines) ?? '';;
276+
}
277+
}
278+
279+
if(isset($data['responseStatusCode'])) {
280+
$data['responseStatusCode'] = (int) ($data['responseStatusCode'] ?? '');
281+
}
282+
if(isset($data['duration'])) {
283+
$data['duration'] = ((float) ($data['duration'] ?? ''));
284+
}
285+
if(isset($data['responseBody'])) {
286+
$data['responseBody'] = Payload::fromString($data['responseBody'] ?? '');
287+
}
288+
289+
return $data;
290+
}
243291
}

0 commit comments

Comments
 (0)