Passed
Pull Request — develop (#282)
by Mikaël
27:56 queued 25:33
created

AbstractModel::cleanString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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