• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DoclerLabs / api-client-generator / 9254068657

27 May 2024 11:30AM UTC coverage: 86.981% (-1.4%) from 88.428%
9254068657

push

github

web-flow
Merge pull request #112 from DoclerLabs/php81

php 8.1 features

106 of 172 new or added lines in 20 files covered. (61.63%)

4 existing lines in 2 files now uncovered.

2913 of 3349 relevant lines covered (86.98%)

4.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.79
/src/Generator/SchemaGenerator.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace DoclerLabs\ApiClientGenerator\Generator;
6

7
use DateTimeInterface;
8
use DoclerLabs\ApiClientGenerator\Ast\Builder\ParameterBuilder;
9
use DoclerLabs\ApiClientGenerator\Ast\ParameterNode;
10
use DoclerLabs\ApiClientGenerator\Entity\Field;
11
use DoclerLabs\ApiClientGenerator\Entity\FieldType;
12
use DoclerLabs\ApiClientGenerator\Input\Specification;
13
use DoclerLabs\ApiClientGenerator\Output\Php\PhpFileCollection;
14
use JsonSerializable;
15
use PhpParser\Node\Expr\Variable;
16
use PhpParser\Node\Stmt;
17
use PhpParser\Node\Stmt\ClassMethod;
18
use UnexpectedValueException;
19

20
class SchemaGenerator extends MutatorAccessorClassGeneratorAbstract
21
{
22
    public const SUBDIRECTORY = 'Schema/';
23

24
    public const NAMESPACE_SUBPATH = '\\Schema';
25

26
    private const OPTIONAL_CHANGED_FIELDS_PROPERTY_NAME = 'optionalPropertyChanged';
27

28
    public function generate(Specification $specification, PhpFileCollection $fileRegistry): void
29
    {
30
        $compositeFields = $specification->getCompositeFields()->getUniqueByPhpClassName();
8✔
31
        foreach ($compositeFields as $field) {
8✔
32
            if ($field->isObject() && !$field->isFreeFormObject()) {
8✔
33
                $this->generateSchema($field, $fileRegistry);
8✔
34
            }
35
        }
36
    }
8✔
37

38
    protected function generateSchema(Field $root, PhpFileCollection $fileRegistry): void
39
    {
40
        $this->addImport(JsonSerializable::class);
8✔
41

42
        $className = $root->getPhpClassName();
8✔
43

44
        $classBuilder = $this
8✔
45
            ->builder
8✔
46
            ->class($className)
8✔
47
            ->implement('SerializableInterface', 'JsonSerializable')
8✔
48
            ->addStmts($this->generateEnumConsts($root))
8✔
49
            ->addStmts($this->generateProperties($root))
8✔
50
            ->addStmt($this->generateOptionalChangedFieldsProperty($root))
8✔
51
            ->addStmt($this->generateConstructor($root))
8✔
52
            ->addStmts($this->generateSetMethods($root))
8✔
53
            ->addStmts($this->generateHasMethods($root))
8✔
54
            ->addStmts($this->generateGetMethods($root))
8✔
55
            ->addStmt($this->generateToArray($root))
8✔
56
            ->addStmt($this->generateJsonSerialize());
8✔
57

58
        $this->registerFile($fileRegistry, $classBuilder, self::SUBDIRECTORY, self::NAMESPACE_SUBPATH);
8✔
59
    }
8✔
60

61
    private function generateOptionalChangedFieldsProperty(Field $root): ?Stmt
62
    {
63
        $optionalProperties = [];
8✔
64

65
        foreach ($root->getObjectProperties() as $propertyField) {
8✔
66
            if ($propertyField->isOptional()) {
8✔
67
                if ($propertyField->getPhpVariableName() === self::OPTIONAL_CHANGED_FIELDS_PROPERTY_NAME) {
8✔
68
                    throw new UnexpectedValueException('Property "' . self::OPTIONAL_CHANGED_FIELDS_PROPERTY_NAME . '" not supported!');
×
69
                }
70

71
                if ($propertyField->isNullable()) {
8✔
72
                    trigger_error('Property "' . $propertyField->getName() . '" is nullable and optional, that might be a sign of a bad api design', E_USER_WARNING);
8✔
73
                }
74
                $optionalProperties[] = $propertyField;
8✔
75
            }
76
        }
77

78
        if (empty($optionalProperties)) {
8✔
79
            return null;
×
80
        }
81

82
        $propertyArrayValues = [];
8✔
83
        foreach ($optionalProperties as $optionalProperty) {
8✔
84
            $propertyArrayValues[$optionalProperty->getPhpVariableName()] = $this->builder->val(false);
8✔
85
        }
86

87
        return $this->builder->localProperty(
8✔
88
            self::OPTIONAL_CHANGED_FIELDS_PROPERTY_NAME,
8✔
89
            'array',
8✔
90
            'array',
8✔
91
            false,
8✔
92
            $this->builder->array($propertyArrayValues)
8✔
93
        );
94
    }
95

96
    protected function generateEnumConsts(Field $root): array
97
    {
98
        $statements = [];
8✔
99
        foreach ($root->getObjectProperties() as $propertyField) {
8✔
100
            foreach ($this->generateEnumStatements($propertyField) as $statement) {
8✔
101
                $statements[] = $statement;
6✔
102
            }
103
        }
104

105
        return $statements;
8✔
106
    }
107

108
    protected function generateProperties(Field $root): array
109
    {
110
        $statements = [];
8✔
111
        foreach ($root->getObjectProperties() as $propertyField) {
8✔
112
            if ($propertyField->isDate()) {
8✔
113
                $this->addImport(DateTimeInterface::class);
7✔
114
            }
115
            if (
116
                $propertyField->isRequired()
8✔
117
                && $this->phpVersion->isConstructorPropertyPromotionSupported()
8✔
118
            ) {
119
                continue;
2✔
120
            }
121

122
            $statements[] = $this->generateProperty($propertyField);
8✔
123
        }
124

125
        return $statements;
8✔
126
    }
127

128
    protected function generateConstructor(Field $root): ?ClassMethod
129
    {
130
        $params             = [];
8✔
131
        $validations        = [];
8✔
132
        $paramsInit         = [];
8✔
133
        $paramsDoc          = [];
8✔
134
        $thrownExceptionMap = [];
8✔
135

136
        foreach ($root->getObjectProperties() as $propertyField) {
8✔
137
            if ($propertyField->isRequired()) {
8✔
138
                $validationStmts = $this->generateValidationStmts($propertyField);
8✔
139
                array_push($validations, ...$validationStmts);
8✔
140
                if (!empty($validationStmts)) {
8✔
141
                    $thrownExceptionMap['RequestValidationException'] = true;
6✔
142
                }
143
                $params[] = $this->builder
8✔
144
                    ->param($propertyField->getPhpVariableName())
8✔
145
                    ->setType($propertyField->getPhpTypeHint(), $propertyField->isNullable());
8✔
146

147
                $paramsInit[] = $this->builder->assign(
8✔
148
                    $this->builder->localPropertyFetch($propertyField->getPhpVariableName()),
8✔
149
                    $this->builder->var($propertyField->getPhpVariableName())
8✔
150
                );
151

152
                $paramsDoc[] = $this->builder
8✔
153
                    ->param($propertyField->getPhpVariableName())
8✔
154
                    ->setType($propertyField->getPhpTypeHint(), $propertyField->isNullable())
8✔
155
                    ->setDocBlockType($propertyField->getPhpDocType($propertyField->isNullable()))
8✔
156
                    ->getNode();
8✔
157
            }
158
        }
159
        if (empty($params)) {
8✔
160
            return null;
7✔
161
        }
162

163
        if ($this->phpVersion->isConstructorPropertyPromotionSupported()) {
8✔
164
            foreach ($params as $param) {
2✔
165
                $param->makePrivate();
2✔
166
            }
167
        }
168
        if ($this->phpVersion->isReadonlyPropertySupported()) {
8✔
169
            foreach ($params as $param) {
1✔
170
                $param->makeReadonly();
1✔
171
            }
172
        }
173

174
        $params = array_map(
8✔
175
            static fn (ParameterBuilder $param): ParameterNode => $param->getNode(),
8✔
176
            $params
177
        );
178

179
        $constructor = $this
8✔
180
            ->builder
8✔
181
            ->method('__construct')
8✔
182
            ->makePublic()
8✔
183
            ->addParams($params)
8✔
184
            ->addStmts($validations)
8✔
185
            ->composeDocBlock($paramsDoc, '', array_keys($thrownExceptionMap));
8✔
186

187
        if (!$this->phpVersion->isConstructorPropertyPromotionSupported()) {
8✔
188
            $constructor->addStmts($paramsInit);
6✔
189
        }
190

191
        return $constructor->getNode();
8✔
192
    }
193

194
    protected function generateSetMethods(Field $root): array
195
    {
196
        $statements = [];
8✔
197
        foreach ($root->getObjectProperties() as $propertyField) {
8✔
198
            if ($propertyField->isOptional()) {
8✔
199
                $changedFieldSetter = $this->builder->assign(
8✔
200
                    $this->builder->getArrayItem(
8✔
201
                        $this->builder->localPropertyFetch(self::OPTIONAL_CHANGED_FIELDS_PROPERTY_NAME),
8✔
202
                        $this->builder->val($propertyField->getPhpVariableName())
8✔
203
                    ),
204
                    $this->builder->val(true)
8✔
205
                );
206

207
                $statements[] = $this->generateSet($propertyField, [$changedFieldSetter]);
8✔
208
            }
209
        }
210

211
        return $statements;
8✔
212
    }
213

214
    protected function generateGetMethods(Field $root): array
215
    {
216
        $statements = [];
8✔
217
        foreach ($root->getObjectProperties() as $propertyField) {
8✔
218
            $statements[] = $this->generateGet($propertyField);
8✔
219
        }
220

221
        return $statements;
8✔
222
    }
223

224
    protected function generateHasMethods(Field $root): array
225
    {
226
        $statements = [];
8✔
227
        foreach ($root->getObjectProperties() as $propertyField) {
8✔
228
            if ($propertyField->isOptional()) {
8✔
229
                $statements[] = $this->generateHas($propertyField);
8✔
230
            }
231
        }
232

233
        return $statements;
8✔
234
    }
235

236
    protected function generateToArray(Field $root): ClassMethod
237
    {
238
        $statements    = [];
8✔
239
        $arrayVariable = $this->builder->var('fields');
8✔
240
        $initialValue  = $this->builder->val([]);
8✔
241

242
        $statements[] = $this->builder->assign($arrayVariable, $initialValue);
8✔
243
        $statements   = array_merge($statements, $this->collectSerializationFields($root, $arrayVariable));
8✔
244
        $statements[] = $this->builder->return($arrayVariable);
8✔
245

246
        $returnType = FieldType::PHP_TYPE_ARRAY;
8✔
247

248
        return $this
249
            ->builder
8✔
250
            ->method('toArray')
8✔
251
            ->makePublic()
8✔
252
            ->addStmts($statements)
8✔
253
            ->setReturnType($returnType)
8✔
254
            ->composeDocBlock([], $returnType)
8✔
255
            ->getNode();
8✔
256
    }
257

258
    private function generateHas(Field $field): ClassMethod
259
    {
260
        $return = $this->builder->return(
8✔
261
            $this->builder->getArrayItem(
8✔
262
                $this->builder->localPropertyFetch(
8✔
263
                    self::OPTIONAL_CHANGED_FIELDS_PROPERTY_NAME
8✔
264
                ),
265
                $this->builder->val($field->getPhpVariableName())
8✔
266
            )
267
        );
268

269
        return $this->builder
8✔
270
            ->method($this->getHasMethodName($field))
8✔
271
            ->makePublic()
8✔
272
            ->addStmt($return)
8✔
273
            ->setReturnType('bool')
8✔
274
            ->composeDocBlock([], 'bool')
8✔
275
            ->getNode();
8✔
276
    }
277

278
    private function collectSerializationFields(Field $root, Variable $arrayVariable): array
279
    {
280
        $statements = [];
8✔
281
        foreach ($root->getObjectProperties() as $propertyField) {
8✔
282
            $value = $this->builder->localPropertyFetch($propertyField->getPhpVariableName());
8✔
283
            if ($propertyField->isComposite()) {
8✔
284
                $methodCall = $this->builder->methodCall($value, 'toArray');
7✔
285
                if ($propertyField->isNullable()) {
7✔
286
                    if ($this->phpVersion->isNullSafeSupported()) {
6✔
287
                        $value = $this->builder->nullsafeMethodCall($value, 'toArray');
2✔
288
                    } else {
289
                        $value = $this->builder->ternary(
4✔
290
                            $this->builder->notEquals($value, $this->builder->val(null)),
4✔
291
                            $methodCall,
292
                            $this->builder->val(null)
4✔
293
                        );
294
                    }
295
                } else {
296
                    $value = $methodCall;
7✔
297
                }
298
            } elseif ($propertyField->isDate()) {
8✔
299
                $methodCall = $this->builder->methodCall(
7✔
300
                    $value,
301
                    'format',
7✔
302
                    [$this->builder->constFetch('DATE_RFC3339')]
7✔
303
                );
304

305
                if ($propertyField->isNullable()) {
7✔
306
                    if ($this->phpVersion->isNullSafeSupported()) {
6✔
307
                        $value = $this->builder->nullsafeMethodCall(
2✔
308
                            $value,
309
                            'format',
2✔
310
                            [$this->builder->constFetch('DATE_RFC3339')]
2✔
311
                        );
312
                    } else {
313
                        $value = $this->builder->ternary(
4✔
314
                            $this->builder->notEquals($value, $this->builder->val(null)),
4✔
315
                            $methodCall,
316
                            $this->builder->val(null)
4✔
317
                        );
318
                    }
319
                } else {
320
                    $value = $methodCall;
7✔
321
                }
322
            } elseif ($propertyField->isEnum() && $this->phpVersion->isEnumSupported()) {
8✔
323
                $value = $this->builder->propertyFetch($value, 'value');
1✔
324

325
                if ($propertyField->isNullable()) {
1✔
NEW
326
                    $value = $this->builder->nullsafePropertyFetch($value, 'value');
×
327
                }
328
            }
329

330
            $fieldName = $this->builder->val($propertyField->getName());
8✔
331
            if ($root->hasOneOf()) {
8✔
332
                $assignStatement = $this->builder->expr($this->builder->assign($arrayVariable, $value));
×
333
            } else {
334
                $assignStatement = $this->builder->appendToAssociativeArray($arrayVariable, $fieldName, $value);
8✔
335
            }
336

337
            if ($propertyField->isOptional()) {
8✔
338
                $ifCondition = $this->builder->localMethodCall(
8✔
339
                    $this->getHasMethodName($propertyField)
8✔
340
                );
341

342
                $statements[] = $this->builder->if($ifCondition, [$assignStatement]);
8✔
343
            } else {
344
                $statements[] = $assignStatement;
8✔
345
            }
346
        }
347

348
        return $statements;
8✔
349
    }
350
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc