Passed
Push — feature/issue-124 ( 167236...6b1eff )
by Mikaël
07:01
created

AbstractModel::mergeMeta()   B

Complexity

Conditions 10
Paths 14

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 10.0512

Importance

Changes 0
Metric Value
cc 10
eloc 25
nc 14
nop 0
dl 0
loc 39
ccs 23
cts 25
cp 0.92
crap 10.0512
rs 7.6666
c 0
b 0
f 0

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 167
    public function getExtendsClassName(): string
76
    {
77 167
        $extends = '';
78 167
        if (($model = $this->getInheritedModel()) instanceof Struct && $model->isStruct()) {
79 14
            $extends = $model->getPackagedName($model->isRestriction());
80
        }
81 167
        if (empty($extends)) {
82 157
            $extends = $this->getExtends(true);
83
        }
84
85 167
        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 167
    public function getInheritedModel(): ?Struct
101
    {
102 167
        return $this->getGenerator()->getStructByName($this->getInheritance());
103
    }
104
105 233
    public function getMeta(): array
106
    {
107 233
        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 181
    public function getMetaValue(string $metaName, $fallback = null)
152
    {
153 181
        $meta = $this->getMeta();
154
155 181
        return array_key_exists($metaName, $meta) ? $meta[$metaName] : $fallback;
156
    }
157
158 121
    public function getMetaValueFirstSet(array $names, $fallback = null)
159
    {
160 121
        $meta = $this->getMeta();
161 121
        foreach ($names as $name) {
162 121
            if (array_key_exists($name, $meta)) {
163 89
                return $meta[$name];
164
            }
165
        }
166
167 99
        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 169
    public function isAbstract(): bool
200
    {
201 169
        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 109
    public function nameIsClean(): bool
212
    {
213 109
        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 155
            $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 47
    public function getContextualPart(): string
242
    {
243 47
        return '';
244
    }
245
246 23
    public function getExtends(bool $short = false): ?string
247
    {
248 23
        return '';
249
    }
250
251 215
    public function getNamespace(): string
252
    {
253 215
        $namespaces = [];
254 215
        $namespace = $this->getGenerator()->getOptionNamespacePrefix();
255
256 215
        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 5
            $namespaces[] = $namespace;
264
        }
265
266 215
        if (!empty($this->getSubDirectory())) {
267 204
            $namespaces[] = $this->getSubDirectory();
268
        }
269
270 215
        return implode('\\', $namespaces);
271
    }
272
273 227
    public function getSubDirectory(): string
274
    {
275 227
        $subDirectory = '';
276 227
        if (GeneratorOptions::VALUE_CAT === $this->getGenerator()->getOptionCategory()) {
277 224
            $subDirectory = $this->getContextualPart();
278
        }
279
280 227
        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 187
    protected function mergeMeta(): array
393
    {
394 187
        $meta = func_get_args();
395 187
        $mergedMeta = [];
396 187
        $metaDocumentation = [];
397
        // gather meta
398 187
        foreach ($meta as $metaItem) {
399 187
            foreach ($metaItem as $metaName => $metaValue) {
400 149
                if (self::META_DOCUMENTATION === $metaName) {
401 42
                    $metaDocumentation = array_merge($metaDocumentation, $metaValue);
402 141
                } elseif (!array_key_exists($metaName, $mergedMeta)) {
403 141
                    $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 187
        ksort($mergedMeta);
416
417
        // add documentation if any at first position
418 187
        if (!empty($metaDocumentation)) {
419 21
            $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 187
        unset($meta, $metaDocumentation);
429
430 187
        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