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
126
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
10.24
 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    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);
39    }
40
41    public function getModel(): ?StructModel
42    {
43        return parent::getModel();
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();
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) {
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}