Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.23% covered (success)
96.23%
153 / 159
94.74% covered (success)
94.74%
36 / 38
CRAP
0.00% covered (danger)
0.00%
0 / 1
Struct
96.23% covered (success)
96.23%
153 / 159
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%
9 / 9
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        if (!empty($inheritance)) {
290            $struct = $this->getInheritanceStruct();
291            while ($struct instanceof Struct) {
292                $structInheritance = $struct->getInheritance();
293                if (!empty($structInheritance)) {
294                    $inheritance = $structInheritance;
295                }
296                $struct = $struct->getInheritanceStruct();
297            }
298        }
299
300        return $inheritance;
301    }
302
303    public function getTopInheritanceStruct(): ?Struct
304    {
305        $struct = $this->getInheritanceStruct();
306        $latestValidStruct = $struct;
307        while ($struct instanceof Struct) {
308            $struct = $struct->getInheritanceStruct();
309            if ($struct instanceof Struct) {
310                $latestValidStruct = $struct;
311            }
312        }
313
314        return $latestValidStruct;
315    }
316
317    public function getMeta(): array
318    {
319        $inheritanceStruct = $this->getInheritanceStruct();
320
321        return $this->mergeMeta(($inheritanceStruct && !$inheritanceStruct->isStruct()) ? $inheritanceStruct->getMeta() : [], parent::getMeta());
322    }
323
324    public function getReservedMethodsInstance(?string $filename = null): AbstractReservedWord
325    {
326        $instance = StructReservedMethod::instance($filename);
327        if ($this->isArray()) {
328            $instance = StructArrayReservedMethod::instance($filename);
329        }
330
331        return $instance;
332    }
333
334    public function getTypes(): array
335    {
336        return $this->types;
337    }
338
339    public function isUnion(): bool
340    {
341        return 0 < count($this->types);
342    }
343
344    public function setTypes(array $types): self
345    {
346        $this->types = $types;
347
348        return $this;
349    }
350
351    public function setAttributesFromSerializedJson(array $attributes): void
352    {
353        foreach ($attributes as $attribute) {
354            $this->attributes->add(self::instanceFromSerializedJson($this->generator, $attribute)->setOwner($this));
355        }
356    }
357
358    public function setValuesFromSerializedJson(array $values): void
359    {
360        foreach ($values as $value) {
361            $this->values->add(self::instanceFromSerializedJson($this->generator, $value)->setOwner($this));
362        }
363    }
364
365    protected function getAllAttributes(bool $includeInheritanceAttributes, bool $requiredFirst): StructAttributeContainer
366    {
367        $allAttributes = new StructAttributeContainer($this->getGenerator());
368        if ($includeInheritanceAttributes) {
369            $this->addInheritanceAttributes($allAttributes);
370        }
371
372        foreach ($this->attributes as $attribute) {
373            $allAttributes->add($attribute);
374        }
375
376        if ($requiredFirst) {
377            $attributes = $this->putRequiredAttributesFirst($allAttributes);
378        } else {
379            $attributes = $allAttributes;
380        }
381
382        return $attributes;
383    }
384
385    protected function addInheritanceAttributes(StructAttributeContainer $attributes): void
386    {
387        if (!empty($this->getInheritance()) && ($model = $this->getInheritanceStruct()) instanceof Struct) {
388            while ($model instanceof Struct && $model->isStruct()) {
389                foreach ($model->getAttributes() as $attribute) {
390                    $attributes->add($attribute);
391                }
392                $model = $model->getInheritanceStruct();
393            }
394        }
395    }
396
397    protected function putRequiredAttributesFirst(StructAttributeContainer $allAttributes): StructAttributeContainer
398    {
399        $attributes = new StructAttributeContainer($this->getGenerator());
400        $requiredAttributes = [];
401        $notRequiredAttributes = [];
402        $nullableNotRequiredAttributes = [];
403
404        /** @var StructAttribute $attribute */
405        foreach ($allAttributes as $attribute) {
406            if ($attribute->isRequired() && !$attribute->isNullable()) {
407                $requiredAttributes[] = $attribute;
408            } elseif (!$attribute->isNullable()) {
409                $notRequiredAttributes[] = $attribute;
410            } else {
411                $nullableNotRequiredAttributes[] = $attribute;
412            }
413        }
414
415        array_walk($requiredAttributes, [$attributes, 'add']);
416        array_walk($nullableNotRequiredAttributes, [$attributes, 'add']);
417        array_walk($notRequiredAttributes, [$attributes, 'add']);
418
419        unset($requiredAttributes, $notRequiredAttributes, $nullableNotRequiredAttributes);
420
421        return $attributes;
422    }
423
424    protected function setValues(StructValueContainer $structValueContainer): self
425    {
426        $this->values = $structValueContainer;
427
428        return $this;
429    }
430
431    protected function toJsonSerialize(): array
432    {
433        return [
434            'attributes' => $this->attributes,
435            'restriction' => $this->isRestriction,
436            'struct' => $this->isStruct,
437            'types' => $this->types,
438            'values' => $this->values,
439            'list' => $this->list,
440        ];
441    }
442}