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