Skip to content

feat: php multiparts #940

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 80 additions & 71 deletions src/SDK/Language/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,110 +137,110 @@ public function getFiles(): array
{
return [
[
'scope' => 'default',
'destination' => 'README.md',
'template' => 'php/README.md.twig',
'scope' => 'default',
'destination' => 'README.md',
'template' => 'php/README.md.twig',
//'block' => 'default',
],
[
'scope' => 'default',
'destination' => 'CHANGELOG.md',
'template' => 'php/CHANGELOG.md.twig',
'scope' => 'default',
'destination' => 'CHANGELOG.md',
'template' => 'php/CHANGELOG.md.twig',
],
[
'scope' => 'default',
'destination' => 'LICENSE',
'template' => 'php/LICENSE.twig',
'scope' => 'default',
'destination' => 'LICENSE',
'template' => 'php/LICENSE.twig',
],
[
'scope' => 'default',
'destination' => 'composer.json',
'template' => 'php/composer.json.twig',
'scope' => 'default',
'destination' => 'composer.json',
'template' => 'php/composer.json.twig',
],
[
'scope' => 'service',
'destination' => 'docs/{{service.name | caseLower}}.md',
'template' => 'php/docs/service.md.twig',
'scope' => 'service',
'destination' => 'docs/{{service.name | caseLower}}.md',
'template' => 'php/docs/service.md.twig',
],
[
'scope' => 'method',
'destination' => 'docs/examples/{{service.name | caseLower}}/{{method.name | caseDash}}.md',
'template' => 'php/docs/example.md.twig',
'scope' => 'method',
'destination' => 'docs/examples/{{service.name | caseLower}}/{{method.name | caseDash}}.md',
'template' => 'php/docs/example.md.twig',
],
[
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/Client.php',
'template' => 'php/src/Client.php.twig',
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/Client.php',
'template' => 'php/src/Client.php.twig',
],
[
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/Permission.php',
'template' => 'php/src/Permission.php.twig',
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/Permission.php',
'template' => 'php/src/Permission.php.twig',
],
[
'scope' => 'default',
'destination' => 'tests/{{ spec.title | caseUcfirst}}/PermissionTest.php',
'template' => 'php/tests/PermissionTest.php.twig',
'scope' => 'default',
'destination' => 'tests/{{ spec.title | caseUcfirst}}/PermissionTest.php',
'template' => 'php/tests/PermissionTest.php.twig',
],
[
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/Role.php',
'template' => 'php/src/Role.php.twig',
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/Role.php',
'template' => 'php/src/Role.php.twig',
],
[
'scope' => 'default',
'destination' => 'tests/{{ spec.title | caseUcfirst}}/RoleTest.php',
'template' => 'php/tests/RoleTest.php.twig',
'scope' => 'default',
'destination' => 'tests/{{ spec.title | caseUcfirst}}/RoleTest.php',
'template' => 'php/tests/RoleTest.php.twig',
],
[
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/ID.php',
'template' => 'php/src/ID.php.twig',
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/ID.php',
'template' => 'php/src/ID.php.twig',
],
[
'scope' => 'default',
'destination' => 'tests/{{ spec.title | caseUcfirst}}/IDTest.php',
'template' => 'php/tests/IDTest.php.twig',
'scope' => 'default',
'destination' => 'tests/{{ spec.title | caseUcfirst}}/IDTest.php',
'template' => 'php/tests/IDTest.php.twig',
],
[
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/Query.php',
'template' => 'php/src/Query.php.twig',
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/Query.php',
'template' => 'php/src/Query.php.twig',
],
[
'scope' => 'default',
'destination' => 'tests/{{ spec.title | caseUcfirst}}/QueryTest.php',
'template' => 'php/tests/QueryTest.php.twig',
'scope' => 'default',
'destination' => 'tests/{{ spec.title | caseUcfirst}}/QueryTest.php',
'template' => 'php/tests/QueryTest.php.twig',
],
[
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/InputFile.php',
'template' => 'php/src/InputFile.php.twig',
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/Payload.php',
'template' => 'php/src/Payload.php.twig',
],
[
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/{{ spec.title | caseUcfirst}}Exception.php',
'template' => 'php/src/Exception.php.twig',
'scope' => 'default',
'destination' => 'src/{{ spec.title | caseUcfirst}}/{{ spec.title | caseUcfirst}}Exception.php',
'template' => 'php/src/Exception.php.twig',
],
[
'scope' => 'default',
'destination' => '/src/{{ spec.title | caseUcfirst}}/Service.php',
'template' => 'php/src/Service.php.twig',
'scope' => 'default',
'destination' => '/src/{{ spec.title | caseUcfirst}}/Service.php',
'template' => 'php/src/Service.php.twig',
],
[
'scope' => 'service',
'destination' => '/src/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}.php',
'template' => 'php/src/Services/Service.php.twig',
'scope' => 'service',
'destination' => '/src/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}.php',
'template' => 'php/src/Services/Service.php.twig',
],
[
'scope' => 'service',
'destination' => '/tests/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}Test.php',
'template' => 'php/tests/Services/ServiceTest.php.twig',
'scope' => 'service',
'destination' => '/tests/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}Test.php',
'template' => 'php/tests/Services/ServiceTest.php.twig',
],
[
'scope' => 'enum',
'destination' => '/src/{{ spec.title | caseUcfirst}}/Enums/{{ enum.name | caseUcfirst }}.php',
'template' => 'php/src/Enums/Enum.php.twig',
'scope' => 'enum',
'destination' => '/src/{{ spec.title | caseUcfirst}}/Enums/{{ enum.name | caseUcfirst }}.php',
'template' => 'php/src/Enums/Enum.php.twig',
],
];
}
Expand All @@ -258,14 +258,17 @@ public function getTypeName(array $parameter, array $spec = []): string
if (!empty($parameter['enumValues'])) {
return \ucfirst($parameter['name']);
}


return match ($parameter['type']) {
self::TYPE_STRING => 'string',
self::TYPE_BOOLEAN => 'bool',
self::TYPE_NUMBER => 'float',
self::TYPE_INTEGER => 'int',
self::TYPE_ARRAY,
self::TYPE_OBJECT => 'array',
self::TYPE_FILE => 'InputFile',
self::TYPE_FILE,
self::TYPE_PAYLOAD => 'Payload',
default => $parameter['type'],
};
}
Expand All @@ -276,9 +279,9 @@ public function getTypeName(array $parameter, array $spec = []): string
*/
public function getParamDefault(array $param): string
{
$type = $param['type'] ?? '';
$default = $param['default'] ?? '';
$required = $param['required'] ?? '';
$type = $param['type'] ?? '';
$default = $param['default'] ?? '';
$required = $param['required'] ?? '';

if ($required) {
return '';
Expand Down Expand Up @@ -329,8 +332,8 @@ public function getParamDefault(array $param): string
*/
public function getParamExample(array $param): string
{
$type = $param['type'] ?? '';
$example = $param['example'] ?? '';
$type = $param['type'] ?? '';
$example = $param['example'] ?? '';

$output = '';

Expand All @@ -348,8 +351,11 @@ public function getParamExample(array $param): string
case self::TYPE_OBJECT:
$output .= '[]';
break;
case self::TYPE_PAYLOAD:
$output .= "Payload::fromString('<BODY>')";
break;
case self::TYPE_FILE:
$output .= "InputFile::withPath('file.png')";
$output .= "Payload::fromFile('file.png')";
break;
}
} else {
Expand All @@ -368,8 +374,11 @@ public function getParamExample(array $param): string
case self::TYPE_STRING:
$output .= "'{$example}'";
break;
case self::TYPE_PAYLOAD:
$output .= "Payload::fromJson([])";
break;
case self::TYPE_FILE:
$output .= "InputFile::withPath('file.png')";
$output .= "Payload::fromFile('file.png')";
break;
}
}
Expand Down
6 changes: 5 additions & 1 deletion templates/php/base/params.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
{% if not parameter.required and not parameter.nullable %}

if (!is_null(${{ parameter.name | caseCamel | escapeKeyword }})) {
$apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};
{%~ if param.type == 'payload' %}
$apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }}->toBinary();
{%~ else %}
$apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};
{%~ endif %}
}
{% else %}
$apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }};
Expand Down
2 changes: 1 addition & 1 deletion templates/php/base/requests/api.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
$apiHeaders,
$apiParams{% if method.type == 'webAuth' -%}, 'location'{% endif %}

);
);
6 changes: 3 additions & 3 deletions templates/php/base/requests/file.twig
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
fseek($handle, $start);
$chunk = @fread($handle, Client::CHUNK_SIZE);
} else {
$chunk = substr($file->getData(), $start, Client::CHUNK_SIZE);
$chunk = substr(${{parameter.name}}->getData(), $start, Client::CHUNK_SIZE);
}
$apiParams['{{ parameter.name }}'] = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode($chunk), $mimeType, $postedName);
$apiHeaders['content-range'] = 'bytes ' . ($counter * Client::CHUNK_SIZE) . '-' . min(((($counter * Client::CHUNK_SIZE) + Client::CHUNK_SIZE) - 1), $size - 1) . '/' . $size;
Expand All @@ -84,7 +84,7 @@
'progress' => min(((($counter * Client::CHUNK_SIZE) + Client::CHUNK_SIZE)), $size) / $size * 100,
'sizeUploaded' => min($counter * Client::CHUNK_SIZE),
'chunksTotal' => $response['chunksTotal'],
'chunksUploaded' => $response['chunksUploaded'],
'chunksUploaded' => $response['chunksUploaded'],
]);
}
}
Expand All @@ -93,4 +93,4 @@
}
return $response;
{% endif %}
{% endfor %}
{% endfor %}
4 changes: 2 additions & 2 deletions templates/php/docs/example.md.twig
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php

use {{ spec.title | caseUcfirst }}\Client;
{% if method.parameters.all | filter((param) => param.type == 'file') | length > 0 %}
use {{ spec.title | caseUcfirst }}\InputFile;
{% if method.parameters.all | filter((param) => param.type == 'file' or param.type == 'payload') | length > 0 %}
use {{ spec.title | caseUcfirst }}\Payload;
{% endif %}
use {{ spec.title | caseUcfirst }}\Services\{{ service.name | caseUcfirst }};
{% set added = [] %}
Expand Down
58 changes: 53 additions & 5 deletions templates/php/src/Client.php.twig
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Client
{
{% for key,header in spec.global.defaultHeaders %}
$this->headers['{{key}}'] = '{{header}}';
{% endfor %}
{% endfor %}
}

{% for header in spec.global.headers %}
Expand Down Expand Up @@ -104,7 +104,7 @@ class Client
public function addHeader(string $key, string $value): Client
{
$this->headers[strtolower($key)] = $value;

return $this;
}

Expand Down Expand Up @@ -138,6 +138,7 @@ class Client
break;

case 'multipart/form-data':
$headers['accept'] = 'multipart/form-data';
$query = $this->flatten($params);
break;

Expand Down Expand Up @@ -189,17 +190,23 @@ class Client
echo 'Warning: ' . $warning . PHP_EOL;
}
}

switch(substr($contentType, 0, strpos($contentType, ';'))) {
case 'application/json':
$responseBody = json_decode($responseBody, true);
break;
}

if (str_contains($contentType, 'multipart/form-data')) {
$matches = [];
preg_match('/(?<boundary>[-]+[\w]+)--/m', $responseBody, $matches);
if (isset($matches['boundary'])) {
$responseBody = self::handleFormData($matches['boundary'], $responseBody);
}
}
if (curl_errno($ch)) {
throw new {{spec.title | caseUcfirst}}Exception(curl_error($ch), $responseStatus, $responseBody['type'] ?? '', $responseBody);
}

curl_close($ch);

if($responseStatus >= 400) {
Expand Down Expand Up @@ -240,4 +247,45 @@ class Client

return $output;
}

public static function handleFormData(string $boundary, mixed $responseBody)
{
$parts = explode($boundary, $responseBody);
$data = [];
foreach ($parts as $part) {
$lines = array_values(array_filter(explode("\r\n", $part)));
$matches = [];
$matched = preg_match('/name="?(?<name>\w+)/s', $part, $matches);
if ($matched) {
array_shift($lines);
if(isset($lines[0]) && $lines[0] === 'Content-Type: application/json'){
array_shift($lines);
$json = json_decode(implode($lines), true);

if (count($json) > 0 && isset($json[0]['name']) && isset($json[0]['value'])) {
$json = array_combine(
array_map(fn($header) => $header['name'], $json),
array_map(fn($header) => $header['value'], $json)
);
}

$data[$matches['name']] = $json;
continue;
}
$data[$matches['name']] = implode("\r\n",$lines) ?? '';;
}
}

if(isset($data['responseStatusCode'])) {
$data['responseStatusCode'] = (int) ($data['responseStatusCode'] ?? '');
}
if(isset($data['duration'])) {
$data['duration'] = ((float) ($data['duration'] ?? ''));
}
if(isset($data['responseBody'])) {
$data['responseBody'] = Payload::fromString($data['responseBody'] ?? '');
}

return $data;
}
}
Loading
Loading