Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.14% covered (success)
98.14%
158 / 161
94.44% covered (success)
94.44%
34 / 36
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractModel
98.14% covered (success)
98.14%
158 / 161
94.44% covered (success)
94.44%
34 / 36
91
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getExtendsClassName
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getInheritance
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setInheritance
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getInheritedModel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMeta
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMeta
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addMeta
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
15
 setDocumentation
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getMetaValue
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getMetaValueFirstSet
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setName
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getCleanName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOwner
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setOwner
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isAbstract
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setAbstract
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 nameIsClean
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getPackagedName
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 getContextualPart
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExtends
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNamespace
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getSubDirectory
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getDocSubPackages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 cleanString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 replacePhpReservedKeyword
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 getReservedMethodsInstance
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 replaceReservedMethod
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
5.03
 purgeUniqueNames
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 purgePhpReservedKeywords
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 jsonSerialize
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 instanceFromSerializedJson
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 mergeMeta
92.00% covered (success)
92.00%
23 / 25
0.00% covered (danger)
0.00%
0 / 1
10.05
 uniqueName
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 toJsonSerialize
n/a
0 / 0
n/a
0 / 0
0
 checkSerializedJson
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace WsdlToPhp\PackageGenerator\Model;
6
7use WsdlToPhp\PackageGenerator\ConfigurationReader\AbstractReservedWord;
8use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
9use WsdlToPhp\PackageGenerator\ConfigurationReader\PhpReservedKeyword;
10use WsdlToPhp\PackageGenerator\Generator\AbstractGeneratorAware;
11use WsdlToPhp\PackageGenerator\Generator\Generator;
12use WsdlToPhp\PackageGenerator\Generator\Utils as GeneratorUtils;
13
14/**
15 * Class AbstractModel defines the basic properties and methods to operations and structs extracted from the WSDL.
16 */
17abstract class AbstractModel extends AbstractGeneratorAware implements \JsonSerializable
18{
19    public const META_DOCUMENTATION = 'documentation';
20
21    /**
22     * Original name of the element.
23     *
24     * @var mixed
25     */
26    protected $name;
27
28    /**
29     * Values associated to the operation.
30     *
31     * @var string[]
32     */
33    protected array $meta = [];
34
35    /**
36     * Define the inheritance of a struct by the name of the parent struct or type.
37     */
38    protected string $inheritance = '';
39
40    /**
41     * Store the object which owns the current model.
42     */
43    protected ?AbstractModel $owner = null;
44
45    /**
46     * Indicates that the current element is an abstract element.
47     * It allows to generated an abstract class.
48     * This will happen for element/complexType that are defined with abstract="true".
49     */
50    protected bool $isAbstract = false;
51
52    /**
53     * Replaced keywords time in order to generate unique new keyword.
54     */
55    protected static array $replacedPhpReservedKeywords = [];
56
57    /**
58     * Replaced methods time in order to generate unique new method.
59     */
60    protected array $replacedReservedMethods = [];
61
62    /**
63     * Unique name generated in order to ensure unique naming (for struct constructor and setters/getters even for different case attribute name with same value).
64     */
65    protected static array $uniqueNames = [];
66
67    public function __construct(Generator $generator, $name)
68    {
69        parent::__construct($generator);
70        $this->setName($name);
71    }
72
73    public function getExtendsClassName(): string
74    {
75        $extends = '';
76        if (($model = $this->getInheritedModel()) instanceof Struct && $model->isStruct()) {
77            $extends = $model->getPackagedName($model->isRestriction());
78        }
79        if (empty($extends)) {
80            $extends = $this->getExtends(true);
81        }
82
83        return $extends;
84    }
85
86    public function getInheritance(): string
87    {
88        return $this->inheritance;
89    }
90
91    public function setInheritance(string $inheritance = ''): self
92    {
93        $this->inheritance = $inheritance;
94
95        return $this;
96    }
97
98    public function getInheritedModel(): ?Struct
99    {
100        return $this->getGenerator()->getStructByName($this->getInheritance());
101    }
102
103    public function getMeta(): array
104    {
105        return $this->meta;
106    }
107
108    public function setMeta(array $meta = []): self
109    {
110        $this->meta = $meta;
111
112        return $this;
113    }
114
115    /**
116     * @param mixed $metaValue
117     * @throws \InvalidArgumentException
118     */
119    public function addMeta(string $metaName, $metaValue): self
120    {
121        if (!is_scalar($metaName) || (!is_scalar($metaValue) && !is_array($metaValue))) {
122            throw new \InvalidArgumentException(sprintf('Invalid meta name "%s" or value "%s". Please provide scalar meta name and scalar or array meta value.', gettype($metaName), gettype($metaValue)), __LINE__);
123        }
124        $metaValue = is_scalar($metaValue) ? (is_numeric($metaValue) || is_bool($metaValue) ? $metaValue : trim($metaValue)) : $metaValue;
125        if (is_scalar($metaValue) || is_array($metaValue)) {
126            if (!array_key_exists($metaName, $this->meta)) {
127                $this->meta[$metaName] = $metaValue;
128            } elseif (is_array($this->meta[$metaName]) && is_array($metaValue)) {
129                $this->meta[$metaName] = array_merge($this->meta[$metaName], $metaValue);
130            } elseif (is_array($this->meta[$metaName])) {
131                array_push($this->meta[$metaName], $metaValue);
132            } elseif (array_key_exists($metaName, $this->meta) && $metaValue !== $this->meta[$metaName]) {
133                $this->meta[$metaName] = [
134                    $this->meta[$metaName],
135                    $metaValue,
136                ];
137            } else {
138                $this->meta[$metaName] = $metaValue;
139            }
140            ksort($this->meta);
141        }
142
143        return $this;
144    }
145
146    public function setDocumentation(string $documentation): self
147    {
148        return $this->addMeta(self::META_DOCUMENTATION, is_array($documentation) ? $documentation : [
149            $documentation,
150        ]);
151    }
152
153    public function getMetaValue(string $metaName, $fallback = null)
154    {
155        $meta = $this->getMeta();
156
157        return array_key_exists($metaName, $meta) ? $meta[$metaName] : $fallback;
158    }
159
160    public function getMetaValueFirstSet(array $names, $fallback = null)
161    {
162        $meta = $this->getMeta();
163        foreach ($names as $name) {
164            if (array_key_exists($name, $meta)) {
165                return $meta[$name];
166            }
167        }
168
169        return $fallback;
170    }
171
172    public function getName()
173    {
174        return $this->name;
175    }
176
177    public function setName($name): self
178    {
179        $this->name = $name;
180
181        return $this;
182    }
183
184    public function getCleanName(bool $keepMultipleUnderscores = true): string
185    {
186        return self::cleanString($this->getName(), $keepMultipleUnderscores);
187    }
188
189    public function getOwner(): ?AbstractModel
190    {
191        return $this->owner;
192    }
193
194    public function setOwner(?AbstractModel $owner = null): self
195    {
196        $this->owner = $owner;
197
198        return $this;
199    }
200
201    public function isAbstract(): bool
202    {
203        return $this->isAbstract;
204    }
205
206    public function setAbstract(bool $isAbstract): self
207    {
208        $this->isAbstract = $isAbstract;
209
210        return $this;
211    }
212
213    public function nameIsClean(): bool
214    {
215        return '' !== $this->getName() && $this->getName() === $this->getCleanName();
216    }
217
218    public function getPackagedName(bool $namespaced = false): string
219    {
220        $nameParts = [];
221        if ($namespaced && !empty($this->getNamespace())) {
222            $nameParts[] = sprintf('\%s\\', $this->getNamespace());
223        }
224
225        $cleanName = $this->getCleanName();
226        if (!empty($this->getGenerator()->getOptionPrefix())) {
227            $nameParts[] = $this->getGenerator()->getOptionPrefix();
228        } else {
229            $cleanName = self::replacePhpReservedKeyword($cleanName);
230        }
231
232        $nameParts[] = ucfirst(self::uniqueName($cleanName, $this->getContextualPart()));
233        if (!empty($this->getGenerator()->getOptionSuffix())) {
234            $nameParts[] = $this->getGenerator()->getOptionSuffix();
235        }
236
237        return implode('', $nameParts);
238    }
239
240    /**
241     * Allows to define the contextual part of the class name for the package.
242     */
243    public function getContextualPart(): string
244    {
245        return '';
246    }
247
248    public function getExtends(bool $short = false): ?string
249    {
250        return '';
251    }
252
253    public function getNamespace(): string
254    {
255        $namespaces = [];
256        $namespace = $this->getGenerator()->getOptionNamespacePrefix();
257
258        if (!empty($namespace)) {
259            $namespaces[] = $namespace;
260        }
261
262        if (!empty($this->getSubDirectory())) {
263            $namespaces[] = str_replace('/', '\\', $this->getSubDirectory());
264        }
265
266        return implode('\\', $namespaces);
267    }
268
269    public function getSubDirectory(): string
270    {
271        $subDirectory = '';
272        if (GeneratorOptions::VALUE_CAT === $this->getGenerator()->getOptionCategory()) {
273            $subDirectory = $this->getContextualPart();
274        }
275
276        return $subDirectory;
277    }
278
279    /**
280     * Returns the sub package name which the model belongs to
281     * Must be overridden by sub classes.
282     */
283    public function getDocSubPackages(): array
284    {
285        return [];
286    }
287
288    public static function cleanString(string $string, bool $keepMultipleUnderscores = true): string
289    {
290        return GeneratorUtils::cleanString($string, $keepMultipleUnderscores);
291    }
292
293    public static function replacePhpReservedKeyword(string $keyword, ?string $context = null): string
294    {
295        if (PhpReservedKeyword::instance()->is($keyword)) {
296            if (!is_null($context)) {
297                $keywordKey = $keyword.'_'.$context;
298                if (!array_key_exists($keywordKey, self::$replacedPhpReservedKeywords)) {
299                    self::$replacedPhpReservedKeywords[$keywordKey] = 0;
300                } else {
301                    ++self::$replacedPhpReservedKeywords[$keywordKey];
302                }
303
304                return '_'.$keyword.(self::$replacedPhpReservedKeywords[$keywordKey] ? '_'.self::$replacedPhpReservedKeywords[$keywordKey] : '');
305            }
306
307            return '_'.$keyword;
308        }
309
310        return $keyword;
311    }
312
313    /**
314     * @throws \InvalidArgumentException
315     */
316    public function getReservedMethodsInstance(): AbstractReservedWord
317    {
318        throw new \InvalidArgumentException(sprintf('The method %s should be defined in the class %s', __FUNCTION__, static::class));
319    }
320
321    public function replaceReservedMethod(string $methodName, ?string $context = null): string
322    {
323        if ($this->getReservedMethodsInstance()->is($methodName)) {
324            if (!is_null($context)) {
325                $methodKey = $methodName.'_'.$context;
326                if (!array_key_exists($methodKey, $this->replacedReservedMethods)) {
327                    $this->replacedReservedMethods[$methodKey] = 0;
328                } else {
329                    ++$this->replacedReservedMethods[$methodKey];
330                }
331
332                return '_'.$methodName.($this->replacedReservedMethods[$methodKey] ? '_'.$this->replacedReservedMethods[$methodKey] : '');
333            }
334
335            return '_'.$methodName;
336        }
337
338        return $methodName;
339    }
340
341    /**
342     * Gives the availability for test purpose and multiple package generation to purge unique names.
343     */
344    public static function purgeUniqueNames()
345    {
346        self::$uniqueNames = [];
347    }
348
349    /**
350     * Gives the availability for test purpose and multiple package generation to purge reserved keywords usage.
351     */
352    public static function purgePhpReservedKeywords()
353    {
354        self::$replacedPhpReservedKeywords = [];
355    }
356
357    public function jsonSerialize(): array
358    {
359        return array_merge($this->toJsonSerialize(), [
360            'inheritance' => $this->inheritance,
361            'abstract' => $this->isAbstract,
362            'meta' => $this->meta,
363            'name' => $this->name,
364            '__CLASS__' => static::class,
365        ]);
366    }
367
368    public static function instanceFromSerializedJson(Generator $generator, array $args): self
369    {
370        self::checkSerializedJson($args);
371        $class = $args['__CLASS__'];
372        $instance = new $class($generator, $args['name']);
373        unset($args['name'], $args['__CLASS__']);
374        foreach ($args as $arg => $value) {
375            $setFromSerializedJson = sprintf('set%sFromSerializedJson', ucfirst($arg));
376            $set = sprintf('set%s', ucfirst($arg));
377            if (method_exists($instance, $setFromSerializedJson)) {
378                $instance->{$setFromSerializedJson}($value);
379            } elseif (method_exists($instance, $set)) {
380                $instance->{$set}($value);
381            }
382        }
383
384        return $instance;
385    }
386
387    /**
388     * Allows to merge meta from different sources and ensure consistency of their order
389     * Must be passed as less important (at first position) to most important (last position).
390     */
391    protected function mergeMeta(): array
392    {
393        $meta = func_get_args();
394        $mergedMeta = [];
395        $metaDocumentation = [];
396        // gather meta
397        foreach ($meta as $metaItem) {
398            foreach ($metaItem as $metaName => $metaValue) {
399                if (self::META_DOCUMENTATION === $metaName) {
400                    $metaDocumentation = array_merge($metaDocumentation, $metaValue);
401                } elseif (!array_key_exists($metaName, $mergedMeta)) {
402                    $mergedMeta[$metaName] = $metaValue;
403                } elseif (is_array($mergedMeta[$metaName]) && is_array($metaValue)) {
404                    $mergedMeta[$metaName] = array_merge($mergedMeta[$metaName], $metaValue);
405                } elseif (is_array($mergedMeta[$metaName])) {
406                    $mergedMeta[$metaName][] = $metaValue;
407                } else {
408                    $mergedMeta[$metaName] = $metaValue;
409                }
410            }
411        }
412
413        // sort by key
414        ksort($mergedMeta);
415
416        // add documentation if any at first position
417        if (!empty($metaDocumentation)) {
418            $definitiveMeta = [
419                self::META_DOCUMENTATION => array_unique(array_reverse($metaDocumentation)),
420            ];
421            foreach ($mergedMeta as $metaName => $metaValue) {
422                $definitiveMeta[$metaName] = $metaValue;
423            }
424            $mergedMeta = $definitiveMeta;
425            unset($definitiveMeta);
426        }
427        unset($meta, $metaDocumentation);
428
429        return $mergedMeta;
430    }
431
432    /**
433     * Static method which returns a unique name case sensitively
434     * Useful to name methods case sensitively distinct, see http://the-echoplex.net/log/php-case-sensitivity.
435     *
436     * @param string $name the original name
437     * @param string $context the context where the name is needed unique
438     */
439    protected static function uniqueName(string $name, string $context): string
440    {
441        $insensitiveKey = mb_strtolower($name.'_'.$context);
442        $sensitiveKey = $name.'_'.$context;
443        if (array_key_exists($sensitiveKey, self::$uniqueNames)) {
444            return self::$uniqueNames[$sensitiveKey];
445        }
446
447        if (!array_key_exists($insensitiveKey, self::$uniqueNames)) {
448            self::$uniqueNames[$insensitiveKey] = 0;
449        } else {
450            ++self::$uniqueNames[$insensitiveKey];
451        }
452
453        $uniqueName = $name.(self::$uniqueNames[$insensitiveKey] ? '_'.self::$uniqueNames[$insensitiveKey] : '');
454        self::$uniqueNames[$sensitiveKey] = $uniqueName;
455
456        return $uniqueName;
457    }
458
459    /**
460     * Must return the properties of the inherited class.
461     */
462    abstract protected function toJsonSerialize(): array;
463
464    /**
465     * @throws \InvalidArgumentException
466     */
467    protected static function checkSerializedJson(array $args): void
468    {
469        if (!array_key_exists('__CLASS__', $args)) {
470            throw new \InvalidArgumentException(sprintf('__CLASS__ key is missing from "%s"', var_export($args, true)));
471        }
472
473        if (!class_exists($args['__CLASS__'])) {
474            throw new \InvalidArgumentException(sprintf('Class "%s" is unknown', $args['__CLASS__']));
475        }
476
477        if (!array_key_exists('name', $args)) {
478            throw new \InvalidArgumentException(sprintf('name key is missing from "%s"', var_export($args, true)));
479        }
480    }
481}