Passed
Push — feature/issue-312 ( 4ecd3b )
by Mikaël
49:06
created

Struct::addStructMethodConstruct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 9
rs 10
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($this->getStructAttributeTypeAsPhpType($attribute));
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
        // it can either be a string, a DOMDocument or null...
351
        if ($attribute->isXml()) {
352
            $returnType = '';
353
        } else {
354
            $returnType = (
355
                !$attribute->getRemovableFromRequest()
356
                && !$attribute->isAChoice()
357
                && $attribute->isRequired()
358
                && !$attribute->isNullable() ? '' : '?'
359
            ).$this->getStructAttributeTypeAsPhpType($attribute);
360
        }
361
362
        $method = new PhpMethod(
363
            $attribute->getGetterName(),
364
            $this->getStructMethodGetParameters($attribute),
365
            $returnType
366
        );
367
        if ($attribute->nameIsClean()) {
368
            $thisAccess = sprintf('%s', $attribute->getName());
369
        } else {
370
            $thisAccess = sprintf('{\'%s\'}', addslashes($attribute->getName()));
371
        }
372
        $this->addStructMethodGetBody($method, $attribute, $thisAccess);
373
        $this->methods->add($method);
374
375
        return $this;
376
    }
377
378
    protected function getStructMethodGetParameters(StructAttributeModel $attribute): array
379
    {
380
        $parameters = [];
381
        if ($attribute->isXml()) {
382
            $parameters[] = new PhpFunctionParameter('asDomDocument', false, self::TYPE_BOOL, $attribute);
383
        }
384
385
        return $parameters;
386
    }
387
388
    protected function getMethodAnnotationBlock(PhpMethod $method): ?PhpAnnotationBlock
389
    {
390
        return $this->getStructMethodAnnotationBlock($method);
391
    }
392
393
    protected function getStructMethodAnnotationBlock(PhpMethod $method): ?PhpAnnotationBlock
394
    {
395
        $annotationBlock = null;
396
        $matches = [];
397
398
        switch ($method->getName()) {
399
            case self::METHOD_CONSTRUCT:
400
                $annotationBlock = $this->getStructMethodConstructAnnotationBlock();
401
402
                break;
403
404
            case 0 === mb_strpos($method->getName(), 'get'):
405
            case 0 === mb_strpos($method->getName(), 'set'):
406
                $annotationBlock = $this->getStructMethodsSetAndGetAnnotationBlock($method);
407
408
                break;
409
410
            case 0 === mb_strpos($method->getName(), 'addTo'):
411
                $annotationBlock = $this->getStructMethodsAddToAnnotationBlock($method);
412
413
                break;
414
415
            case 1 === preg_match('/validate(.+)For(.+)ConstraintFrom(.+)/', $method->getName(), $matches):
416
                $annotationBlock = $this->getStructMethodsValidateMethodAnnotationBlock($matches[1], $matches[2], $matches[3]);
417
418
                break;
419
        }
420
421
        return $annotationBlock;
422
    }
423
424
    protected function getStructMethodConstructAnnotationBlock(): PhpAnnotationBlock
425
    {
426
        $annotationBlock = new PhpAnnotationBlock([
427
            sprintf('Constructor method for %s', $this->getModel()->getName()),
428
        ]);
429
        $this->addStructPropertiesToAnnotationBlock($annotationBlock);
430
431
        return $annotationBlock;
432
    }
433
434
    protected function getStructMethodsSetAndGetAnnotationBlock(PhpMethod $method): PhpAnnotationBlock
435
    {
436
        $parameters = $method->getParameters();
437
        $setOrGet = mb_strtolower(mb_substr($method->getName(), 0, 3));
438
        $parameter = array_shift($parameters);
439
        // Only set parameter must be based on a potential PhpFunctionParameter
440
        if ($parameter instanceof PhpFunctionParameter && 'set' === $setOrGet) {
441
            $parameterName = ucfirst($parameter->getName());
442
        } else {
443
            $parameterName = mb_substr($method->getName(), 3);
444
        }
445
446
        /**
447
         * 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.
448
         */
449
        $parameterName = preg_replace('/(_\d+)/', '', $parameterName);
450
        $attribute = $this->getModel()->getAttribute($parameterName);
451
        if (!$attribute instanceof StructAttributeModel) {
452
            $attribute = $this->getModel()->getAttributeByCleanName($parameterName);
453
        }
454
        if (!$attribute instanceof StructAttributeModel) {
455
            $parameterName = lcfirst($parameterName);
456
            $attribute = $this->getModel()->getAttribute($parameterName);
457
            if (!$attribute instanceof StructAttributeModel) {
458
                $attribute = $this->getModel()->getAttributeByCleanName($parameterName);
459
            }
460
        }
461
        $setValueAnnotation = '%s %s value';
462
        $annotationBlock = new PhpAnnotationBlock();
463
        if ($attribute instanceof StructAttributeModel) {
464
            $annotationBlock->addChild(sprintf($setValueAnnotation, ucfirst($setOrGet), $parameterName));
465
            $this->addStructMethodsSetAndGetAnnotationBlockFromStructAttribute($setOrGet, $annotationBlock, $attribute);
466
        } elseif (!$attribute) {
0 ignored issues
show
introduced by
$attribute is of type null, thus it always evaluated to false.
Loading history...
467
            $annotationBlock->addChild(sprintf($setValueAnnotation, ucfirst($setOrGet), lcfirst($parameterName)));
468
            $this->addStructMethodsSetAndGetAnnotationBlockFromScalar($setOrGet, $annotationBlock, $parameterName);
469
        }
470
471
        return $annotationBlock;
472
    }
473
474
    protected function addStructMethodsSetAndGetAnnotationBlockFromStructAttribute(string $setOrGet, PhpAnnotationBlock $annotationBlock, StructAttributeModel $attribute): self
475
    {
476
        switch ($setOrGet) {
477
            case 'set':
478
                if ($attribute->getRemovableFromRequest()) {
479
                    $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');
480
                }
481
                if ($attribute->isAChoice()) {
482
                    $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');
483
                }
484
                if ($attribute->isXml()) {
485
                    $annotationBlock
486
                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, '\DOMDocument::hasChildNodes()'))
487
                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, '\DOMDocument::saveXML()'))
488
                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, '\DOMNode::item()'))
489
                    ;
490
                }
491
                if ($this->getGenerator()->getOptionValidation()) {
492
                    if ($attribute->isAChoice()) {
493
                        $annotationBlock->addChild(new PhpAnnotation(self::ANNOTATION_THROWS, \InvalidArgumentException::class));
494
                    }
495
                    if (($model = $this->getRestrictionFromStructAttribute($attribute)) instanceof StructModel) {
496
                        $annotationBlock
497
                            ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $model->getPackagedName(true), StructEnum::METHOD_VALUE_IS_VALID)))
498
                            ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $model->getPackagedName(true), StructEnum::METHOD_GET_VALID_VALUES)))
499
                            ->addChild(new PhpAnnotation(self::ANNOTATION_THROWS, \InvalidArgumentException::class))
500
                        ;
501
                    } elseif ($attribute->isArray()) {
502
                        $annotationBlock->addChild(new PhpAnnotation(self::ANNOTATION_THROWS, \InvalidArgumentException::class));
503
                    }
504
                }
505
                $this->addStructMethodsSetAnnotationBlock($annotationBlock, $this->getStructAttributeTypeSetAnnotation($attribute, false), lcfirst($attribute->getCleanName()));
506
507
                break;
508
509
            case 'get':
510
                if ($attribute->getRemovableFromRequest()) {
511
                    $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)');
512
                }
513
                $this
514
                    ->addStructMethodsGetAnnotationBlockFromXmlAttribute($annotationBlock, $attribute)
515
                    ->addStructMethodsGetAnnotationBlock($annotationBlock, $this->getStructAttributeTypeGetAnnotation($attribute, true, $attribute->isAChoice()))
516
                ;
517
518
                break;
519
        }
520
521
        return $this;
522
    }
523
524
    protected function addStructMethodsSetAndGetAnnotationBlockFromScalar(string $setOrGet, PhpAnnotationBlock $annotationBlock, string $attributeName): self
525
    {
526
        switch ($setOrGet) {
527
            case 'set':
528
                $this->addStructMethodsSetAnnotationBlock($annotationBlock, lcfirst($attributeName), lcfirst($attributeName));
529
530
                break;
531
532
            case 'get':
533
                $this->addStructMethodsGetAnnotationBlock($annotationBlock, lcfirst($attributeName));
534
535
                break;
536
        }
537
538
        return $this;
539
    }
540
541
    protected function addStructMethodsSetAnnotationBlock(PhpAnnotationBlock $annotationBlock, string $type, string $name): self
542
    {
543
        $annotationBlock
544
            ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', $type, $name)))
545
            ->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $this->getModel()->getPackagedName(true)))
546
        ;
547
548
        return $this;
549
    }
550
551
    protected function addStructMethodsGetAnnotationBlock(PhpAnnotationBlock $annotationBlock, string $attributeType): self
552
    {
553
        $annotationBlock->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $attributeType));
554
555
        return $this;
556
    }
557
558
    protected function addStructMethodsGetAnnotationBlockFromXmlAttribute(PhpAnnotationBlock $annotationBlock, StructAttributeModel $attribute): self
559
    {
560
        if ($attribute->isXml()) {
561
            $annotationBlock
562
                ->addChild(new PhpAnnotation(self::ANNOTATION_USES, '\DOMDocument::loadXML()'))
563
                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, 'bool $asDomDocument true: returns \DOMDocument, false: returns XML string'))
564
            ;
565
        }
566
567
        return $this;
568
    }
569
570
    protected function addStructPropertiesToAnnotationBlock(PhpAnnotationBlock $annotationBlock): self
571
    {
572
        return $this
573
            ->addStructPropertiesToAnnotationBlockUses($annotationBlock)
574
            ->addStructPropertiesToAnnotationBlockParams($annotationBlock)
575
        ;
576
    }
577
578
    protected function addStructPropertiesToAnnotationBlockUses(PhpAnnotationBlock $annotationBlock): self
579
    {
580
        foreach ($this->getModelAttributes() as $attribute) {
581
            $annotationBlock->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $this->getModel()->getPackagedName(), $attribute->getSetterName())));
582
        }
583
584
        return $this;
585
    }
586
587
    protected function addStructPropertiesToAnnotationBlockParams(PhpAnnotationBlock $annotationBlock): self
588
    {
589
        foreach ($this->getModelAttributes() as $attribute) {
590
            $annotationBlock->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', $this->getStructAttributeTypeSetAnnotation($attribute, false), lcfirst($attribute->getCleanName()))));
591
        }
592
593
        return $this;
594
    }
595
596
    protected function getStructMethodsAddToAnnotationBlock(PhpMethod $method): PhpAnnotationBlock
597
    {
598
        $methodParameters = $method->getParameters();
599
600
        /** @var PhpFunctionParameter $firstParameter */
601
        $firstParameter = array_shift($methodParameters);
602
        $attribute = $this->getModel()->getAttribute($firstParameter->getModel()->getName());
603
        $annotationBlock = new PhpAnnotationBlock();
604
        if ($attribute instanceof StructAttributeModel) {
605
            $model = $this->getRestrictionFromStructAttribute($attribute);
606
            $annotationBlock->addChild(sprintf('Add item to %s value', $attribute->getCleanName()));
607
            if ($model instanceof StructModel) {
608
                $annotationBlock
609
                    ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $model->getPackagedName(true), StructEnum::METHOD_VALUE_IS_VALID)))
610
                    ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $model->getPackagedName(true), StructEnum::METHOD_GET_VALID_VALUES)))
611
                ;
612
            }
613
            $annotationBlock
614
                ->addChild(new PhpAnnotation(self::ANNOTATION_THROWS, \InvalidArgumentException::class))
615
                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $item', $this->getStructAttributeTypeSetAnnotation($attribute, false, true))))
616
                ->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $this->getModel()->getPackagedName(true)))
617
            ;
618
        }
619
620
        return $annotationBlock;
621
    }
622
623
    protected function getStructMethodsValidateMethodAnnotationBlock(string $propertyName, string $constraintName, string $fromMethodName): PhpAnnotationBlock
624
    {
625
        $customConstraintMessage = '';
626
        $constraintArgName = 'array $values';
627
628
        switch (lcfirst($constraintName)) {
629
            case ArrayRule::NAME:
630
                $customConstraintMessage = 'This has to validate that each item contained by the array match the itemType constraint';
631
632
                break;
633
634
            case ChoiceRule::NAME:
635
                $customConstraintMessage = 'This has to validate that the property which is being set is the only one among the given choices';
636
                $constraintArgName = 'mixed $value';
637
638
                break;
639
640
            case LengthRule::NAME:
641
            case MaxLengthRule::NAME:
642
            case MinLengthRule::NAME:
643
                $customConstraintMessage = 'This has to validate that the items contained by the array match the length constraint';
644
645
                break;
646
647
            case PatternRule::NAME:
648
                $customConstraintMessage = 'This has to validate that the items contained by the array match the defined pattern';
649
650
                break;
651
652
            case UnionRule::NAME:
653
                $customConstraintMessage = sprintf('This is a set of validation rules based on the union types associated to the property %s', $propertyName);
654
                $constraintArgName = 'mixed $value';
655
656
                break;
657
        }
658
659
        return new PhpAnnotationBlock([
660
            new PhpAnnotation(
661
                PhpAnnotation::NO_NAME,
662
                sprintf(
663
                    'This method is responsible for validating the value(s) passed to the %s method',
664
                    lcfirst($fromMethodName)
665
                ),
666
                self::ANNOTATION_LONG_LENGTH
667
            ),
668
            new PhpAnnotation(
669
                PhpAnnotation::NO_NAME,
670
                sprintf(
671
                    'This method is willingly generated in order to preserve the one-line inline validation within the %s method',
672
                    lcfirst($fromMethodName)
673
                ),
674
                self::ANNOTATION_LONG_LENGTH
675
            ),
676
            new PhpAnnotation(
677
                PhpAnnotation::NO_NAME,
678
                $customConstraintMessage,
679
                self::ANNOTATION_LONG_LENGTH
680
            ),
681
            new PhpAnnotation(self::ANNOTATION_PARAM, $constraintArgName),
682
            new PhpAnnotation(
683
                self::ANNOTATION_RETURN,
684
                'string A non-empty message if the values does not match the validation rules'
685
            ),
686
        ]);
687
    }
688
689
    protected function applyRules(PhpMethod $method, StructAttributeModel $attribute, string $parameterName, bool $itemType = false): self
690
    {
691
        if ($this->getGenerator()->getOptionValidation()) {
692
            $rules = new Rules($this, $method, $attribute, $this->methods);
693
            $rules->applyRules($parameterName, $itemType);
694
        }
695
696
        return $this;
697
    }
698
}
699