Passed
Push — feature/issue-246 ( 79981d )
by Mikaël
14:50
created

AbstractModel::setOwner()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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