Skip to content

Commit 0f73f28

Browse files
authored
Merge pull request #51 from Yarre/free-form-response-object-support
Free-form response object support
2 parents bfdff2f + 64c30a0 commit 0f73f28

20 files changed

+438
-46
lines changed

CHANGELOG.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## [7.1.0] - 2021-08-30
8+
### Added
9+
- Support for free-form object in response
10+
711
## [7.0.1] - 2021-08-29
812
### Fixed
913
- Added `ext-intl` to composer.json. It was missing since 7.0.0.
@@ -12,10 +16,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1216
### Fixed
1317
- Using `grapheme_strlen` to properly count string lengths.
1418

15-
## [6.1.0] - 2021-08-27
16-
### Added
17-
- Readded enums as consts only
18-
1919
## [6.0.2] - 2021-08-12
2020
### Fixed
2121
- The array of serializers in BodySerializer depends on both requests and responses content type
@@ -197,4 +197,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
197197

198198
## [1.0.0] - 2020-07-09
199199
### Added
200-
- Initial API client generator release
200+
- Initial API client generator release

example/gen/src/Schema/GetInventoryResponseBody.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,25 @@
99
namespace OpenApi\PetStoreClient\Schema;
1010

1111
use JsonSerializable;
12+
use stdClass;
1213

1314
class GetInventoryResponseBody implements SerializableInterface, JsonSerializable
1415
{
15-
public function toArray(): array
16+
private stdClass $data;
17+
18+
public function __construct(array $data)
19+
{
20+
$this->data = (object) $data;
21+
}
22+
23+
public function getData(): stdClass
1624
{
17-
$fields = [];
25+
return $this->data;
26+
}
1827

19-
return $fields;
28+
public function toArray(): array
29+
{
30+
return (array) $this->data;
2031
}
2132

2233
public function jsonSerialize(): array

example/gen/src/Schema/Mapper/GetInventoryResponseBodyMapper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ class GetInventoryResponseBodyMapper implements SchemaMapperInterface
1414
{
1515
public function toSchema(array $payload): GetInventoryResponseBody
1616
{
17-
return new GetInventoryResponseBody();
17+
return new GetInventoryResponseBody($payload);
1818
}
1919
}

src/Ast/Builder/CodeBuilder.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use PhpParser\Node\Expr\BinaryOp\Smaller;
3131
use PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
3232
use PhpParser\Node\Expr\BooleanNot;
33+
use PhpParser\Node\Expr\Cast;
3334
use PhpParser\Node\Expr\Closure;
3435
use PhpParser\Node\Expr\Instanceof_;
3536
use PhpParser\Node\Expr\MethodCall;
@@ -195,6 +196,16 @@ public function return(Expr $expr = null, array $attributes = []): Return_
195196
return new Return_($expr, $attributes);
196197
}
197198

199+
public function castToObject(Expr $expr): Cast\Object_
200+
{
201+
return new Cast\Object_($expr);
202+
}
203+
204+
public function castToArray(Expr $expr): Cast\Array_
205+
{
206+
return new Cast\Array_($expr);
207+
}
208+
198209
public function expr(Expr $expr): Expression
199210
{
200211
return new Expression($expr);

src/Entity/Field.php

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,33 @@
1212
class Field
1313
{
1414
public const FORMAT_DATE = 'date';
15+
1516
public const FORMAT_DATE_TIME = 'date-time';
17+
1618
public const TYPE_MIXED = 'mixed';
1719

20+
private bool $additionalProperties;
21+
1822
private string $name;
23+
1924
private FieldType $type;
25+
2026
private ConstraintCollection $constraints;
27+
2128
private string $referenceName;
29+
2230
private bool $required;
31+
2332
private bool $nullable;
33+
2434
private ?Field $arrayItem = null;
35+
2536
private array $objectProperties = [];
37+
2638
private array $enumValues = [];
39+
2740
private string $format = '';
41+
2842
/** @phpstan-ignore-next-line cannot use strict type before PHP8 with "mixed" pseudo type */
2943
private $default;
3044

@@ -34,14 +48,16 @@ public function __construct(
3448
ConstraintCollection $constraints,
3549
string $referenceName,
3650
bool $required,
37-
bool $nullable
51+
bool $nullable,
52+
bool $additionalProperties
3853
) {
39-
$this->name = $name;
40-
$this->type = $type;
41-
$this->constraints = $constraints;
42-
$this->referenceName = $referenceName;
43-
$this->required = $required;
44-
$this->nullable = $nullable;
54+
$this->name = $name;
55+
$this->type = $type;
56+
$this->constraints = $constraints;
57+
$this->referenceName = $referenceName;
58+
$this->required = $required;
59+
$this->nullable = $nullable;
60+
$this->additionalProperties = $additionalProperties;
4561
}
4662

4763
public function getArrayItem(): Field
@@ -146,6 +162,11 @@ public function isObject(): bool
146162
return $this->type->isObject();
147163
}
148164

165+
public function isFreeFormObject(): bool
166+
{
167+
return $this->isObject() && $this->additionalProperties && count($this->getObjectProperties()) === 0;
168+
}
169+
149170
public function isArray(): bool
150171
{
151172
return $this->type->isArray();
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoclerLabs\ApiClientGenerator\Generator;
6+
7+
use DoclerLabs\ApiClientGenerator\Entity\Constraint\ConstraintCollection;
8+
use DoclerLabs\ApiClientGenerator\Entity\Field;
9+
use DoclerLabs\ApiClientGenerator\Entity\FieldType;
10+
use DoclerLabs\ApiClientGenerator\Input\Specification;
11+
use DoclerLabs\ApiClientGenerator\Output\Php\PhpFileCollection;
12+
use JsonSerializable;
13+
use PhpParser\Node\Stmt\ClassMethod;
14+
use stdClass;
15+
16+
class FreeFormSchemaGenerator extends MutatorAccessorClassGeneratorAbstract
17+
{
18+
private const FREE_FORM_SCHEMA_VARIABLE = 'data';
19+
20+
public function generate(Specification $specification, PhpFileCollection $fileRegistry): void
21+
{
22+
$compositeFields = $specification->getCompositeFields()->getUniqueByPhpClassName();
23+
foreach ($compositeFields as $field) {
24+
if ($field->isFreeFormObject()) {
25+
$this->generateFreeFormSchema($field, $fileRegistry);
26+
}
27+
}
28+
}
29+
30+
private function generateFreeFormSchema(Field $field, PhpFileCollection $fileRegistry): void
31+
{
32+
$this->addImport(JsonSerializable::class);
33+
$this->addImport(stdClass::class);
34+
35+
$className = $field->getPhpClassName();
36+
$freeFormField = $this->generateFreeFormField();
37+
38+
$classBuilder = $this->builder
39+
->class($className)
40+
->implement('SerializableInterface', 'JsonSerializable')
41+
->addStmt($this->generateProperty($freeFormField))
42+
->addStmt($this->generateConstructor())
43+
->addStmt($this->generateGet($freeFormField))
44+
->addStmt($this->generateToArray())
45+
->addStmt($this->generateJsonSerialize());
46+
47+
$this->registerFile($fileRegistry, $classBuilder, SchemaGenerator::SUBDIRECTORY, SchemaGenerator::NAMESPACE_SUBPATH);
48+
}
49+
50+
private function generateConstructor(): ClassMethod
51+
{
52+
$param = $this->builder
53+
->param(self::FREE_FORM_SCHEMA_VARIABLE)
54+
->setType(FieldType::PHP_TYPE_ARRAY)
55+
->getNode();
56+
57+
$paramInit = $this->builder->assign(
58+
$this->builder->localPropertyFetch(self::FREE_FORM_SCHEMA_VARIABLE),
59+
$this->builder->castToObject(
60+
$this->builder->var(self::FREE_FORM_SCHEMA_VARIABLE)
61+
)
62+
);
63+
64+
return $this->builder
65+
->method('__construct')
66+
->makePublic()
67+
->addParam($param)
68+
->addStmt($paramInit)
69+
->getNode();
70+
}
71+
72+
private function generateFreeFormField(): Field
73+
{
74+
return new Field(
75+
self::FREE_FORM_SCHEMA_VARIABLE,
76+
new FieldType(FieldType::PHP_TYPE_OBJECT),
77+
new ConstraintCollection(),
78+
stdClass::class,
79+
true,
80+
false,
81+
true
82+
);
83+
}
84+
85+
private function generateToArray(): ClassMethod
86+
{
87+
$return = $this->builder->return(
88+
$this->builder->castToArray(
89+
$this->builder->localPropertyFetch(self::FREE_FORM_SCHEMA_VARIABLE)
90+
)
91+
);
92+
$returnType = FieldType::PHP_TYPE_ARRAY;
93+
94+
return $this->builder
95+
->method('toArray')
96+
->makePublic()
97+
->addStmt($return)
98+
->setReturnType($returnType)
99+
->composeDocBlock([], $returnType)
100+
->getNode();
101+
}
102+
}

src/Generator/GeneratorAbstract.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
use DoclerLabs\ApiClientGenerator\Ast\Builder\ClassBuilder;
66
use DoclerLabs\ApiClientGenerator\Ast\Builder\CodeBuilder;
7+
use DoclerLabs\ApiClientGenerator\Entity\FieldType;
78
use DoclerLabs\ApiClientGenerator\Entity\ImportCollection;
89
use DoclerLabs\ApiClientGenerator\Input\Specification;
910
use DoclerLabs\ApiClientGenerator\Output\Php\PhpFile;
1011
use DoclerLabs\ApiClientGenerator\Output\Php\PhpFileCollection;
12+
use PhpParser\Node\Stmt\ClassMethod;
1113

1214
abstract class GeneratorAbstract implements GeneratorInterface
1315
{
@@ -63,4 +65,15 @@ protected function resetImports(): void
6365
{
6466
$this->imports = new ImportCollection();
6567
}
68+
69+
protected function generateJsonSerialize(): ClassMethod
70+
{
71+
return $this->builder
72+
->method('jsonSerialize')
73+
->makePublic()
74+
->addStmts([$this->builder->return($this->builder->localMethodCall('toArray'))])
75+
->setReturnType(FieldType::PHP_TYPE_ARRAY)
76+
->composeDocBlock([], FieldType::PHP_TYPE_ARRAY)
77+
->getNode();
78+
}
6679
}

src/Generator/SchemaCollectionGenerator.php

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,6 @@ protected function generateConstructor(Field $item): ClassMethod
8585
->getNode();
8686
}
8787

88-
protected function generateJsonSerialize(): ClassMethod
89-
{
90-
return $this->builder
91-
->method('jsonSerialize')
92-
->makePublic()
93-
->addStmts([$this->builder->return($this->builder->localMethodCall('toArray'))])
94-
->setReturnType(FieldType::PHP_TYPE_ARRAY)
95-
->composeDocBlock([], FieldType::PHP_TYPE_ARRAY)
96-
->getNode();
97-
}
98-
9988
protected function generateToArray(Field $field): ClassMethod
10089
{
10190
$statements = [];

src/Generator/SchemaGenerator.php

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function generate(Specification $specification, PhpFileCollection $fileRe
2121
{
2222
$compositeFields = $specification->getCompositeFields()->getUniqueByPhpClassName();
2323
foreach ($compositeFields as $field) {
24-
if ($field->isObject()) {
24+
if ($field->isObject() && !$field->isFreeFormObject()) {
2525
$this->generateSchema($field, $fileRegistry);
2626
}
2727
}
@@ -135,17 +135,6 @@ protected function generateGetMethods(Field $root): array
135135
return $statements;
136136
}
137137

138-
protected function generateJsonSerialize(): ClassMethod
139-
{
140-
return $this->builder
141-
->method('jsonSerialize')
142-
->makePublic()
143-
->addStmts([$this->builder->return($this->builder->localMethodCall('toArray'))])
144-
->setReturnType(FieldType::PHP_TYPE_ARRAY)
145-
->composeDocBlock([], FieldType::PHP_TYPE_ARRAY)
146-
->getNode();
147-
}
148-
149138
protected function generateToArray(Field $root): ClassMethod
150139
{
151140
$statements = [];

src/Generator/SchemaMapperGenerator.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99
use DoclerLabs\ApiClientGenerator\Naming\SchemaMapperNaming;
1010
use DoclerLabs\ApiClientGenerator\Output\Php\PhpFileCollection;
1111
use PhpParser\Node\Expr\Variable;
12+
use PhpParser\Node\Stmt;
1213
use PhpParser\Node\Stmt\ClassMethod;
1314

1415
class SchemaMapperGenerator extends MutatorAccessorClassGeneratorAbstract
1516
{
1617
public const NAMESPACE_SUBPATH = '\\Schema\\Mapper';
18+
1719
public const SUBDIRECTORY = 'Schema/Mapper/';
20+
1821
private array $mapMethodThrownExceptions;
1922

2023
public function generate(Specification $specification, PhpFileCollection $fileRegistry): void
@@ -132,7 +135,6 @@ protected function generateConstructor(Field $root): ?ClassMethod
132135
protected function generateMap(Field $root): ClassMethod
133136
{
134137
$statements = [];
135-
$returnObj = null;
136138
$payloadParam = $this->builder
137139
->param('payload')
138140
->setType('array')
@@ -148,7 +150,9 @@ protected function generateMap(Field $root): ClassMethod
148150
)
149151
);
150152

151-
if ($root->isObject()) {
153+
if ($root->isFreeFormObject()) {
154+
$statements[] = $this->generateMapStatementForFreeFormObject($root, $payloadVariable);
155+
} elseif ($root->isObject()) {
152156
$statements = array_merge(
153157
$statements,
154158
$this->generateMapStatementsForObject($root, $payloadVariable)
@@ -202,6 +206,13 @@ protected function generateMapStatementsForArrayOfObjects(
202206
return $statements;
203207
}
204208

209+
protected function generateMapStatementForFreeFormObject(Field $root, Variable $payloadVariable): Stmt
210+
{
211+
$schemaInit = $this->builder->new($root->getPhpClassName(), [$payloadVariable]);
212+
213+
return $this->builder->return($schemaInit);
214+
}
215+
205216
protected function generateMapStatementsForObject(Field $root, Variable $payloadVariable): array
206217
{
207218
$statements = [];

0 commit comments

Comments
 (0)