Completed
Push — master ( 9b8f8b...ad8621 )
by Mikaël
29:42
created

AbstractModel::getSubDirectory()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 7
ccs 6
cts 6
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace WsdlToPhp\PackageGenerator\Model;
4
5
use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
6
use WsdlToPhp\PackageGenerator\ConfigurationReader\PhpReservedKeyword;
7
use WsdlToPhp\PackageGenerator\ConfigurationReader\AbstractReservedWord;
8
use WsdlToPhp\PackageGenerator\Generator\Generator;
9
use WsdlToPhp\PackageGenerator\Generator\Utils as GeneratorUtils;
10
use WsdlToPhp\PackageGenerator\Generator\AbstractGeneratorAware;
11
12
/**
13
 * Class AbstractModel defines the basic properties and methods to operations and structs extracted from the WSDL
14
 */
15
abstract class AbstractModel extends AbstractGeneratorAware implements \JsonSerializable
16
{
17
    /**
18
     * Constant used to define the key to store documentation value in meta
19
     * @var string
20
     */
21
    const META_DOCUMENTATION = 'documentation';
22
    /**
23
     * Original name od the element
24
     * @var string
25
     */
26
    protected $name = '';
27
    /**
28
     * Values associated to the operation
29
     * @var string[]
30
     */
31
    protected $meta = [];
32
    /**
33
     * Define the inheritance of a struct by the name of the parent struct or type
34
     * @var string
35
     */
36
    protected $inheritance = '';
37
    /**
38
     * Store the object which owns the current model
39
     * @var AbstractModel
40
     */
41
    protected $owner = null;
42
    /**
43
     * Indicates that the current element is an abstract element.
44
     * It allows to generated an abstract class.
45
     * This will happen for element/complexType that are defined with abstract="true"
46
     * @var bool
47
     */
48
    protected $isAbstract = false;
49
    /**
50
     * Replaced keywords time in order to generate unique new keyword
51
     * @var array
52
     */
53
    protected static $replacedPhpReservedKeywords = [];
54
    /**
55
     * Replaced methods time in order to generate unique new method
56
     * @var array
57
     */
58
    protected $replacedReservedMethods = [];
59
    /**
60
     * Unique name generated in order to ensure unique naming (for struct constructor and setters/getters even for different case attribute name with same value)
61
     * @var array
62
     */
63
    protected static $uniqueNames = [];
64
    /**
65
     * Main constructor
66
     * @uses AbstractModel::setName()
67
     * @param Generator $generator
68
     * @param string $name the original name
69
     */
70 1572
    public function __construct(Generator $generator, $name)
71
    {
72 1572
        parent::__construct($generator);
73 1572
        $this->setName($name);
74 1572
    }
75
    /**
76
     * @uses AbstractModel::getInheritedModel()
77
     * @uses AbstractModel::getPackagedName()
78
     * @uses AbstractModel::getExtends()
79
     * @uses Struct::isStruct()
80
     * @return string
81
     */
82 474
    public function getExtendsClassName()
83
    {
84 474
        $extends = '';
85 474
        if (($model = $this->getInheritedModel()) instanceof Struct && $model->isStruct()) {
86 30
            $extends = $model->getPackagedName($model->isRestriction());
87 15
        }
88 474
        if (empty($extends)) {
89 456
            $extends = $this->getExtends(true);
90 228
        }
91 474
        return $extends;
92
    }
93
    /**
94
     * Returns the name of the class the current class inherits from
95
     * @return string
96
     */
97 1014
    public function getInheritance()
98
    {
99 1014
        return $this->inheritance;
100
    }
101
    /**
102
     * Sets the name of the class the current class inherits from
103
     * @param string $inheritance
104
     * @return AbstractModel
105
     */
106 876
    public function setInheritance($inheritance = '')
107
    {
108 876
        $this->inheritance = $inheritance;
109 876
        return $this;
110
    }
111
    /**
112
     * @uses AbstractGeneratorAware::getGenerator()
113
     * @uses Generator::getStructByName()
114
     * @uses AbstractModel::getInheritance()
115
     * @return Struct
116
     */
117 474
    public function getInheritedModel()
118
    {
119 474
        return $this->getGenerator()->getStructByName($this->getInheritance());
120
    }
121
    /**
122
     * Returns the meta
123
     * @return string[]
124
     */
125 666
    public function getMeta()
126
    {
127 666
        return $this->meta;
128
    }
129
    /**
130
     * Sets the meta
131
     * @param string[] $meta
132
     * @return AbstractModel
133
     */
134 552
    public function setMeta(array $meta = [])
135
    {
136 552
        $this->meta = $meta;
137 552
        return $this;
138
    }
139
    /**
140
     * Add meta information to the operation
141
     * @uses AbstractModel::getMeta()
142
     * @throws \InvalidArgumentException
143
     * @param string $metaName
144
     * @param mixed $metaValue
145
     * @return AbstractModel
146
     */
147 222
    public function addMeta($metaName, $metaValue)
148
    {
149 222
        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...
150 12
            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__);
151
        }
152 210
        $metaValue = is_scalar($metaValue) ? ((is_numeric($metaValue) || is_bool($metaValue) ? $metaValue : trim($metaValue))) : $metaValue;
153 210
        if ((is_scalar($metaValue) && $metaValue !== '') || is_array($metaValue)) {
154 210
            if (!array_key_exists($metaName, $this->meta)) {
155 210
                $this->meta[$metaName] = $metaValue;
156 132
            } elseif (is_array($this->meta[$metaName]) && is_array($metaValue)) {
0 ignored issues
show
introduced by
The condition is_array($this->meta[$metaName]) is always false.
Loading history...
157 30
                $this->meta[$metaName] = array_merge($this->meta[$metaName], $metaValue);
158 39
            } elseif (is_array($this->meta[$metaName])) {
0 ignored issues
show
introduced by
The condition is_array($this->meta[$metaName]) is always false.
Loading history...
159 6
                array_push($this->meta[$metaName], $metaValue);
160 3
            } else {
161 18
                $this->meta[$metaName] = $metaValue;
162 3
            }
163 210
            ksort($this->meta);
164 105
        }
165 210
        return $this;
166
    }
167
168
    /**
169
     * Allows to merge meta from different sources and ensure consistency of their order
170
     * Must be passed as less important (at first position) to most important (last position)
171
     * @param array $meta
172
     * @param array $meta
173
     * @param array $meta
174
     * @param array ...
175
     * @return array
176
     */
177 528
    protected function mergeMeta()
178
    {
179 528
        $meta = func_get_args();
180 528
        $mergedMeta = [];
181 528
        $metaDocumentation = [];
182
        // gather meta
183 528
        foreach ($meta as $metaItem) {
184 528
            foreach ($metaItem as $metaName => $metaValue) {
185 426
                if (self::META_DOCUMENTATION === $metaName) {
186 120
                    $metaDocumentation = array_merge($metaDocumentation, $metaValue);
187 414
                } elseif (!array_key_exists($metaName, $mergedMeta)) {
188 402
                    $mergedMeta[$metaName] = $metaValue;
189 216
                } elseif (is_array($mergedMeta[$metaName]) && is_array($metaValue)) {
190
                    $mergedMeta[$metaName] = array_merge($mergedMeta[$metaName], $metaValue);
191 30
                } elseif (is_array($mergedMeta[$metaName])) {
192
                    $mergedMeta[$metaName][] = $metaValue;
193
                } else {
194 196
                    $mergedMeta[$metaName] = $metaValue;
195
                }
196 264
            }
197 264
        }
198
199
        // sort by key
200 528
        ksort($mergedMeta);
201
202
        // add documentation if any at first position
203 528
        if (!empty($metaDocumentation)) {
204
            $definitiveMeta = [
205 120
                self::META_DOCUMENTATION => array_unique(array_reverse($metaDocumentation)),
206 60
            ];
207 120
            foreach ($mergedMeta as $metaName => $metaValue) {
208 96
                $definitiveMeta[$metaName] = $metaValue;
209 60
            }
210 120
            $mergedMeta = $definitiveMeta;
211 120
            unset($definitiveMeta);
212 60
        }
213 528
        unset($meta, $metaDocumentation);
214 528
        return $mergedMeta;
215
    }
216
    /**
217
     * Sets the documentation meta value.
218
     * Documentation is set as an array so if multiple documentation nodes are set for an unique element, it will gather them.
219
     * @uses AbstractModel::META_DOCUMENTATION
220
     * @uses AbstractModel::addMeta()
221
     * @param string $documentation the documentation from the WSDL
222
     * @return AbstractModel
223
     */
224 36
    public function setDocumentation($documentation)
225
    {
226 36
        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...
227 36
            $documentation,
228 18
        ]);
229
    }
230
    /**
231
     * Returns a meta value according to its name
232
     * @uses AbstractModel::getMeta()
233
     * @param string $metaName the meta information name
234
     * @param mixed $fallback the fallback value if unset
235
     * @return mixed the meta information value
236
     */
237 528
    public function getMetaValue($metaName, $fallback = null)
238
    {
239 528
        $meta = $this->getMeta();
240 528
        return array_key_exists($metaName, $meta) ? $meta[$metaName] : $fallback;
241
    }
242
    /**
243
     * Returns the value of the first meta value assigned to the name
244
     * @param string[] $names the meta names to check
245
     * @param mixed $fallback the fallback value if anyone is set
246
     * @return mixed the meta information value
247
     */
248 348
    public function getMetaValueFirstSet(array $names, $fallback = null)
249
    {
250 348
        $meta = $this->getMeta();
251 348
        foreach ($names as $name) {
252 348
            if (array_key_exists($name, $meta)) {
253 288
                return $meta[$name];
254
            }
255 153
        }
256 288
        return $fallback;
257
    }
258
    /**
259
     * Returns the original name extracted from the WSDL
260
     * @return string
261
     */
262 1512
    public function getName()
263
    {
264 1512
        return $this->name;
265
    }
266
    /**
267
     * Sets the original name extracted from the WSDL
268
     * @param string $name
269
     * @return AbstractModel
270
     */
271 1572
    public function setName($name)
272
    {
273 1572
        $this->name = $name;
274 1572
        return $this;
275
    }
276
    /**
277
     * Returns a valid clean name for PHP
278
     * @uses AbstractModel::getName()
279
     * @uses AbstractModel::cleanString()
280
     * @param bool $keepMultipleUnderscores optional, allows to keep the multiple consecutive underscores
281
     * @return string
282
     */
283 1152
    public function getCleanName($keepMultipleUnderscores = true)
284
    {
285 1152
        return self::cleanString($this->getName(), $keepMultipleUnderscores);
286
    }
287
    /**
288
     * Returns the owner model object
289
     * @return AbstractModel
290
     */
291 1194
    public function getOwner()
292
    {
293 1194
        return $this->owner;
294
    }
295
    /**
296
     * Sets the owner model object
297
     * @param AbstractModel $owner object the owner of the current model
298
     * @return AbstractModel
299
     */
300 1224
    public function setOwner(AbstractModel $owner = null)
301
    {
302 1224
        $this->owner = $owner;
303 1224
        return $this;
304
    }
305
    /**
306
     * @return bool
307
     */
308 480
    public function isAbstract()
309
    {
310 480
        return $this->isAbstract;
311
    }
312
    /**
313
     * @param bool $isAbstract
314
     * @return AbstractModel
315
     */
316 546
    public function setAbstract($isAbstract)
317
    {
318 546
        $this->isAbstract = $isAbstract;
319 546
        return $this;
320
    }
321
    /**
322
     * Returns true if the original name is safe to use as a PHP property, variable name or class name
323
     * @uses AbstractModel::getName()
324
     * @uses AbstractModel::getCleanName()
325
     * @return bool
326
     */
327 414
    public function nameIsClean()
328
    {
329 414
        return ($this->getName() !== '' && $this->getName() === $this->getCleanName());
330
    }
331
    /**
332
     * Returns the packaged name
333
     * @uses AbstractModel::getNamespace()
334
     * @uses AbstractModel::getCleanName()
335
     * @uses AbstractModel::getContextualPart()
336
     * @uses AbstractModel::uniqueName()
337
     * @uses AbstractModel::replacePhpReservedKeyword()
338
     * @uses AbstractGeneratorAware::getGenerator()
339
     * @uses Generator::getOptionPrefix()
340
     * @uses Generator::getOptionSuffix()
341
     * @uses AbstractModel::uniqueName() to ensure unique naming of struct case sensitively
342
     * @param bool $namespaced
343
     * @return string
344
     */
345 1134
    public function getPackagedName($namespaced = false)
346
    {
347 1134
        $nameParts = [];
348 1134
        if ($namespaced && $this->getNamespace() !== '') {
349 450
            $nameParts[] = sprintf('\%s\\', $this->getNamespace());
350 225
        }
351 1134
        $cleanName = $this->getCleanName();
352 1134
        if ($this->getGenerator()->getOptionPrefix() !== '') {
353 780
            $nameParts[] = $this->getGenerator()->getOptionPrefix();
354 390
        } else {
355 408
            $cleanName = self::replacePhpReservedKeyword($cleanName);
356
        }
357 1134
        $nameParts[] = ucfirst(self::uniqueName($cleanName, $this->getContextualPart()));
358 1134
        if ($this->getGenerator()->getOptionSuffix() !== '') {
359 36
            $nameParts[] = $this->getGenerator()->getOptionSuffix();
360 18
        }
361 1134
        return implode('', $nameParts);
362
    }
363
    /**
364
     * Allows to define the contextual part of the class name for the package
365
     * @return string
366
     */
367 138
    public function getContextualPart()
368
    {
369 138
        return '';
370
    }
371
    /**
372
     * Allows to define from which class the current model extends
373
     * @param bool $short
374
     * @return string|null
375
     */
376 66
    public function getExtends($short = false)
377
    {
378 66
        return '';
379
    }
380
    /**
381
     * @uses AbstractGeneratorAware::getGenerator()
382
     * @uses Generator::getOptionNamespacePrefix()
383
     * @uses Generator::getOptionPrefix()
384
     * @uses Generator::getOptionSuffix()
385
     * @uses AbstractModel::getSubDirectory()
386
     * @return string
387
     */
388 618
    public function getNamespace()
389
    {
390 618
        $namespaces = [];
391 618
        $namespace = $this->getGenerator()->getOptionNamespacePrefix();
392 618
        if (empty($namespace)) {
393 606
            if ($this->getGenerator()->getOptionPrefix() !== '') {
394 522
                $namespaces[] = $this->getGenerator()->getOptionPrefix();
395 345
            } elseif ($this->getGenerator()->getOptionSuffix() !== '') {
396 309
                $namespaces[] = $this->getGenerator()->getOptionSuffix();
397 6
            }
398 303
        } else {
399 12
            $namespaces[] = $namespace;
400
        }
401 618
        if ($this->getSubDirectory() !== '') {
402 588
            $namespaces[] = $this->getSubDirectory();
403 294
        }
404 618
        return implode('\\', $namespaces);
405
    }
406
    /**
407
     * Returns directory where to store class and create it if needed
408
     * @uses AbstractGeneratorAware::getGenerator()
409
     * @uses AbstractModel::getOptionCategory()
410
     * @uses AbstractGeneratorAware::getContextualPart()
411
     * @uses GeneratorOptions::VALUE_CAT
412
     * @return string
413
     */
414 654
    public function getSubDirectory()
415
    {
416 654
        $subDirectory = '';
417 654
        if ($this->getGenerator()->getOptionCategory() === GeneratorOptions::VALUE_CAT) {
418 648
            $subDirectory = $this->getContextualPart();
419 324
        }
420 654
        return $subDirectory;
421
    }
422
    /**
423
     * Returns the sub package name which the model belongs to
424
     * Must be overridden by sub classes
425
     * @return array
426
     */
427 6
    public function getDocSubPackages()
428
    {
429 6
        return [];
430
    }
431
    /**
432
     * Clean a string to make it valid as PHP variable
433
     * @uses GeneratorUtils::cleanString()
434
     * @param string $string the string to clean
435
     * @param bool $keepMultipleUnderscores optional, allows to keep the multiple consecutive underscores
436
     * @return string
437
     */
438 1158
    public static function cleanString($string, $keepMultipleUnderscores = true)
439
    {
440 1158
        return GeneratorUtils::cleanString($string, $keepMultipleUnderscores);
441
    }
442
    /**
443
     * Returns a usable keyword for a original keyword
444
     * @uses PhpReservedKeyword::instance()
445
     * @uses PhpReservedKeyword::is()
446
     * @param string $keyword the keyword
447
     * @param string $context the context
448
     * @return string
449
     */
450 984
    public static function replacePhpReservedKeyword($keyword, $context = null)
451
    {
452 984
        if (PhpReservedKeyword::instance()->is($keyword)) {
453 222
            if ($context !== null) {
454 114
                $keywordKey = $keyword . '_' . $context;
455 114
                if (!array_key_exists($keywordKey, self::$replacedPhpReservedKeywords)) {
456 72
                    self::$replacedPhpReservedKeywords[$keywordKey] = 0;
457 36
                } else {
458 42
                    self::$replacedPhpReservedKeywords[$keywordKey]++;
459
                }
460 114
                return '_' . $keyword . (self::$replacedPhpReservedKeywords[$keywordKey] ? '_' . self::$replacedPhpReservedKeywords[$keywordKey] : '');
461
            } else {
462 174
                return '_' . $keyword;
463
            }
464
        } else {
465 984
            return $keyword;
466
        }
467
    }
468
    /**
469
     * @throws \InvalidArgumentException
470
     * @param $filename
471
     * @return AbstractReservedWord
472
     */
473 6
    public function getReservedMethodsInstance($filename = null)
474
    {
475 6
        throw new \InvalidArgumentException(sprintf('The method %s should be defined in the class %s', __FUNCTION__, get_called_class(), __LINE__));
476
    }
477
    /**
478
     * Returns a usable method for a original method
479
     * @uses PhpReservedKeywords::instance()
480
     * @uses PhpReservedKeywords::is()
481
     * @param string $methodName the method name
482
     * @param string $context the context
483
     * @return string
484
     */
485 1050
    public function replaceReservedMethod($methodName, $context = null)
486
    {
487 1050
        if ($this->getReservedMethodsInstance()->is($methodName)) {
488 18
            if ($context !== null) {
489 18
                $methodKey = $methodName . '_' . $context;
490 18
                if (!array_key_exists($methodKey, $this->replacedReservedMethods)) {
491 18
                    $this->replacedReservedMethods[$methodKey] = 0;
492 9
                } else {
493
                    $this->replacedReservedMethods[$methodKey]++;
494
                }
495 18
                return '_' . $methodName . ($this->replacedReservedMethods[$methodKey] ? '_' . $this->replacedReservedMethods[$methodKey] : '');
496
            } else {
497 6
                return '_' . $methodName;
498
            }
499
        } else {
500 1038
            return $methodName;
501
        }
502
    }
503
    /**
504
     * Static method which returns a unique name case sensitively
505
     * Useful to name methods case sensitively distinct, see http://the-echoplex.net/log/php-case-sensitivity
506
     * @param string $name the original name
507
     * @param string $context the context where the name is needed unique
508
     * @return string
509
     */
510 1140
    protected static function uniqueName($name, $context)
511
    {
512 1140
        $insensitiveKey = mb_strtolower($name . '_' . $context);
513 1140
        $sensitiveKey = $name . '_' . $context;
514 1140
        if (array_key_exists($sensitiveKey, self::$uniqueNames)) {
515 1056
            return self::$uniqueNames[$sensitiveKey];
516 972
        } elseif (!array_key_exists($insensitiveKey, self::$uniqueNames)) {
517 966
            self::$uniqueNames[$insensitiveKey] = 0;
518 483
        } else {
519 54
            self::$uniqueNames[$insensitiveKey]++;
520
        }
521 972
        $uniqueName = $name . (self::$uniqueNames[$insensitiveKey] ? '_' . self::$uniqueNames[$insensitiveKey] : '');
522 972
        self::$uniqueNames[$sensitiveKey] = $uniqueName;
523 972
        return $uniqueName;
524
    }
525
    /**
526
     * Gives the availability for test purpose and multiple package generation to purge unique names
527
     */
528 1110
    public static function purgeUniqueNames()
529
    {
530 1110
        self::$uniqueNames = [];
531 1110
    }
532
    /**
533
     * Gives the availability for test purpose and multiple package generation to purge reserved keywords usage
534
     */
535 1074
    public static function purgePhpReservedKeywords()
536
    {
537 1074
        self::$replacedPhpReservedKeywords = [];
538 1074
    }
539
    /**
540
     * Should return the properties of the inherited class
541
     * @return array
542
     */
543
    abstract protected function toJsonSerialize();
544
    /**
545
     * {@inheritDoc}
546
     * @see JsonSerializable::jsonSerialize()
547
     */
548 18
    public function jsonSerialize()
549
    {
550 18
        return array_merge($this->toJsonSerialize(), [
551 18
            'inheritance' => $this->inheritance,
552 18
            'abstract' => $this->isAbstract,
553 18
            'meta' => $this->meta,
554 18
            'name' => $this->name,
555 18
            '__CLASS__' => get_called_class(),
556 9
        ]);
557
    }
558
    /**
559
     * @param Generator $generator
560
     * @param array $args
561
     * @return AbstractModel
562
     */
563 540
    public static function instanceFromSerializedJson(Generator $generator, array $args)
564
    {
565 540
        self::checkSerializedJson($args);
566 540
        $class = $args['__CLASS__'];
567 540
        $instance = new $class($generator, $args['name']);
568 540
        unset($args['name'], $args['__CLASS__']);
569 540
        foreach ($args as $arg => $value) {
570 540
            $setFromSerializedJson = sprintf('set%sFromSerializedJson', ucfirst($arg));
571 540
            $set = sprintf('set%s', ucfirst($arg));
572 540
            if (method_exists($instance, $setFromSerializedJson)) {
573 540
                $instance->$setFromSerializedJson($value);
574 540
            } elseif (method_exists($instance, $set)) {
575 540
                $instance->$set($value);
576 270
            }
577 270
        }
578 540
        return $instance;
579
    }
580
    /**
581
     * @param array $args
582
     * @throws \InvalidArgumentException
583
     */
584 540
    protected static function checkSerializedJson(array $args)
585
    {
586 540
        if (!array_key_exists('__CLASS__', $args)) {
587 6
            throw new \InvalidArgumentException(sprintf('__CLASS__ key is missing from "%s"', var_export($args, true)));
588
        }
589 540
        if (!class_exists($args['__CLASS__'])) {
590 6
            throw new \InvalidArgumentException(sprintf('Class %s is unknown', $args['__CLASS__']));
591
        }
592 540
        if (!array_key_exists('name', $args)) {
593 6
            throw new \InvalidArgumentException(sprintf('name key is missing from %s', var_export($args, true)));
594
        }
595 540
    }
596
}
597