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