Passed
Push — master ( 2952f7...fd05cd )
by Mikaël
87:30 queued 83:30
created

AbstractModel::instanceFromSerializedJson()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 12
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 17
ccs 13
cts 13
cp 1
crap 4
rs 9.8666
1
<?php
2
3
declare(strict_types=1);
4
5
namespace WsdlToPhp\PackageGenerator\Model;
6
7
use InvalidArgumentException;
8
use JsonSerializable;
9
use WsdlToPhp\PackageGenerator\ConfigurationReader\AbstractReservedWord;
10
use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
11
use WsdlToPhp\PackageGenerator\ConfigurationReader\PhpReservedKeyword;
12
use WsdlToPhp\PackageGenerator\Generator\AbstractGeneratorAware;
13
use WsdlToPhp\PackageGenerator\Generator\Generator;
14
use WsdlToPhp\PackageGenerator\Generator\Utils as GeneratorUtils;
15
16
/**
17
 * Class AbstractModel defines the basic properties and methods to operations and structs extracted from the WSDL.
18
 */
19
abstract class AbstractModel extends AbstractGeneratorAware implements JsonSerializable
20
{
21
    public const META_DOCUMENTATION = 'documentation';
22
23
    /**
24
     * Original name od the element.
25
     *
26
     * @var mixed
27
     */
28
    protected $name;
29
30
    /**
31
     * Values associated to the operation.
32
     *
33
     * @var string[]
34
     */
35
    protected array $meta = [];
36
37
    /**
38
     * Define the inheritance of a struct by the name of the parent struct or type.
39
     */
40
    protected string $inheritance = '';
41
42
    /**
43
     * Store the object which owns the current model.
44
     */
45
    protected ?AbstractModel $owner = null;
46
47
    /**
48
     * Indicates that the current element is an abstract element.
49
     * It allows to generated an abstract class.
50
     * This will happen for element/complexType that are defined with abstract="true".
51
     */
52
    protected bool $isAbstract = false;
53
54
    /**
55
     * Replaced keywords time in order to generate unique new keyword.
56
     */
57
    protected static array $replacedPhpReservedKeywords = [];
58
59
    /**
60
     * Replaced methods time in order to generate unique new method.
61
     */
62
    protected array $replacedReservedMethods = [];
63
64
    /**
65
     * Unique name generated in order to ensure unique naming (for struct constructor and setters/getters even for different case attribute name with same value).
66
     */
67
    protected static array $uniqueNames = [];
68
69 524
    public function __construct(Generator $generator, $name)
70
    {
71 524
        parent::__construct($generator);
72 524
        $this->setName($name);
73 524
    }
74
75 166
    public function getExtendsClassName(): string
76
    {
77 166
        $extends = '';
78 166
        if (($model = $this->getInheritedModel()) instanceof Struct && $model->isStruct()) {
79 14
            $extends = $model->getPackagedName($model->isRestriction());
80
        }
81 166
        if (empty($extends)) {
82 156
            $extends = $this->getExtends(true);
83
        }
84
85 166
        return $extends;
86
    }
87
88 388
    public function getInheritance(): string
89
    {
90 388
        return $this->inheritance;
91
    }
92
93 312
    public function setInheritance(string $inheritance = ''): self
94
    {
95 312
        $this->inheritance = $inheritance;
96
97 312
        return $this;
98
    }
99
100 166
    public function getInheritedModel(): ?Struct
101
    {
102 166
        return $this->getGenerator()->getStructByName($this->getInheritance());
103
    }
104
105 232
    public function getMeta(): array
106
    {
107 232
        return $this->meta;
108
    }
109
110 202
    public function setMeta(array $meta = []): self
111
    {
112 202
        $this->meta = $meta;
113
114 202
        return $this;
115
    }
116
117 76
    public function addMeta(string $metaName, $metaValue): self
118
    {
119 76
        if (!is_scalar($metaName) || (!is_scalar($metaValue) && !is_array($metaValue))) {
0 ignored issues
show
introduced by
The condition is_scalar($metaName) is always true.
Loading history...
120 2
            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__);
121
        }
122 74
        $metaValue = is_scalar($metaValue) ? ((is_numeric($metaValue) || is_bool($metaValue) ? $metaValue : trim($metaValue))) : $metaValue;
123 74
        if (is_scalar($metaValue) || is_array($metaValue)) {
124 74
            if (!array_key_exists($metaName, $this->meta)) {
125 72
                $this->meta[$metaName] = $metaValue;
126 22
            } elseif (is_array($this->meta[$metaName]) && is_array($metaValue)) {
0 ignored issues
show
introduced by
The condition is_array($metaValue) is always false.
Loading history...
127 10
                $this->meta[$metaName] = array_merge($this->meta[$metaName], $metaValue);
128 12
            } elseif (is_array($this->meta[$metaName])) {
129 8
                array_push($this->meta[$metaName], $metaValue);
130 10
            } elseif (array_key_exists($metaName, $this->meta) && $metaValue !== $this->meta[$metaName]) {
131 6
                $this->meta[$metaName] = [
132 6
                    $this->meta[$metaName],
133 6
                    $metaValue,
134
                ];
135
            } else {
136 6
                $this->meta[$metaName] = $metaValue;
137
            }
138 74
            ksort($this->meta);
139
        }
140
141 74
        return $this;
142
    }
143
144 12
    public function setDocumentation(string $documentation): self
145
    {
146 12
        return $this->addMeta(self::META_DOCUMENTATION, is_array($documentation) ? $documentation : [
0 ignored issues
show
introduced by
The condition is_array($documentation) is always false.
Loading history...
147 12
            $documentation,
148
        ]);
149
    }
150
151 180
    public function getMetaValue(string $metaName, $fallback = null)
152
    {
153 180
        $meta = $this->getMeta();
154
155 180
        return array_key_exists($metaName, $meta) ? $meta[$metaName] : $fallback;
156
    }
157
158 120
    public function getMetaValueFirstSet(array $names, $fallback = null)
159
    {
160 120
        $meta = $this->getMeta();
161 120
        foreach ($names as $name) {
162 120
            if (array_key_exists($name, $meta)) {
163 88
                return $meta[$name];
164
            }
165
        }
166
167 98
        return $fallback;
168
    }
169
170 528
    public function getName()
171
    {
172 528
        return $this->name;
173
    }
174
175 524
    public function setName($name): self
176
    {
177 524
        $this->name = $name;
178
179 524
        return $this;
180
    }
181
182 408
    public function getCleanName(bool $keepMultipleUnderscores = true): string
183
    {
184 408
        return self::cleanString($this->getName(), $keepMultipleUnderscores);
185
    }
186
187 426
    public function getOwner(): ?AbstractModel
188
    {
189 426
        return $this->owner;
190
    }
191
192 434
    public function setOwner(?AbstractModel $owner = null): self
193
    {
194 434
        $this->owner = $owner;
195
196 434
        return $this;
197
    }
198
199 168
    public function isAbstract(): bool
200
    {
201 168
        return $this->isAbstract;
202
    }
203
204 200
    public function setAbstract(bool $isAbstract): self
205
    {
206 200
        $this->isAbstract = $isAbstract;
207
208 200
        return $this;
209
    }
210
211 108
    public function nameIsClean(): bool
212
    {
213 108
        return '' !== $this->getName() && $this->getName() === $this->getCleanName();
214
    }
215
216 400
    public function getPackagedName(bool $namespaced = false): string
217
    {
218 400
        $nameParts = [];
219 400
        if ($namespaced && !empty($this->getNamespace())) {
220 154
            $nameParts[] = sprintf('\%s\\', $this->getNamespace());
221
        }
222
223 400
        $cleanName = $this->getCleanName();
224 400
        if (!empty($this->getGenerator()->getOptionPrefix())) {
225 280
            $nameParts[] = $this->getGenerator()->getOptionPrefix();
226
        } else {
227 138
            $cleanName = self::replacePhpReservedKeyword($cleanName);
228
        }
229
230 400
        $nameParts[] = ucfirst(self::uniqueName($cleanName, $this->getContextualPart()));
231 400
        if (!empty($this->getGenerator()->getOptionSuffix())) {
232 12
            $nameParts[] = $this->getGenerator()->getOptionSuffix();
233
        }
234
235 400
        return implode('', $nameParts);
236
    }
237
238
    /**
239
     * Allows to define the contextual part of the class name for the package.
240
     */
241 48
    public function getContextualPart(): string
242
    {
243 48
        return '';
244
    }
245
246 22
    public function getExtends(bool $short = false): ?string
247
    {
248 22
        return '';
249
    }
250
251 216
    public function getNamespace(): string
252
    {
253 216
        $namespaces = [];
254 216
        $namespace = $this->getGenerator()->getOptionNamespacePrefix();
255
256 216
        if (!empty($namespace)) {
257 4
            $namespaces[] = $namespace;
258
        }
259
260 216
        if (!empty($this->getSubDirectory())) {
261 204
            $namespaces[] = $this->getSubDirectory();
262
        }
263
264 216
        return implode('\\', $namespaces);
265
    }
266
267 228
    public function getSubDirectory(): string
268
    {
269 228
        $subDirectory = '';
270 228
        if (GeneratorOptions::VALUE_CAT === $this->getGenerator()->getOptionCategory()) {
271 226
            $subDirectory = $this->getContextualPart();
272
        }
273
274 228
        return $subDirectory;
275
    }
276
277
    /**
278
     * Returns the sub package name which the model belongs to
279
     * Must be overridden by sub classes.
280
     */
281 2
    public function getDocSubPackages(): array
282
    {
283 2
        return [];
284
    }
285
286 410
    public static function cleanString(string $string, bool $keepMultipleUnderscores = true): string
287
    {
288 410
        return GeneratorUtils::cleanString($string, $keepMultipleUnderscores);
289
    }
290
291 348
    public static function replacePhpReservedKeyword(string $keyword, ?string $context = null): string
292
    {
293 348
        if (PhpReservedKeyword::instance()->is($keyword)) {
0 ignored issues
show
Bug introduced by
The method is() does not exist on WsdlToPhp\PackageGenerat...ader\AbstractYamlReader. It seems like you code against a sub-type of WsdlToPhp\PackageGenerat...ader\AbstractYamlReader such as WsdlToPhp\PackageGenerat...er\AbstractReservedWord. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

293
        if (PhpReservedKeyword::instance()->/** @scrutinizer ignore-call */ is($keyword)) {
Loading history...
294 74
            if (!is_null($context)) {
295 38
                $keywordKey = $keyword.'_'.$context;
296 38
                if (!array_key_exists($keywordKey, self::$replacedPhpReservedKeywords)) {
297 24
                    self::$replacedPhpReservedKeywords[$keywordKey] = 0;
298
                } else {
299 14
                    ++self::$replacedPhpReservedKeywords[$keywordKey];
300
                }
301
302 38
                return '_'.$keyword.(self::$replacedPhpReservedKeywords[$keywordKey] ? '_'.self::$replacedPhpReservedKeywords[$keywordKey] : '');
303
            }
304
305 58
            return '_'.$keyword;
306
        }
307
308 348
        return $keyword;
309
    }
310
311 2
    public function getReservedMethodsInstance(): AbstractReservedWord
312
    {
313 2
        throw new InvalidArgumentException(sprintf('The method %s should be defined in the class %s', __FUNCTION__, get_called_class(), __LINE__));
314
    }
315
316 372
    public function replaceReservedMethod(string $methodName, ?string $context = null): string
317
    {
318 372
        if ($this->getReservedMethodsInstance()->is($methodName)) {
319 6
            if (!is_null($context)) {
320 6
                $methodKey = $methodName.'_'.$context;
321 6
                if (!array_key_exists($methodKey, $this->replacedReservedMethods)) {
322 6
                    $this->replacedReservedMethods[$methodKey] = 0;
323
                } else {
324
                    ++$this->replacedReservedMethods[$methodKey];
325
                }
326
327 6
                return '_'.$methodName.($this->replacedReservedMethods[$methodKey] ? '_'.$this->replacedReservedMethods[$methodKey] : '');
328
            }
329
330 2
            return '_'.$methodName;
331
        }
332
333 368
        return $methodName;
334
    }
335
336
    /**
337
     * Gives the availability for test purpose and multiple package generation to purge unique names.
338
     */
339 402
    public static function purgeUniqueNames()
340
    {
341 402
        self::$uniqueNames = [];
342 402
    }
343
344
    /**
345
     * Gives the availability for test purpose and multiple package generation to purge reserved keywords usage.
346
     */
347 390
    public static function purgePhpReservedKeywords()
348
    {
349 390
        self::$replacedPhpReservedKeywords = [];
350 390
    }
351
352 6
    public function jsonSerialize(): array
353
    {
354 6
        return array_merge($this->toJsonSerialize(), [
355 6
            'inheritance' => $this->inheritance,
356 6
            'abstract' => $this->isAbstract,
357 6
            'meta' => $this->meta,
358 6
            'name' => $this->name,
359 6
            '__CLASS__' => get_called_class(),
360
        ]);
361
    }
362
363 198
    public static function instanceFromSerializedJson(Generator $generator, array $args): self
364
    {
365 198
        self::checkSerializedJson($args);
366 198
        $class = $args['__CLASS__'];
367 198
        $instance = new $class($generator, $args['name']);
368 198
        unset($args['name'], $args['__CLASS__']);
369 198
        foreach ($args as $arg => $value) {
370 198
            $setFromSerializedJson = sprintf('set%sFromSerializedJson', ucfirst($arg));
371 198
            $set = sprintf('set%s', ucfirst($arg));
372 198
            if (method_exists($instance, $setFromSerializedJson)) {
373 198
                $instance->{$setFromSerializedJson}($value);
374 198
            } elseif (method_exists($instance, $set)) {
375 198
                $instance->{$set}($value);
376
            }
377
        }
378
379 198
        return $instance;
380
    }
381
382
    /**
383
     * Allows to merge meta from different sources and ensure consistency of their order
384
     * Must be passed as less important (at first position) to most important (last position).
385
     */
386 186
    protected function mergeMeta(): array
387
    {
388 186
        $meta = func_get_args();
389 186
        $mergedMeta = [];
390 186
        $metaDocumentation = [];
391
        // gather meta
392 186
        foreach ($meta as $metaItem) {
393 186
            foreach ($metaItem as $metaName => $metaValue) {
394 148
                if (self::META_DOCUMENTATION === $metaName) {
395 42
                    $metaDocumentation = array_merge($metaDocumentation, $metaValue);
396 140
                } elseif (!array_key_exists($metaName, $mergedMeta)) {
397 140
                    $mergedMeta[$metaName] = $metaValue;
398 10
                } elseif (is_array($mergedMeta[$metaName]) && is_array($metaValue)) {
399
                    $mergedMeta[$metaName] = array_merge($mergedMeta[$metaName], $metaValue);
400 10
                } elseif (is_array($mergedMeta[$metaName])) {
401
                    $mergedMeta[$metaName][] = $metaValue;
402
                } else {
403 10
                    $mergedMeta[$metaName] = $metaValue;
404
                }
405
            }
406
        }
407
408
        // sort by key
409 186
        ksort($mergedMeta);
410
411
        // add documentation if any at first position
412 186
        if (!empty($metaDocumentation)) {
413
            $definitiveMeta = [
414 42
                self::META_DOCUMENTATION => array_unique(array_reverse($metaDocumentation)),
415
            ];
416 42
            foreach ($mergedMeta as $metaName => $metaValue) {
417 34
                $definitiveMeta[$metaName] = $metaValue;
418
            }
419 42
            $mergedMeta = $definitiveMeta;
420 42
            unset($definitiveMeta);
421
        }
422 186
        unset($meta, $metaDocumentation);
423
424 186
        return $mergedMeta;
425
    }
426
427
    /**
428
     * Static method which returns a unique name case sensitively
429
     * Useful to name methods case sensitively distinct, see http://the-echoplex.net/log/php-case-sensitivity.
430
     *
431
     * @param string $name    the original name
432
     * @param string $context the context where the name is needed unique
433
     */
434 404
    protected static function uniqueName(string $name, string $context): string
435
    {
436 404
        $insensitiveKey = mb_strtolower($name.'_'.$context);
437 404
        $sensitiveKey = $name.'_'.$context;
438 404
        if (array_key_exists($sensitiveKey, self::$uniqueNames)) {
439 374
            return self::$uniqueNames[$sensitiveKey];
440
        }
441
442 348
        if (!array_key_exists($insensitiveKey, self::$uniqueNames)) {
443 346
            self::$uniqueNames[$insensitiveKey] = 0;
444
        } else {
445 18
            ++self::$uniqueNames[$insensitiveKey];
446
        }
447
448 348
        $uniqueName = $name.(self::$uniqueNames[$insensitiveKey] ? '_'.self::$uniqueNames[$insensitiveKey] : '');
449 348
        self::$uniqueNames[$sensitiveKey] = $uniqueName;
450
451 348
        return $uniqueName;
452
    }
453
454
    /**
455
     * Must return the properties of the inherited class.
456
     */
457
    abstract protected function toJsonSerialize(): array;
458
459 198
    protected static function checkSerializedJson(array $args): void
460
    {
461 198
        if (!array_key_exists('__CLASS__', $args)) {
462 2
            throw new InvalidArgumentException(sprintf('__CLASS__ key is missing from "%s"', var_export($args, true)));
463
        }
464
465 198
        if (!class_exists($args['__CLASS__'])) {
466 2
            throw new InvalidArgumentException(sprintf('Class "%s" is unknown', $args['__CLASS__']));
467
        }
468
469 198
        if (!array_key_exists('name', $args)) {
470 2
            throw new InvalidArgumentException(sprintf('name key is missing from "%s"', var_export($args, true)));
471
        }
472 198
    }
473
}
474