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

AbstractModel::mergeMeta()   B

Complexity

Conditions 10
Paths 14

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 10.0578

Importance

Changes 0
Metric Value
cc 10
eloc 25
c 0
b 0
f 0
nc 14
nop 0
dl 0
loc 39
ccs 22
cts 24
cp 0.9167
crap 10.0578
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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