AbstractModel::mergeMeta()   B
last analyzed

Complexity

Conditions 10
Paths 14

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 10.0454

Importance

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