Passed
Pull Request — develop (#279)
by Mikaël
10:56 queued 07:51
created

AbstractModel::mergeMeta()   B

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 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 of 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 546
    public function __construct(Generator $generator, $name)
70
    {
71 546
        parent::__construct($generator);
72 546
        $this->setName($name);
73
    }
74
75 172
    public function getExtendsClassName(): string
76
    {
77 172
        $extends = '';
78 172
        if (($model = $this->getInheritedModel()) instanceof Struct && $model->isStruct()) {
79 14
            $extends = $model->getPackagedName($model->isRestriction());
80
        }
81 172
        if (empty($extends)) {
82 162
            $extends = $this->getExtends(true);
83
        }
84
85 172
        return $extends;
86
    }
87
88 404
    public function getInheritance(): string
89
    {
90 404
        return $this->inheritance;
91
    }
92
93 326
    public function setInheritance(string $inheritance = ''): self
94
    {
95 326
        $this->inheritance = $inheritance;
96
97 326
        return $this;
98
    }
99
100 172
    public function getInheritedModel(): ?Struct
101
    {
102 172
        return $this->getGenerator()->getStructByName($this->getInheritance());
103
    }
104
105 244
    public function getMeta(): array
106
    {
107 244
        return $this->meta;
108
    }
109
110 216
    public function setMeta(array $meta = []): self
111
    {
112 216
        $this->meta = $meta;
113
114 216
        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 6
                ];
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 12
        ]);
149
    }
150
151 190
    public function getMetaValue(string $metaName, $fallback = null)
152
    {
153 190
        $meta = $this->getMeta();
154
155 190
        return array_key_exists($metaName, $meta) ? $meta[$metaName] : $fallback;
156
    }
157
158 124
    public function getMetaValueFirstSet(array $names, $fallback = null)
159
    {
160 124
        $meta = $this->getMeta();
161 124
        foreach ($names as $name) {
162 124
            if (array_key_exists($name, $meta)) {
163 90
                return $meta[$name];
164
            }
165
        }
166
167 104
        return $fallback;
168
    }
169
170 542
    public function getName()
171
    {
172 542
        return $this->name;
173
    }
174
175 546
    public function setName($name): self
176
    {
177 546
        $this->name = $name;
178
179 546
        return $this;
180
    }
181
182 422
    public function getCleanName(bool $keepMultipleUnderscores = true): string
183
    {
184 422
        return self::cleanString($this->getName(), $keepMultipleUnderscores);
185
    }
186
187 440
    public function getOwner(): ?AbstractModel
188
    {
189 440
        return $this->owner;
190
    }
191
192 448
    public function setOwner(?AbstractModel $owner = null): self
193
    {
194 448
        $this->owner = $owner;
195
196 448
        return $this;
197
    }
198
199 174
    public function isAbstract(): bool
200
    {
201 174
        return $this->isAbstract;
202
    }
203
204 214
    public function setAbstract(bool $isAbstract): self
205
    {
206 214
        $this->isAbstract = $isAbstract;
207
208 214
        return $this;
209
    }
210
211 112
    public function nameIsClean(): bool
212
    {
213 112
        return '' !== $this->getName() && $this->getName() === $this->getCleanName();
214
    }
215
216 414
    public function getPackagedName(bool $namespaced = false): string
217
    {
218 414
        $nameParts = [];
219 414
        if ($namespaced && !empty($this->getNamespace())) {
220 158
            $nameParts[] = sprintf('\%s\\', $this->getNamespace());
221
        }
222
223 414
        $cleanName = $this->getCleanName();
224 414
        if (!empty($this->getGenerator()->getOptionPrefix())) {
225 284
            $nameParts[] = $this->getGenerator()->getOptionPrefix();
226
        } else {
227 148
            $cleanName = self::replacePhpReservedKeyword($cleanName);
228
        }
229
230 414
        $nameParts[] = ucfirst(self::uniqueName($cleanName, $this->getContextualPart()));
231 414
        if (!empty($this->getGenerator()->getOptionSuffix())) {
232 12
            $nameParts[] = $this->getGenerator()->getOptionSuffix();
233
        }
234
235 414
        return implode('', $nameParts);
236
    }
237
238
    /**
239
     * Allows to define the contextual part of the class name for the package.
240
     */
241 52
    public function getContextualPart(): string
242
    {
243 52
        return '';
244
    }
245
246 22
    public function getExtends(bool $short = false): ?string
247
    {
248 22
        return '';
249
    }
250
251 230
    public function getNamespace(): string
252
    {
253 230
        $namespaces = [];
254 230
        $namespace = $this->getGenerator()->getOptionNamespacePrefix();
255
256 230
        if (!empty($namespace)) {
257 6
            $namespaces[] = $namespace;
258
        }
259
260 230
        if (!empty($this->getSubDirectory())) {
261 214
            $namespaces[] = str_replace('/', '\\', $this->getSubDirectory());
262
        }
263
264 230
        return implode('\\', $namespaces);
265
    }
266
267 242
    public function getSubDirectory(): string
268
    {
269 242
        $subDirectory = '';
270 242
        if (GeneratorOptions::VALUE_CAT === $this->getGenerator()->getOptionCategory()) {
271 240
            $subDirectory = $this->getContextualPart();
272
        }
273
274 242
        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 424
    public static function cleanString(string $string, bool $keepMultipleUnderscores = true): string
287
    {
288 424
        return GeneratorUtils::cleanString($string, $keepMultipleUnderscores);
289
    }
290
291 362
    public static function replacePhpReservedKeyword(string $keyword, ?string $context = null): string
292
    {
293 362
        if (PhpReservedKeyword::instance()->is($keyword)) {
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 362
        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()));
314
    }
315
316 386
    public function replaceReservedMethod(string $methodName, ?string $context = null): string
317
    {
318 386
        if ($this->getReservedMethodsInstance()->is($methodName)) {
319 12
            if (!is_null($context)) {
320 12
                $methodKey = $methodName.'_'.$context;
321 12
                if (!array_key_exists($methodKey, $this->replacedReservedMethods)) {
322 12
                    $this->replacedReservedMethods[$methodKey] = 0;
323
                } else {
324
                    ++$this->replacedReservedMethods[$methodKey];
325
                }
326
327 12
                return '_'.$methodName.($this->replacedReservedMethods[$methodKey] ? '_'.$this->replacedReservedMethods[$methodKey] : '');
328
            }
329
330 2
            return '_'.$methodName;
331
        }
332
333 378
        return $methodName;
334
    }
335
336
    /**
337
     * Gives the availability for test purpose and multiple package generation to purge unique names.
338
     */
339 416
    public static function purgeUniqueNames()
340
    {
341 416
        self::$uniqueNames = [];
342
    }
343
344
    /**
345
     * Gives the availability for test purpose and multiple package generation to purge reserved keywords usage.
346
     */
347 404
    public static function purgePhpReservedKeywords()
348
    {
349 404
        self::$replacedPhpReservedKeywords = [];
350
    }
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 6
        ]);
361
    }
362
363 212
    public static function instanceFromSerializedJson(Generator $generator, array $args): self
364
    {
365 212
        self::checkSerializedJson($args);
366 212
        $class = $args['__CLASS__'];
367 212
        $instance = new $class($generator, $args['name']);
368 212
        unset($args['name'], $args['__CLASS__']);
369 212
        foreach ($args as $arg => $value) {
370 212
            $setFromSerializedJson = sprintf('set%sFromSerializedJson', ucfirst($arg));
371 212
            $set = sprintf('set%s', ucfirst($arg));
372 212
            if (method_exists($instance, $setFromSerializedJson)) {
373 212
                $instance->{$setFromSerializedJson}($value);
374 212
            } elseif (method_exists($instance, $set)) {
375 212
                $instance->{$set}($value);
376
            }
377
        }
378
379 212
        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 196
    protected function mergeMeta(): array
387
    {
388 196
        $meta = func_get_args();
389 196
        $mergedMeta = [];
390 196
        $metaDocumentation = [];
391
        // gather meta
392 196
        foreach ($meta as $metaItem) {
393 196
            foreach ($metaItem as $metaName => $metaValue) {
394 156
                if (self::META_DOCUMENTATION === $metaName) {
395 42
                    $metaDocumentation = array_merge($metaDocumentation, $metaValue);
396 148
                } elseif (!array_key_exists($metaName, $mergedMeta)) {
397 148
                    $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 196
        ksort($mergedMeta);
410
411
        // add documentation if any at first position
412 196
        if (!empty($metaDocumentation)) {
413 42
            $definitiveMeta = [
414 42
                self::META_DOCUMENTATION => array_unique(array_reverse($metaDocumentation)),
415 42
            ];
416 42
            foreach ($mergedMeta as $metaName => $metaValue) {
417 34
                $definitiveMeta[$metaName] = $metaValue;
418
            }
419 42
            $mergedMeta = $definitiveMeta;
420 42
            unset($definitiveMeta);
421
        }
422 196
        unset($meta, $metaDocumentation);
423
424 196
        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 418
    protected static function uniqueName(string $name, string $context): string
435
    {
436 418
        $insensitiveKey = mb_strtolower($name.'_'.$context);
437 418
        $sensitiveKey = $name.'_'.$context;
438 418
        if (array_key_exists($sensitiveKey, self::$uniqueNames)) {
439 388
            return self::$uniqueNames[$sensitiveKey];
440
        }
441
442 362
        if (!array_key_exists($insensitiveKey, self::$uniqueNames)) {
443 360
            self::$uniqueNames[$insensitiveKey] = 0;
444
        } else {
445 18
            ++self::$uniqueNames[$insensitiveKey];
446
        }
447
448 362
        $uniqueName = $name.(self::$uniqueNames[$insensitiveKey] ? '_'.self::$uniqueNames[$insensitiveKey] : '');
449 362
        self::$uniqueNames[$sensitiveKey] = $uniqueName;
450
451 362
        return $uniqueName;
452
    }
453
454
    /**
455
     * Must return the properties of the inherited class.
456
     */
457
    abstract protected function toJsonSerialize(): array;
458
459 212
    protected static function checkSerializedJson(array $args): void
460
    {
461 212
        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 212
        if (!class_exists($args['__CLASS__'])) {
466 2
            throw new InvalidArgumentException(sprintf('Class "%s" is unknown', $args['__CLASS__']));
467
        }
468
469 212
        if (!array_key_exists('name', $args)) {
470 2
            throw new InvalidArgumentException(sprintf('name key is missing from "%s"', var_export($args, true)));
471
        }
472
    }
473
}
474