Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.25% covered (success)
96.25%
154 / 160
94.74% covered (success)
94.74%
36 / 38
CRAP
0.00% covered (danger)
0.00%
0 / 1
Struct
96.25% covered (success)
96.25%
154 / 160
94.74% covered (success)
94.74%
36 / 38
89
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getContextualPart
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getDocSubPackages
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 isArray
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
7
 getAttributes
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getProperAttributes
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
9
 countOwnAttributes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 countAllAttributes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setAttributes
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addAttribute
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAttributeByCleanName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRestriction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRestriction
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isStruct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setStruct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setList
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getValues
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addValue
61.54% covered (warning)
61.54%
8 / 13
0.00% covered (danger)
0.00%
0 / 1
3.51
 getValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExtends
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getInheritanceStruct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getTopInheritance
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 getTopInheritanceStruct
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 getMeta
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 getReservedMethodsInstance
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getTypes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isUnion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTypes
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setAttributesFromSerializedJson
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 setValuesFromSerializedJson
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getAllAttributes
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 addInheritanceAttributes
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
6
 putRequiredAttributesFirst
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
5
 setValues
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 toJsonSerialize
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace WsdlToPhp\PackageGenerator\Model;
6
7use WsdlToPhp\PackageGenerator\ConfigurationReader\AbstractReservedWord;
8use WsdlToPhp\PackageGenerator\ConfigurationReader\StructArrayReservedMethod;
9use WsdlToPhp\PackageGenerator\ConfigurationReader\StructReservedMethod;
10use WsdlToPhp\PackageGenerator\Container\Model\StructAttribute as StructAttributeContainer;
11use WsdlToPhp\PackageGenerator\Container\Model\StructValue as StructValueContainer;
12use WsdlToPhp\PackageGenerator\Generator\Generator;
13use WsdlToPhp\PackageGenerator\Generator\Utils;
14
15/**
16 * Class Struct stands for an available struct described in the WSDL.
17 */
18final class Struct extends AbstractModel
19{
20    public const DOC_SUB_PACKAGE_STRUCTS = 'Structs';
21    public const DOC_SUB_PACKAGE_ENUMERATIONS = 'Enumerations';
22    public const DOC_SUB_PACKAGE_ARRAYS = 'Arrays';
23    public const DEFAULT_ENUM_TYPE = 'string';
24
25    /**
26     * Attributes of the struct.
27     */
28    protected StructAttributeContainer $attributes;
29
30    /**
31     * Is the struct a restriction with defined values  ?
32     */
33    protected bool $isRestriction = false;
34
35    /**
36     * If the struct is a restriction with values, then store values.
37     */
38    protected StructValueContainer $values;
39
40    /**
41     * If the struct is a union with types, then store types.
42     *
43     * @var string[]
44     */
45    protected array $types = [];
46
47    /**
48     * Defines if the current struct is a concrete struct or just a virtual struct to store meta information.
49     */
50    protected bool $isStruct = false;
51
52    /**
53     * Defines if the current struct is a list of a type or not.
54     * If it is a list of a type, then the list property value is the type.
55     */
56    protected string $list = '';
57
58    public function __construct(Generator $generator, $name, $isStruct = true, $isRestriction = false)
59    {
60        parent::__construct($generator, $name);
61        $this
62            ->setStruct($isStruct)
63            ->setRestriction($isRestriction)
64            ->setAttributes(new StructAttributeContainer($generator))
65            ->setValues(new StructValueContainer($generator))
66        ;
67    }
68
69    public function getContextualPart(): string
70    {
71        $part = $this->getGenerator()->getOptionStructsFolder();
72        if ($this->isRestriction()) {
73            $part = $this->getGenerator()->getOptionEnumsFolder();
74        } elseif ($this->isArray()) {
75            $part = $this->getGenerator()->getOptionArraysFolder();
76        }
77
78        return $part;
79    }
80
81    public function getDocSubPackages(): array
82    {
83        $package = self::DOC_SUB_PACKAGE_STRUCTS;
84        if ($this->isRestriction()) {
85            $package = self::DOC_SUB_PACKAGE_ENUMERATIONS;
86        } elseif ($this->isArray()) {
87            $package = self::DOC_SUB_PACKAGE_ARRAYS;
88        }
89
90        return [
91            $package,
92        ];
93    }
94
95    public function isArray(): bool
96    {
97        return
98            (
99                (
100                    ($this->isStruct() && 1 === $this->countAllAttributes())
101                    || (!$this->isStruct() && 1 >= $this->countOwnAttributes())
102                )
103                && false !== mb_stripos($this->getName(), 'array')
104            )
105            || (!$this->isStruct() && false !== $this->getMetaValueFirstSet(['arraytype', 'arrayType'], false));
106    }
107
108    public function getAttributes(bool $includeInheritanceAttributes = false, bool $requiredFirst = false): StructAttributeContainer
109    {
110        if (!$includeInheritanceAttributes && !$requiredFirst) {
111            $attributes = $this->attributes;
112        } else {
113            $attributes = $this->getAllAttributes($includeInheritanceAttributes, $requiredFirst);
114        }
115
116        return $attributes;
117    }
118
119    /**
120     * Returns the attributes of the struct and not the ones that are declared by the parent struct if this struct inherits from a Struct
121     * This means it removes from the attributes this Struct has the attributes declared by its parent class(es).
122     *
123     * @param bool $requiredFirst places the required attributes first, then the not required in order to have the _construct method with the required attribute at first
124     */
125    public function getProperAttributes(bool $requiredFirst = false): StructAttributeContainer
126    {
127        $properAttributes = new StructAttributeContainer($this->getGenerator());
128        $parentAttributes = new StructAttributeContainer($this->getGenerator());
129
130        if (!empty($this->getInheritance()) && ($model = $this->getInheritanceStruct()) instanceof Struct) {
131            while ($model instanceof Struct && $model->isStruct()) {
132                foreach ($model->getAttributes() as $attribute) {
133                    $parentAttributes->add($attribute);
134                }
135                $model = $model->getInheritanceStruct();
136            }
137        }
138
139        /** @var StructAttribute $attribute */
140        foreach ($this->getAttributes() as $attribute) {
141            if ($parentAttributes->getStructAttributeByName($attribute->getName())) {
142                continue;
143            }
144            $properAttributes->add($attribute);
145        }
146
147        return $requiredFirst ? $this->putRequiredAttributesFirst($properAttributes) : $properAttributes;
148    }
149
150    public function countOwnAttributes(): int
151    {
152        return $this->getAttributes()->count();
153    }
154
155    public function countAllAttributes(): int
156    {
157        return $this->getAttributes(true)->count();
158    }
159
160    public function setAttributes(StructAttributeContainer $structAttributeContainer): self
161    {
162        $this->attributes = $structAttributeContainer;
163
164        return $this;
165    }
166
167    /**
168     * @throws \InvalidArgumentException
169     */
170    public function addAttribute(string $attributeName, string $attributeType): self
171    {
172        if (empty($attributeName) || empty($attributeType)) {
173            throw new \InvalidArgumentException(sprintf('Attribute name "%s" and/or attribute type "%s" is invalid for Struct "%s"', $attributeName, $attributeType, $this->getName()), __LINE__);
174        }
175        if (is_null($this->attributes->getStructAttributeByName($attributeName))) {
176            $structAttribute = new StructAttribute($this->getGenerator(), $attributeName, $attributeType, $this);
177            $this->attributes->add($structAttribute);
178        }
179
180        return $this;
181    }
182
183    public function getAttribute(string $attributeName): ?StructAttribute
184    {
185        return $this->attributes->getStructAttributeByName($attributeName);
186    }
187
188    public function getAttributeByCleanName(string $attributeCleanName): ?StructAttribute
189    {
190        return $this->attributes->getStructAttributeByCleanName($attributeCleanName);
191    }
192
193    public function isRestriction(): bool
194    {
195        return $this->isRestriction;
196    }
197
198    public function setRestriction($isRestriction = true): self
199    {
200        $this->isRestriction = $isRestriction;
201
202        return $this;
203    }
204
205    public function isStruct(): bool
206    {
207        return $this->isStruct;
208    }
209
210    public function setStruct(bool $isStruct = true): self
211    {
212        $this->isStruct = $isStruct;
213
214        return $this;
215    }
216
217    public function getList(): string
218    {
219        return $this->list;
220    }
221
222    public function isList(): bool
223    {
224        return !empty($this->list);
225    }
226
227    public function setList(string $list = ''): self
228    {
229        $this->list = $list;
230
231        return $this;
232    }
233
234    public function getValues(): StructValueContainer
235    {
236        return $this->values;
237    }
238
239    public function addValue($value): self
240    {
241        if (is_null($this->getValue($value))) {
242            // issue #177, rare case: a struct and an enum has the same name and the enum is not detected by the SoapClient,
243            // then we need to create the enumeration struct in order to deduplicate the two structs
244            // this is why enumerations has to be parsed before any other elements by the WSDL parsers
245            if (0 < $this->countOwnAttributes()) {
246                $enum = new Struct($this->getGenerator(), $this->getName(), true, true);
247                $enum->setInheritance(self::DEFAULT_ENUM_TYPE);
248                $enum->getValues()->add(new StructValue($enum->getGenerator(), $value, $enum->getValues()->count(), $enum));
249                $this->getGenerator()->getStructs()->add($enum);
250
251                return $enum;
252            }
253            $this
254                ->setStruct(true)
255                ->setRestriction(true)
256                ->getValues()->add(new StructValue($this->getGenerator(), $value, $this->getValues()->count(), $this))
257            ;
258        }
259
260        return $this;
261    }
262
263    public function getValue($value): ?StructValue
264    {
265        return $this->values->getStructValueByName($value);
266    }
267
268    public function getExtends(bool $short = false): string
269    {
270        if ($this->isArray()) {
271            $extends = $this->getGenerator()->getOptionStructArrayClass();
272        } elseif ($this->isRestriction()) {
273            $extends = $this->getGenerator()->getOptionStructEnumClass();
274        } else {
275            $extends = $this->getGenerator()->getOptionStructClass();
276        }
277
278        return $short ? Utils::removeNamespace($extends) : $extends;
279    }
280
281    public function getInheritanceStruct(): ?Struct
282    {
283        return $this->getName() === $this->getInheritance() ? null : $this->getGenerator()->getStructByName(str_replace('[]', '', $this->getInheritance()));
284    }
285
286    public function getTopInheritance(): string
287    {
288        $inheritance = $this->getInheritance();
289
290        if (empty($inheritance)) {
291            return $inheritance;
292        }
293
294        $struct = $this->getInheritanceStruct();
295        while ($struct instanceof Struct) {
296            $structInheritance = $struct->getInheritance();
297            if (!empty($structInheritance)) {
298                $inheritance = $structInheritance;
299            }
300            $struct = $struct->getInheritanceStruct();
301        }
302
303        return $inheritance;
304    }
305
306    public function getTopInheritanceStruct(): ?Struct
307    {
308        $struct = $this->getInheritanceStruct();
309        $latestValidStruct = $struct;
310        while ($struct instanceof Struct) {
311            $struct = $struct->getInheritanceStruct();
312            if ($struct instanceof Struct) {
313                $latestValidStruct = $struct;
314            }
315        }
316
317        return $latestValidStruct;
318    }
319
320    public function getMeta(): array
321    {
322        $inheritanceStruct = $this->getInheritanceStruct();
323
324        return $this->mergeMeta(($inheritanceStruct && !$inheritanceStruct->isStruct()) ? $inheritanceStruct->getMeta() : [], parent::getMeta());
325    }
326
327    public function getReservedMethodsInstance(?string $filename = null): AbstractReservedWord
328    {
329        $instance = StructReservedMethod::instance($filename);
330        if ($this->isArray()) {
331            $instance = StructArrayReservedMethod::instance($filename);
332        }
333
334        return $instance;
335    }
336
337    public function getTypes(): array
338    {
339        return $this->types;
340    }
341
342    public function isUnion(): bool
343    {
344        return 0 < count($this->types);
345    }
346
347    public function setTypes(array $types): self
348    {
349        $this->types = $types;
350
351        return $this;
352    }
353
354    public function setAttributesFromSerializedJson(array $attributes): void
355    {
356        foreach ($attributes as $attribute) {
357            $this->attributes->add(self::instanceFromSerializedJson($this->generator, $attribute)->setOwner($this));
358        }
359    }
360
361    public function setValuesFromSerializedJson(array $values): void
362    {
363        foreach ($values as $value) {
364            $this->values->add(self::instanceFromSerializedJson($this->generator, $value)->setOwner($this));
365        }
366    }
367
368    protected function getAllAttributes(bool $includeInheritanceAttributes, bool $requiredFirst): StructAttributeContainer
369    {
370        $allAttributes = new StructAttributeContainer($this->getGenerator());
371        if ($includeInheritanceAttributes) {
372            $this->addInheritanceAttributes($allAttributes);
373        }
374
375        foreach ($this->attributes as $attribute) {
376            $allAttributes->add($attribute);
377        }
378
379        if ($requiredFirst) {
380            $attributes = $this->putRequiredAttributesFirst($allAttributes);
381        } else {
382            $attributes = $allAttributes;
383        }
384
385        return $attributes;
386    }
387
388    protected function addInheritanceAttributes(StructAttributeContainer $attributes): void
389    {
390        if (!empty($this->getInheritance()) && ($model = $this->getInheritanceStruct()) instanceof Struct) {
391            while ($model instanceof Struct && $model->isStruct()) {
392                foreach ($model->getAttributes() as $attribute) {
393                    $attributes->add($attribute);
394                }
395                $model = $model->getInheritanceStruct();
396            }
397        }
398    }
399
400    protected function putRequiredAttributesFirst(StructAttributeContainer $allAttributes): StructAttributeContainer
401    {
402        $attributes = new StructAttributeContainer($this->getGenerator());
403        $requiredAttributes = [];
404        $notRequiredAttributes = [];
405        $nullableNotRequiredAttributes = [];
406
407        /** @var StructAttribute $attribute */
408        foreach ($allAttributes as $attribute) {
409            if ($attribute->isRequired() && !$attribute->isNullable()) {
410                $requiredAttributes[] = $attribute;
411            } elseif (!$attribute->isNullable()) {
412                $notRequiredAttributes[] = $attribute;
413            } else {
414                $nullableNotRequiredAttributes[] = $attribute;
415            }
416        }
417
418        array_walk($requiredAttributes, [$attributes, 'add']);
419        array_walk($nullableNotRequiredAttributes, [$attributes, 'add']);
420        array_walk($notRequiredAttributes, [$attributes, 'add']);
421
422        unset($requiredAttributes, $notRequiredAttributes, $nullableNotRequiredAttributes);
423
424        return $attributes;
425    }
426
427    protected function setValues(StructValueContainer $structValueContainer): self
428    {
429        $this->values = $structValueContainer;
430
431        return $this;
432    }
433
434    protected function toJsonSerialize(): array
435    {
436        return [
437            'attributes' => $this->attributes,
438            'restriction' => $this->isRestriction,
439            'struct' => $this->isStruct,
440            'types' => $this->types,
441            'values' => $this->values,
442            'list' => $this->list,
443        ];
444    }
445}