Passed
Pull Request — develop (#234)
by Mikaël
49:21 queued 46:19
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 522
    public function __construct(Generator $generator, $name)
70
    {
71 522
        parent::__construct($generator);
72 522
        $this->setName($name);
73 522
    }
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 526
    public function getName()
171
    {
172 526
        return $this->name;
173
    }
174
175 522
    public function setName($name): self
176
    {
177 522
        $this->name = $name;
178
179 522
        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 46
    public function getContextualPart(): string
242
    {
243 46
        return '';
244
    }
245
246 22
    public function getExtends(bool $short = false): ?string
247
    {
248 22
        return '';
249
    }
250
251 214
    public function getNamespace(): string
252
    {
253 214
        $namespaces = [];
254 214
        $namespace = $this->getGenerator()->getOptionNamespacePrefix();
255
256 214
        if (empty($namespace)) {
257 210
            if (!empty($this->getGenerator()->getOptionPrefix())) {
258 182
                $namespaces[] = $this->getGenerator()->getOptionPrefix();
259 28
            } elseif (!empty($this->getGenerator()->getOptionSuffix())) {
260 210
                $namespaces[] = $this->getGenerator()->getOptionSuffix();
261
            }
262
        } else {
263 4
            $namespaces[] = $namespace;
264
        }
265
266 214
        if (!empty($this->getSubDirectory())) {
267 204
            $namespaces[] = $this->getSubDirectory();
268
        }
269
270 214
        return implode('\\', $namespaces);
271
    }
272
273 226
    public function getSubDirectory(): string
274
    {
275 226
        $subDirectory = '';
276 226
        if (GeneratorOptions::VALUE_CAT === $this->getGenerator()->getOptionCategory()) {
277 224
            $subDirectory = $this->getContextualPart();
278
        }
279
280 226
        return $subDirectory;
281
    }
282
283
    /**
284
     * Returns the sub package name which the model belongs to
285
     * Must be overridden by sub classes.
286
     */
287 2
    public function getDocSubPackages(): array
288
    {
289 2
        return [];
290
    }
291
292 410
    public static function cleanString(string $string, bool $keepMultipleUnderscores = true): string
293
    {
294 410
        return GeneratorUtils::cleanString($string, $keepMultipleUnderscores);
295
    }
296
297 348
    public static function replacePhpReservedKeyword(string $keyword, ?string $context = null): string
298
    {
299 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

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