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%
10 / 10
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
58.33% covered (warning)
58.33%
7 / 12
0.00% covered (danger)
0.00%
0 / 1
3.65
 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
109    public function getAttributes(bool $includeInheritanceAttributes = false, bool $requiredFirst = false): StructAttributeContainer
110    {
111        if (!$includeInheritanceAttributes && !$requiredFirst) {
112            $attributes = $this->attributes;
113        } else {
114            $attributes = $this->getAllAttributes($includeInheritanceAttributes, $requiredFirst);
115        }
116
117        return $attributes;
118    }
119
120    /**
121     * Returns the attributes of the struct and not the ones that are declared by the parent struct if this struct inherits from a Struct
122     * This means it removes from the attributes this Struct has the attributes declared by its parent class(es).
123     *
124     * @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
125     */
126    public function getProperAttributes(bool $requiredFirst = false): StructAttributeContainer
127    {
128        $properAttributes = new StructAttributeContainer($this->getGenerator());
129        $parentAttributes = new StructAttributeContainer($this->getGenerator());
130
131        if (!empty($this->getInheritance()) && ($model = $this->getInheritanceStruct()) instanceof Struct) {
132            while ($model instanceof Struct && $model->isStruct()) {
133                foreach ($model->getAttributes() as $attribute) {
134                    $parentAttributes->add($attribute);
135                }
136                $model = $model->getInheritanceStruct();
137            }
138        }
139
140        /** @var StructAttribute $attribute */
141        foreach ($this->getAttributes() as $attribute) {
142            if ($parentAttributes->getStructAttributeByName($attribute->getName())) {
143                continue;
144            }
145            $properAttributes->add($attribute);
146        }
147
148        return $requiredFirst ? $this->putRequiredAttributesFirst($properAttributes) : $properAttributes;
149    }
150
151    public function countOwnAttributes(): int
152    {
153        return $this->getAttributes()->count();
154    }
155
156    public function countAllAttributes(): int
157    {
158        return $this->getAttributes(true)->count();
159    }
160
161    public function setAttributes(StructAttributeContainer $structAttributeContainer): self
162    {
163        $this->attributes = $structAttributeContainer;
164
165        return $this;
166    }
167
168    public function addAttribute(string $attributeName, string $attributeType): self
169    {
170        if (empty($attributeName) || empty($attributeType)) {
171            throw new \InvalidArgumentException(sprintf('Attribute name "%s" and/or attribute type "%s" is invalid for Struct "%s"', $attributeName, $attributeType, $this->getName()), __LINE__);
172        }
173        if (is_null($this->attributes->getStructAttributeByName($attributeName))) {
174            $structAttribute = new StructAttribute($this->getGenerator(), $attributeName, $attributeType, $this);
175            $this->attributes->add($structAttribute);
176        }
177
178        return $this;
179    }
180
181    public function getAttribute(string $attributeName): ?StructAttribute
182    {
183        return $this->attributes->getStructAttributeByName($attributeName);
184    }
185
186    public function getAttributeByCleanName(string $attributeCleanName): ?StructAttribute
187    {
188        return $this->attributes->getStructAttributeByCleanName($attributeCleanName);
189    }
190
191    public function isRestriction(): bool
192    {
193        return $this->isRestriction;
194    }
195
196    public function setRestriction($isRestriction = true): self
197    {
198        $this->isRestriction = $isRestriction;
199
200        return $this;
201    }
202
203    public function isStruct(): bool
204    {
205        return $this->isStruct;
206    }
207
208    public function setStruct(bool $isStruct = true): self
209    {
210        $this->isStruct = $isStruct;
211
212        return $this;
213    }
214
215    public function getList(): string
216    {
217        return $this->list;
218    }
219
220    public function isList(): bool
221    {
222        return !empty($this->list);
223    }
224
225    public function setList(string $list = ''): self
226    {
227        $this->list = $list;
228
229        return $this;
230    }
231
232    public function getValues(): StructValueContainer
233    {
234        return $this->values;
235    }
236
237    public function addValue($value): self
238    {
239        if (is_null($this->getValue($value))) {
240            // issue #177, rare case: a struct and an enum has the same name and the enum is not detected by the SoapClient,
241            // then we need to create the enumeration struct in order to deduplicate the two structs
242            // this is why enumerations has to be parsed before any other elements by the WSDL parsers
243            if (0 < $this->countOwnAttributes()) {
244                $enum = new Struct($this->getGenerator(), $this->getName(), true, true);
245                $enum->setInheritance(self::DEFAULT_ENUM_TYPE);
246                $enum->getValues()->add(new StructValue($enum->getGenerator(), $value, $enum->getValues()->count(), $enum));
247                $this->getGenerator()->getStructs()->add($enum);
248
249                return $enum;
250            }
251            $this
252                ->setStruct(true)
253                ->setRestriction(true)
254                ->getValues()->add(new StructValue($this->getGenerator(), $value, $this->getValues()->count(), $this));
255        }
256
257        return $this;
258    }
259
260    public function getValue($value): ?StructValue
261    {
262        return $this->values->getStructValueByName($value);
263    }
264
265    public function getExtends(bool $short = false): string
266    {
267        if ($this->isArray()) {
268            $extends = $this->getGenerator()->getOptionStructArrayClass();
269        } elseif ($this->isRestriction()) {
270            $extends = $this->getGenerator()->getOptionStructEnumClass();
271        } else {
272            $extends = $this->getGenerator()->getOptionStructClass();
273        }
274
275        return $short ? Utils::removeNamespace($extends) : $extends;
276    }
277
278    public function getInheritanceStruct(): ?Struct
279    {
280        return $this->getName() === $this->getInheritance() ? null : $this->getGenerator()->getStructByName(str_replace('[]', '', $this->getInheritance()));
281    }
282
283    public function getTopInheritance(): string
284    {
285        $inheritance = $this->getInheritance();
286        if (!empty($inheritance)) {
287            $struct = $this->getInheritanceStruct();
288            while ($struct instanceof Struct) {
289                $structInheritance = $struct->getInheritance();
290                if (!empty($structInheritance)) {
291                    $inheritance = $structInheritance;
292                }
293                $struct = $struct->getInheritanceStruct();
294            }
295        }
296
297        return $inheritance;
298    }
299
300    public function getTopInheritanceStruct(): ?Struct
301    {
302        $struct = $this->getInheritanceStruct();
303        $latestValidStruct = $struct;
304        while ($struct instanceof Struct) {
305            $struct = $struct->getInheritanceStruct();
306            if ($struct instanceof Struct) {
307                $latestValidStruct = $struct;
308            }
309        }
310
311        return $latestValidStruct;
312    }
313
314    public function getMeta(): array
315    {
316        $inheritanceStruct = $this->getInheritanceStruct();
317
318        return $this->mergeMeta(($inheritanceStruct && !$inheritanceStruct->isStruct()) ? $inheritanceStruct->getMeta() : [], parent::getMeta());
319    }
320
321    public function getReservedMethodsInstance(?string $filename = null): AbstractReservedWord
322    {
323        $instance = StructReservedMethod::instance($filename);
324        if ($this->isArray()) {
325            $instance = StructArrayReservedMethod::instance($filename);
326        }
327
328        return $instance;
329    }
330
331    public function getTypes(): array
332    {
333        return $this->types;
334    }
335
336    public function isUnion(): bool
337    {
338        return 0 < count($this->types);
339    }
340
341    public function setTypes(array $types): self
342    {
343        $this->types = $types;
344
345        return $this;
346    }
347
348    public function setAttributesFromSerializedJson(array $attributes): void
349    {
350        foreach ($attributes as $attribute) {
351            $this->attributes->add(self::instanceFromSerializedJson($this->generator, $attribute)->setOwner($this));
352        }
353    }
354
355    public function setValuesFromSerializedJson(array $values): void
356    {
357        foreach ($values as $value) {
358            $this->values->add(self::instanceFromSerializedJson($this->generator, $value)->setOwner($this));
359        }
360    }
361
362    protected function getAllAttributes(bool $includeInheritanceAttributes, bool $requiredFirst): StructAttributeContainer
363    {
364        $allAttributes = new StructAttributeContainer($this->getGenerator());
365        if ($includeInheritanceAttributes) {
366            $this->addInheritanceAttributes($allAttributes);
367        }
368
369        foreach ($this->attributes as $attribute) {
370            $allAttributes->add($attribute);
371        }
372
373        if ($requiredFirst) {
374            $attributes = $this->putRequiredAttributesFirst($allAttributes);
375        } else {
376            $attributes = $allAttributes;
377        }
378
379        return $attributes;
380    }
381
382    protected function addInheritanceAttributes(StructAttributeContainer $attributes): void
383    {
384        if (!empty($this->getInheritance()) && ($model = $this->getInheritanceStruct()) instanceof Struct) {
385            while ($model instanceof Struct && $model->isStruct()) {
386                foreach ($model->getAttributes() as $attribute) {
387                    $attributes->add($attribute);
388                }
389                $model = $model->getInheritanceStruct();
390            }
391        }
392    }
393
394    protected function putRequiredAttributesFirst(StructAttributeContainer $allAttributes): StructAttributeContainer
395    {
396        $attributes = new StructAttributeContainer($this->getGenerator());
397        $requiredAttributes = [];
398        $notRequiredAttributes = [];
399        $nullableNotRequiredAttributes = [];
400
401        /** @var StructAttribute $attribute */
402        foreach ($allAttributes as $attribute) {
403            if ($attribute->isRequired() && !$attribute->isNullable()) {
404                $requiredAttributes[] = $attribute;
405            } elseif (!$attribute->isNullable()) {
406                $notRequiredAttributes[] = $attribute;
407            } else {
408                $nullableNotRequiredAttributes[] = $attribute;
409            }
410        }
411
412        array_walk($requiredAttributes, [$attributes, 'add']);
413        array_walk($notRequiredAttributes, [$attributes, 'add']);
414        array_walk($nullableNotRequiredAttributes, [$attributes, 'add']);
415
416        unset($requiredAttributes, $notRequiredAttributes, $nullableNotRequiredAttributes);
417
418        return $attributes;
419    }
420
421    protected function setValues(StructValueContainer $structValueContainer): self
422    {
423        $this->values = $structValueContainer;
424
425        return $this;
426    }
427
428    protected function toJsonSerialize(): array
429    {
430        return [
431            'attributes' => $this->attributes,
432            'restriction' => $this->isRestriction,
433            'struct' => $this->isStruct,
434            'types' => $this->types,
435            'values' => $this->values,
436            'list' => $this->list,
437        ];
438    }
439}