Passed
Push — feature/issue-299 ( cbe70c )
by Mikaël
62:52 queued 12:50
created

Struct::getPropertyAnnotationBlock()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 14
rs 9.9666
1
<?php
2
3
declare(strict_types=1);
4
5
namespace WsdlToPhp\PackageGenerator\File;
6
7
use WsdlToPhp\PackageGenerator\Container\Model\StructAttribute as StructAttributeContainer;
8
use WsdlToPhp\PackageGenerator\Container\PhpElement\Constant as ConstantContainer;
9
use WsdlToPhp\PackageGenerator\Container\PhpElement\Property as PropertyContainer;
10
use WsdlToPhp\PackageGenerator\File\Element\PhpFunctionParameter;
11
use WsdlToPhp\PackageGenerator\File\Validation\ArrayRule;
12
use WsdlToPhp\PackageGenerator\File\Validation\ChoiceRule;
13
use WsdlToPhp\PackageGenerator\File\Validation\LengthRule;
14
use WsdlToPhp\PackageGenerator\File\Validation\MaxLengthRule;
15
use WsdlToPhp\PackageGenerator\File\Validation\MinLengthRule;
16
use WsdlToPhp\PackageGenerator\File\Validation\PatternRule;
17
use WsdlToPhp\PackageGenerator\File\Validation\Rules;
18
use WsdlToPhp\PackageGenerator\File\Validation\UnionRule;
19
use WsdlToPhp\PackageGenerator\Model\AbstractModel;
20
use WsdlToPhp\PackageGenerator\Model\Struct as StructModel;
21
use WsdlToPhp\PackageGenerator\Model\StructAttribute as StructAttributeModel;
22
use WsdlToPhp\PhpGenerator\Element\AccessRestrictedElementInterface;
23
use WsdlToPhp\PhpGenerator\Element\AssignedValueElementInterface;
24
use WsdlToPhp\PhpGenerator\Element\PhpAnnotation;
25
use WsdlToPhp\PhpGenerator\Element\PhpAnnotationBlock;
26
use WsdlToPhp\PhpGenerator\Element\PhpConstant;
27
use WsdlToPhp\PhpGenerator\Element\PhpMethod;
28
use WsdlToPhp\PhpGenerator\Element\PhpProperty;
29
30
class Struct extends AbstractModelFile
31
{
32
    public function setModel(AbstractModel $model): self
33
    {
34
        if (!$model instanceof StructModel) {
35
            throw new \InvalidArgumentException('Model must be an instance of a Struct', __LINE__);
36
        }
37
38
        return parent::setModel($model);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::setModel($model) returns the type WsdlToPhp\PackageGenerator\File\AbstractModelFile which includes types incompatible with the type-hinted return WsdlToPhp\PackageGenerator\File\Struct.
Loading history...
39
    }
40
41
    public function getModel(): ?StructModel
42
    {
43
        return parent::getModel();
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::getModel() could return the type WsdlToPhp\PackageGenerator\Model\AbstractModel which includes types incompatible with the type-hinted return WsdlToPhp\PackageGenerator\Model\Struct|null. Consider adding an additional type-check to rule them out.
Loading history...
44
    }
45
46
    protected function addClassElement(): AbstractModelFile
47
    {
48
        $this->getFile()->addString('#[\AllowDynamicProperties]');
49
50
        return parent::addClassElement();
51
    }
52
53
    protected function defineUseStatements(): self
54
    {
55
        if ($this->getGenerator()->getOptionValidation()) {
56
            $this->getFile()->addUse(\InvalidArgumentException::class);
57
        }
58
59
        return parent::defineUseStatements();
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::defineUseStatements() returns the type WsdlToPhp\PackageGenerator\File\AbstractModelFile which includes types incompatible with the type-hinted return WsdlToPhp\PackageGenerator\File\Struct.
Loading history...
60
    }
61
62
    protected function fillClassConstants(ConstantContainer $constants): void
63
    {
64
    }
65
66
    protected function getConstantAnnotationBlock(PhpConstant $constant): ?PhpAnnotationBlock
67
    {
68
        return null;
69
    }
70
71
    protected function getModelAttributes(): StructAttributeContainer
72
    {
73
        return $this->getModel()->getProperAttributes(true);
74
    }
75
76
    protected function fillClassProperties(PropertyContainer $properties): void
77
    {
78
        /** @var StructAttributeModel $attribute */
79
        foreach ($this->getModelAttributes() as $attribute) {
80
            switch (true) {
81
                case $attribute->isXml():
82
                    $type = null;
83
84
                    break;
85
86
                default:
87
                    $type = (($attribute->isRequired() && !$attribute->isNullable()) ? '' : '?').$this->getStructAttributeTypeAsPhpType($attribute);
88
89
                    break;
90
            }
91
92
            $properties->add(
93
                new PhpProperty(
94
                    $attribute->getCleanName(),
95
                    $attribute->isRequired() ? AssignedValueElementInterface::NO_VALUE : null,
96
                    $this->getGenerator()->getOptionValidation() ? AccessRestrictedElementInterface::ACCESS_PROTECTED : AccessRestrictedElementInterface::ACCESS_PUBLIC,
97
                    $type
98
                )
99
            );
100
        }
101
    }
102
103
    protected function getPropertyAnnotationBlock(PhpProperty $property): ?PhpAnnotationBlock
104
    {
105
        $annotationBlock = new PhpAnnotationBlock();
106
        $annotationBlock->addChild(sprintf('The %s', $property->getName()));
107
        $attribute = $this->getModel()->getAttribute($property->getName());
108
        if (!$attribute instanceof StructAttributeModel) {
109
            $attribute = $this->getModel()->getAttributeByCleanName($property->getName());
110
        }
111
        if ($attribute instanceof StructAttributeModel) {
112
            $this->defineModelAnnotationsFromWsdl($annotationBlock, $attribute);
113
            $annotationBlock->addChild(new PhpAnnotation(self::ANNOTATION_VAR, $this->getStructAttributeTypeGetAnnotation($attribute)));
114
        }
115
116
        return $annotationBlock;
117
    }
118
119
    protected function fillClassMethods(): void
120
    {
121
        $this
122
            ->addStructMethodConstruct()
123
            ->addStructMethodsSetAndGet()
124
        ;
125
    }
126
127
    protected function addStructMethodConstruct(): self
128
    {
129
        if (0 < count($parameters = $this->getStructMethodParametersValues())) {
130
            $method = new PhpMethod(self::METHOD_CONSTRUCT, $parameters);
131
            $this->addStructMethodConstructBody($method);
132
            $this->methods->add($method);
133
        }
134
135
        return $this;
136
    }
137
138
    protected function addStructMethodConstructBody(PhpMethod $method): self
139
    {
140
        $count = $this->getModelAttributes()->count();
141
        foreach ($this->getModelAttributes() as $index => $attribute) {
142
            if (0 === $index) {
143
                $method->addChild('$this');
144
            }
145
            $this->addStructMethodConstructBodyForAttribute($method, $attribute, $count - 1 === $index);
146
        }
147
148
        return $this;
149
    }
150
151
    protected function addStructMethodConstructBodyForAttribute(PhpMethod $method, StructAttributeModel $attribute, bool $isLast): self
152
    {
153
        $uniqueString = $attribute->getUniqueString($attribute->getCleanName(), 'method');
154
        $method->addChild($method->getIndentedString(sprintf('->%s($%s)%s', $attribute->getSetterName(), lcfirst($uniqueString), $isLast ? ';' : ''), 1));
155
156
        return $this;
157
    }
158
159
    protected function getStructMethodParametersValues(): array
160
    {
161
        $parametersValues = [];
162
        foreach ($this->getModelAttributes() as $attribute) {
163
            $parametersValues[] = $this->getStructMethodParameter($attribute);
164
        }
165
166
        return $parametersValues;
167
    }
168
169
    protected function getStructMethodParameter(StructAttributeModel $attribute): PhpFunctionParameter
170
    {
171
        switch (true) {
172
            case $attribute->isXml():
173
            case $attribute->isList():
174
                $type = null;
175
176
                break;
177
178
            default:
179
                $type = (($attribute->isRequired() && !$attribute->isNullable()) ? '' : '?').$this->getStructAttributeTypeAsPhpType($attribute);
180
181
                break;
182
        }
183
184
        try {
185
            $defaultValue = $attribute->getDefaultValue();
186
187
            return new PhpFunctionParameter(
188
                lcfirst($attribute->getUniqueString($attribute->getCleanName(), 'method')),
189
                $attribute->isRequired() && !$attribute->isAChoice() ? AssignedValueElementInterface::NO_VALUE : (str_contains($type ?? '', '?') ? $defaultValue ?? null : $defaultValue),
190
                $type,
191
                $attribute
192
            );
193
        } catch (\InvalidArgumentException $exception) {
194
            throw new \InvalidArgumentException(sprintf('Unable to create function parameter for struct "%s" with type "%s" for attribute "%s"', $this->getModel()->getName(), var_export($this->getStructAttributeTypeAsPhpType($attribute), true), $attribute->getName()), __LINE__, $exception);
195
        }
196
    }
197
198
    protected function addStructMethodsSetAndGet(): self
199
    {
200
        foreach ($this->getModelAttributes() as $attribute) {
201
            $this
202
                ->addStructMethodGet($attribute)
203
                ->addStructMethodSet($attribute)
204
                ->addStructMethodAddTo($attribute)
205
            ;
206
        }
207
208
        return $this;
209
    }
210
211
    protected function addStructMethodAddTo(StructAttributeModel $attribute): self
212
    {
213
        if ($attribute->isArray()) {
214
            $method = new PhpMethod(sprintf('addTo%s', ucfirst($attribute->getCleanName())), [
215
                new PhpFunctionParameter(
216
                    'item',
217
                    AssignedValueElementInterface::NO_VALUE,
218
                    $this->getStructAttributeTypeAsPhpType($attribute, false),
219
                    $attribute
220
                ),
221
            ], self::TYPE_SELF);
222
            $this->addStructMethodAddToBody($method, $attribute);
223
            $this->methods->add($method);
224
        }
225
226
        return $this;
227
    }
228
229
    protected function addStructMethodAddToBody(PhpMethod $method, StructAttributeModel $attribute): self
230
    {
231
        $this->applyRules($method, $attribute, 'item', true);
232
233
        if ($attribute->nameIsClean()) {
234
            $assignment = sprintf('$this->%s[] = $item;', $attribute->getCleanName());
235
        } else {
236
            $assignment = sprintf('$this->%s[] = $this->{\'%s\'}[] = $item;', $attribute->getCleanName(), addslashes($attribute->getName()));
237
        }
238
239
        $method
240
            ->addChild($assignment)
241
            ->addChild('')
242
            ->addChild('return $this;')
243
        ;
244
245
        return $this;
246
    }
247
248
    protected function addStructMethodSet(StructAttributeModel $attribute): self
249
    {
250
        $method = new PhpMethod($attribute->getSetterName(), [
251
            $this->getStructMethodParameter($attribute),
252
        ], self::TYPE_SELF);
253
        $this->addStructMethodSetBody($method, $attribute);
254
        $this->methods->add($method);
255
256
        return $this;
257
    }
258
259
    protected function addStructMethodSetBody(PhpMethod $method, StructAttributeModel $attribute): self
260
    {
261
        $parameters = $method->getParameters();
262
        $parameter = array_shift($parameters);
263
        $parameterName = is_string($parameter) ? $parameter : $parameter->getName();
264
265
        return $this
266
            ->applyRules($method, $attribute, $parameterName)
267
            ->addStructMethodSetBodyAssignment($method, $attribute, $parameterName)
268
            ->addStructMethodSetBodyReturn($method)
269
        ;
270
    }
271
272
    protected function addStructMethodSetBodyAssignment(PhpMethod $method, StructAttributeModel $attribute, string $parameterName): self
273
    {
274
        if ($attribute->getRemovableFromRequest() || $attribute->isAChoice()) {
275
            $method
276
                ->addChild(sprintf('if (is_null($%1$s) || (is_array($%1$s) && empty($%1$s))) {', $parameterName))
277
                ->addChild($method->getIndentedString(sprintf('unset($this->%1$s%2$s);', $attribute->getCleanName(), $attribute->nameIsClean() ? '' : sprintf(', $this->{\'%s\'}', addslashes($attribute->getName()))), 1))
278
                ->addChild('} else {')
279
                ->addChild($method->getIndentedString($this->getStructMethodSetBodyAssignment($attribute, $parameterName), 1))
280
                ->addChild('}')
281
            ;
282
        } else {
283
            $method->addChild($this->getStructMethodSetBodyAssignment($attribute, $parameterName));
284
        }
285
286
        return $this;
287
    }
288
289
    protected function addStructMethodSetBodyReturn(PhpMethod $method): self
290
    {
291
        $method
292
            ->addChild('')
293
            ->addChild('return $this;')
294
        ;
295
296
        return $this;
297
    }
298
299
    protected function getStructMethodSetBodyAssignment(StructAttributeModel $attribute, string $parameterName): string
300
    {
301
        $prefix = '$';
302
        if ($attribute->isList()) {
303
            $prefix = '';
304
            $parameterName = sprintf('is_array($%1$s) ? implode(\' \', $%1$s) : $%1$s', $parameterName);
305
        } elseif ($attribute->isXml()) {
306
            $prefix = '';
307
            $parameterName = sprintf('($%1$s instanceof \DOMDocument) ? $%1$s->saveXML($%1$s->hasChildNodes() ? $%1$s->childNodes->item(0) : null) : $%1$s', $parameterName);
308
        }
309
310
        if ($attribute->nameIsClean()) {
311
            $assignment = sprintf('$this->%s = %s%s;', $attribute->getName(), $prefix, $parameterName);
312
        } else {
313
            $assignment = sprintf('$this->%s = $this->{\'%s\'} = %s%s;', $attribute->getCleanName(), addslashes($attribute->getName()), $prefix, $parameterName);
314
        }
315
316
        return $assignment;
317
    }
318
319
    protected function addStructMethodGetBody(PhpMethod $method, StructAttributeModel $attribute, string $thisAccess): self
320
    {
321
        return $this->addStructMethodGetBodyReturn($method, $attribute, $thisAccess);
322
    }
323
324
    protected function addStructMethodGetBodyReturn(PhpMethod $method, StructAttributeModel $attribute, string $thisAccess): self
325
    {
326
        $return = sprintf('return $this->%s;', $thisAccess);
327
        if ($attribute->isXml()) {
328
            $method
329
                ->addChild('$domDocument = null;')
330
                ->addChild(sprintf('if (!empty($this->%1$s) && $asDomDocument) {', $thisAccess))
331
                ->addChild($method->getIndentedString('$domDocument = new \DOMDocument(\'1.0\', \'UTF-8\');', 1))
332
                ->addChild($method->getIndentedString(sprintf('$domDocument->loadXML($this->%s);', $thisAccess), 1))
333
                ->addChild('}')
334
            ;
335
            if ($attribute->getRemovableFromRequest() || $attribute->isAChoice()) {
336
                $return = sprintf('return $asDomDocument ? $domDocument : (isset($this->%1$s) ? $this->%1$s : null);', $thisAccess);
337
            } else {
338
                $return = sprintf('return $asDomDocument ? $domDocument : $this->%1$s;', $thisAccess);
339
            }
340
        } elseif ($attribute->getRemovableFromRequest() || $attribute->isAChoice()) {
341
            $return = sprintf('return $this->%s ?? null;', $thisAccess);
342
        }
343
        $method->addChild($return);
344
345
        return $this;
346
    }
347
348
    protected function addStructMethodGet(StructAttributeModel $attribute): self
349
    {
350
        switch (true) {
351
            // it can either be a string, a DOMDocument or null...
352
            case $attribute->isXml():
353
                $returnType = '';
354
355
                break;
356
357
            default:
358
                $returnType = (!$attribute->getRemovableFromRequest() && !$attribute->isAChoice() && $attribute->isRequired() ? '' : '?').$this->getStructAttributeTypeAsPhpType($attribute);
359
360
                break;
361
        }
362
363
        $method = new PhpMethod(
364
            $attribute->getGetterName(),
365
            $this->getStructMethodGetParameters($attribute),
366
            $returnType
367
        );
368
        if ($attribute->nameIsClean()) {
369
            $thisAccess = sprintf('%s', $attribute->getName());
370
        } else {
371
            $thisAccess = sprintf('{\'%s\'}', addslashes($attribute->getName()));
372
        }
373
        $this->addStructMethodGetBody($method, $attribute, $thisAccess);
374
        $this->methods->add($method);
375
376
        return $this;
377
    }
378
379
    protected function getStructMethodGetParameters(StructAttributeModel $attribute): array
380
    {
381
        $parameters = [];
382
        if ($attribute->isXml()) {
383
            $parameters[] = new PhpFunctionParameter('asDomDocument', false, self::TYPE_BOOL, $attribute);
384
        }
385
386
        return $parameters;
387
    }
388
389
    protected function getMethodAnnotationBlock(PhpMethod $method): ?PhpAnnotationBlock
390
    {
391
        return $this->getStructMethodAnnotationBlock($method);
392
    }
393
394
    protected function getStructMethodAnnotationBlock(PhpMethod $method): ?PhpAnnotationBlock
395
    {
396
        $annotationBlock = null;
397
        $matches = [];
398
399
        switch ($method->getName()) {
400
            case self::METHOD_CONSTRUCT:
401
                $annotationBlock = $this->getStructMethodConstructAnnotationBlock();
402
403
                break;
404
405
            case 0 === mb_strpos($method->getName(), 'get'):
406
            case 0 === mb_strpos($method->getName(), 'set'):
407
                $annotationBlock = $this->getStructMethodsSetAndGetAnnotationBlock($method);
408
409
                break;
410
411
            case 0 === mb_strpos($method->getName(), 'addTo'):
412
                $annotationBlock = $this->getStructMethodsAddToAnnotationBlock($method);
413
414
                break;
415
416
            case 1 === preg_match('/validate(.+)For(.+)ConstraintFrom(.+)/', $method->getName(), $matches):
417
                $annotationBlock = $this->getStructMethodsValidateMethodAnnotationBlock($matches[1], $matches[2], $matches[3]);
418
419
                break;
420
        }
421
422
        return $annotationBlock;
423
    }
424
425
    protected function getStructMethodConstructAnnotationBlock(): PhpAnnotationBlock
426
    {
427
        $annotationBlock = new PhpAnnotationBlock([
428
            sprintf('Constructor method for %s', $this->getModel()->getName()),
429
        ]);
430
        $this->addStructPropertiesToAnnotationBlock($annotationBlock);
431
432
        return $annotationBlock;
433
    }
434
435
    protected function getStructMethodsSetAndGetAnnotationBlock(PhpMethod $method): PhpAnnotationBlock
436
    {
437
        $parameters = $method->getParameters();
438
        $setOrGet = mb_strtolower(mb_substr($method->getName(), 0, 3));
439
        $parameter = array_shift($parameters);
440
        // Only set parameter must be based on a potential PhpFunctionParameter
441
        if ($parameter instanceof PhpFunctionParameter && 'set' === $setOrGet) {
442
            $parameterName = ucfirst($parameter->getName());
443
        } else {
444
            $parameterName = mb_substr($method->getName(), 3);
445
        }
446
447
        /**
448
         * Since properties can be duplicated with different case, we assume that _\d+ is replaceable by an empty string as methods are "duplicated" with this suffix.
449
         */
450
        $parameterName = preg_replace('/(_\d+)/', '', $parameterName);
451
        $attribute = $this->getModel()->getAttribute($parameterName);
452
        if (!$attribute instanceof StructAttributeModel) {
453
            $attribute = $this->getModel()->getAttributeByCleanName($parameterName);
454
        }
455
        if (!$attribute instanceof StructAttributeModel) {
456
            $parameterName = lcfirst($parameterName);
457
            $attribute = $this->getModel()->getAttribute($parameterName);
458
            if (!$attribute instanceof StructAttributeModel) {
459
                $attribute = $this->getModel()->getAttributeByCleanName($parameterName);
460
            }
461
        }
462
        $setValueAnnotation = '%s %s value';
463
        $annotationBlock = new PhpAnnotationBlock();
464
        if ($attribute instanceof StructAttributeModel) {
465
            $annotationBlock->addChild(sprintf($setValueAnnotation, ucfirst($setOrGet), $parameterName));
466
            $this->addStructMethodsSetAndGetAnnotationBlockFromStructAttribute($setOrGet, $annotationBlock, $attribute);
467
        } elseif (!$attribute) {
0 ignored issues
show
introduced by
$attribute is of type null, thus it always evaluated to false.
Loading history...
468
            $annotationBlock->addChild(sprintf($setValueAnnotation, ucfirst($setOrGet), lcfirst($parameterName)));
469
            $this->addStructMethodsSetAndGetAnnotationBlockFromScalar($setOrGet, $annotationBlock, $parameterName);
470
        }
471
472
        return $annotationBlock;
473
    }
474
475
    protected function addStructMethodsSetAndGetAnnotationBlockFromStructAttribute(string $setOrGet, PhpAnnotationBlock $annotationBlock, StructAttributeModel $attribute): self
476
    {
477
        switch ($setOrGet) {
478
            case 'set':
479
                if ($attribute->getRemovableFromRequest()) {
480
                    $annotationBlock->addChild('This property is removable from request (nillable=true+minOccurs=0), therefore if the value assigned to this property is null, it is removed from this object');
481
                }
482
                if ($attribute->isAChoice()) {
483
                    $annotationBlock->addChild('This property belongs to a choice that allows only one property to exist. It is therefore removable from the request, consequently if the value assigned to this property is null, the property is removed from this object');
484
                }
485
                if ($attribute->isXml()) {
486
                    $annotationBlock
487
                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, '\DOMDocument::hasChildNodes()'))
488
                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, '\DOMDocument::saveXML()'))
489
                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, '\DOMNode::item()'))
490
                    ;
491
                }
492
                if ($this->getGenerator()->getOptionValidation()) {
493
                    if ($attribute->isAChoice()) {
494
                        $annotationBlock->addChild(new PhpAnnotation(self::ANNOTATION_THROWS, \InvalidArgumentException::class));
495
                    }
496
                    if (($model = $this->getRestrictionFromStructAttribute($attribute)) instanceof StructModel) {
497
                        $annotationBlock
498
                            ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $model->getPackagedName(true), StructEnum::METHOD_VALUE_IS_VALID)))
499
                            ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $model->getPackagedName(true), StructEnum::METHOD_GET_VALID_VALUES)))
500
                            ->addChild(new PhpAnnotation(self::ANNOTATION_THROWS, \InvalidArgumentException::class))
501
                        ;
502
                    } elseif ($attribute->isArray()) {
503
                        $annotationBlock->addChild(new PhpAnnotation(self::ANNOTATION_THROWS, \InvalidArgumentException::class));
504
                    }
505
                }
506
                $this->addStructMethodsSetAnnotationBlock($annotationBlock, $this->getStructAttributeTypeSetAnnotation($attribute, false), lcfirst($attribute->getCleanName()));
507
508
                break;
509
510
            case 'get':
511
                if ($attribute->getRemovableFromRequest()) {
512
                    $annotationBlock->addChild('An additional test has been added (isset) before returning the property value as this property may have been unset before, due to the fact that this property is removable from the request (nillable=true+minOccurs=0)');
513
                }
514
                $this
515
                    ->addStructMethodsGetAnnotationBlockFromXmlAttribute($annotationBlock, $attribute)
516
                    ->addStructMethodsGetAnnotationBlock($annotationBlock, $this->getStructAttributeTypeGetAnnotation($attribute, true, $attribute->isAChoice()))
517
                ;
518
519
                break;
520
        }
521
522
        return $this;
523
    }
524
525
    protected function addStructMethodsSetAndGetAnnotationBlockFromScalar(string $setOrGet, PhpAnnotationBlock $annotationBlock, string $attributeName): self
526
    {
527
        switch ($setOrGet) {
528
            case 'set':
529
                $this->addStructMethodsSetAnnotationBlock($annotationBlock, lcfirst($attributeName), lcfirst($attributeName));
530
531
                break;
532
533
            case 'get':
534
                $this->addStructMethodsGetAnnotationBlock($annotationBlock, lcfirst($attributeName));
535
536
                break;
537
        }
538
539
        return $this;
540
    }
541
542
    protected function addStructMethodsSetAnnotationBlock(PhpAnnotationBlock $annotationBlock, string $type, string $name): self
543
    {
544
        $annotationBlock
545
            ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', $type, $name)))
546
            ->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $this->getModel()->getPackagedName(true)))
547
        ;
548
549
        return $this;
550
    }
551
552
    protected function addStructMethodsGetAnnotationBlock(PhpAnnotationBlock $annotationBlock, string $attributeType): self
553
    {
554
        $annotationBlock->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $attributeType));
555
556
        return $this;
557
    }
558
559
    protected function addStructMethodsGetAnnotationBlockFromXmlAttribute(PhpAnnotationBlock $annotationBlock, StructAttributeModel $attribute): self
560
    {
561
        if ($attribute->isXml()) {
562
            $annotationBlock
563
                ->addChild(new PhpAnnotation(self::ANNOTATION_USES, '\DOMDocument::loadXML()'))
564
                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, 'bool $asDomDocument true: returns \DOMDocument, false: returns XML string'))
565
            ;
566
        }
567
568
        return $this;
569
    }
570
571
    protected function addStructPropertiesToAnnotationBlock(PhpAnnotationBlock $annotationBlock): self
572
    {
573
        return $this
574
            ->addStructPropertiesToAnnotationBlockUses($annotationBlock)
575
            ->addStructPropertiesToAnnotationBlockParams($annotationBlock)
576
        ;
577
    }
578
579
    protected function addStructPropertiesToAnnotationBlockUses(PhpAnnotationBlock $annotationBlock): self
580
    {
581
        foreach ($this->getModelAttributes() as $attribute) {
582
            $annotationBlock->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $this->getModel()->getPackagedName(), $attribute->getSetterName())));
583
        }
584
585
        return $this;
586
    }
587
588
    protected function addStructPropertiesToAnnotationBlockParams(PhpAnnotationBlock $annotationBlock): self
589
    {
590
        foreach ($this->getModelAttributes() as $attribute) {
591
            $annotationBlock->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', $this->getStructAttributeTypeSetAnnotation($attribute, false), lcfirst($attribute->getCleanName()))));
592
        }
593
594
        return $this;
595
    }
596
597
    protected function getStructMethodsAddToAnnotationBlock(PhpMethod $method): PhpAnnotationBlock
598
    {
599
        $methodParameters = $method->getParameters();
600
601
        /** @var PhpFunctionParameter $firstParameter */
602
        $firstParameter = array_shift($methodParameters);
603
        $attribute = $this->getModel()->getAttribute($firstParameter->getModel()->getName());
604
        $annotationBlock = new PhpAnnotationBlock();
605
        if ($attribute instanceof StructAttributeModel) {
606
            $model = $this->getRestrictionFromStructAttribute($attribute);
607
            $annotationBlock->addChild(sprintf('Add item to %s value', $attribute->getCleanName()));
608
            if ($model instanceof StructModel) {
609
                $annotationBlock
610
                    ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $model->getPackagedName(true), StructEnum::METHOD_VALUE_IS_VALID)))
611
                    ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $model->getPackagedName(true), StructEnum::METHOD_GET_VALID_VALUES)))
612
                ;
613
            }
614
            $annotationBlock
615
                ->addChild(new PhpAnnotation(self::ANNOTATION_THROWS, \InvalidArgumentException::class))
616
                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $item', $this->getStructAttributeTypeSetAnnotation($attribute, false, true))))
617
                ->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $this->getModel()->getPackagedName(true)))
618
            ;
619
        }
620
621
        return $annotationBlock;
622
    }
623
624
    protected function getStructMethodsValidateMethodAnnotationBlock(string $propertyName, string $constraintName, string $fromMethodName): PhpAnnotationBlock
625
    {
626
        $customConstraintMessage = '';
627
        $constraintArgName = 'array $values';
628
629
        switch (lcfirst($constraintName)) {
630
            case ArrayRule::NAME:
631
                $customConstraintMessage = 'This has to validate that each item contained by the array match the itemType constraint';
632
633
                break;
634
635
            case ChoiceRule::NAME:
636
                $customConstraintMessage = 'This has to validate that the property which is being set is the only one among the given choices';
637
                $constraintArgName = 'mixed $value';
638
639
                break;
640
641
            case LengthRule::NAME:
642
            case MaxLengthRule::NAME:
643
            case MinLengthRule::NAME:
644
                $customConstraintMessage = 'This has to validate that the items contained by the array match the length constraint';
645
646
                break;
647
648
            case PatternRule::NAME:
649
                $customConstraintMessage = 'This has to validate that the items contained by the array match the defined pattern';
650
651
                break;
652
653
            case UnionRule::NAME:
654
                $customConstraintMessage = sprintf('This is a set of validation rules based on the union types associated to the property %s', $propertyName);
655
                $constraintArgName = 'mixed $value';
656
657
                break;
658
        }
659
660
        return new PhpAnnotationBlock([
661
            new PhpAnnotation(
662
                PhpAnnotation::NO_NAME,
663
                sprintf(
664
                    'This method is responsible for validating the value(s) passed to the %s method',
665
                    lcfirst($fromMethodName)
666
                ),
667
                self::ANNOTATION_LONG_LENGTH
668
            ),
669
            new PhpAnnotation(
670
                PhpAnnotation::NO_NAME,
671
                sprintf(
672
                    'This method is willingly generated in order to preserve the one-line inline validation within the %s method',
673
                    lcfirst($fromMethodName)
674
                ),
675
                self::ANNOTATION_LONG_LENGTH
676
            ),
677
            new PhpAnnotation(
678
                PhpAnnotation::NO_NAME,
679
                $customConstraintMessage,
680
                self::ANNOTATION_LONG_LENGTH
681
            ),
682
            new PhpAnnotation(self::ANNOTATION_PARAM, $constraintArgName),
683
            new PhpAnnotation(
684
                self::ANNOTATION_RETURN,
685
                'string A non-empty message if the values does not match the validation rules'
686
            ),
687
        ]);
688
    }
689
690
    protected function applyRules(PhpMethod $method, StructAttributeModel $attribute, string $parameterName, bool $itemType = false): self
691
    {
692
        if ($this->getGenerator()->getOptionValidation()) {
693
            $rules = new Rules($this, $method, $attribute, $this->methods);
694
            $rules->applyRules($parameterName, $itemType);
695
        }
696
697
        return $this;
698
    }
699
}
700