Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.14% |
158 / 161 |
|
94.44% |
34 / 36 |
CRAP | |
0.00% |
0 / 1 |
AbstractModel | |
98.14% |
158 / 161 |
|
94.44% |
34 / 36 |
91 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getExtendsClassName | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
getInheritance | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setInheritance | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getInheritedModel | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMeta | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setMeta | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addMeta | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
15 | |||
setDocumentation | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getMetaValue | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
getMetaValueFirstSet | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
getName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setName | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getCleanName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOwner | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setOwner | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
isAbstract | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setAbstract | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
nameIsClean | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getPackagedName | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
getContextualPart | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getExtends | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getNamespace | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
getSubDirectory | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getDocSubPackages | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
cleanString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
replacePhpReservedKeyword | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
5 | |||
getReservedMethodsInstance | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
replaceReservedMethod | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
5.03 | |||
purgeUniqueNames | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
purgePhpReservedKeywords | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
jsonSerialize | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
instanceFromSerializedJson | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
4 | |||
mergeMeta | |
92.00% |
23 / 25 |
|
0.00% |
0 / 1 |
10.05 | |||
uniqueName | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
toJsonSerialize | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
checkSerializedJson | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace WsdlToPhp\PackageGenerator\Model; |
6 | |
7 | use WsdlToPhp\PackageGenerator\ConfigurationReader\AbstractReservedWord; |
8 | use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions; |
9 | use WsdlToPhp\PackageGenerator\ConfigurationReader\PhpReservedKeyword; |
10 | use WsdlToPhp\PackageGenerator\Generator\AbstractGeneratorAware; |
11 | use WsdlToPhp\PackageGenerator\Generator\Generator; |
12 | use WsdlToPhp\PackageGenerator\Generator\Utils as GeneratorUtils; |
13 | |
14 | /** |
15 | * Class AbstractModel defines the basic properties and methods to operations and structs extracted from the WSDL. |
16 | */ |
17 | abstract class AbstractModel extends AbstractGeneratorAware implements \JsonSerializable |
18 | { |
19 | public const META_DOCUMENTATION = 'documentation'; |
20 | |
21 | /** |
22 | * Original name of the element. |
23 | * |
24 | * @var mixed |
25 | */ |
26 | protected $name; |
27 | |
28 | /** |
29 | * Values associated to the operation. |
30 | * |
31 | * @var string[] |
32 | */ |
33 | protected array $meta = []; |
34 | |
35 | /** |
36 | * Define the inheritance of a struct by the name of the parent struct or type. |
37 | */ |
38 | protected string $inheritance = ''; |
39 | |
40 | /** |
41 | * Store the object which owns the current model. |
42 | */ |
43 | protected ?AbstractModel $owner = null; |
44 | |
45 | /** |
46 | * Indicates that the current element is an abstract element. |
47 | * It allows to generated an abstract class. |
48 | * This will happen for element/complexType that are defined with abstract="true". |
49 | */ |
50 | protected bool $isAbstract = false; |
51 | |
52 | /** |
53 | * Replaced keywords time in order to generate unique new keyword. |
54 | */ |
55 | protected static array $replacedPhpReservedKeywords = []; |
56 | |
57 | /** |
58 | * Replaced methods time in order to generate unique new method. |
59 | */ |
60 | protected array $replacedReservedMethods = []; |
61 | |
62 | /** |
63 | * Unique name generated in order to ensure unique naming (for struct constructor and setters/getters even for different case attribute name with same value). |
64 | */ |
65 | protected static array $uniqueNames = []; |
66 | |
67 | public function __construct(Generator $generator, $name) |
68 | { |
69 | parent::__construct($generator); |
70 | $this->setName($name); |
71 | } |
72 | |
73 | public function getExtendsClassName(): string |
74 | { |
75 | $extends = ''; |
76 | if (($model = $this->getInheritedModel()) instanceof Struct && $model->isStruct()) { |
77 | $extends = $model->getPackagedName($model->isRestriction()); |
78 | } |
79 | if (empty($extends)) { |
80 | $extends = $this->getExtends(true); |
81 | } |
82 | |
83 | return $extends; |
84 | } |
85 | |
86 | public function getInheritance(): string |
87 | { |
88 | return $this->inheritance; |
89 | } |
90 | |
91 | public function setInheritance(string $inheritance = ''): self |
92 | { |
93 | $this->inheritance = $inheritance; |
94 | |
95 | return $this; |
96 | } |
97 | |
98 | public function getInheritedModel(): ?Struct |
99 | { |
100 | return $this->getGenerator()->getStructByName($this->getInheritance()); |
101 | } |
102 | |
103 | public function getMeta(): array |
104 | { |
105 | return $this->meta; |
106 | } |
107 | |
108 | public function setMeta(array $meta = []): self |
109 | { |
110 | $this->meta = $meta; |
111 | |
112 | return $this; |
113 | } |
114 | |
115 | public function addMeta(string $metaName, $metaValue): self |
116 | { |
117 | if (!is_scalar($metaName) || (!is_scalar($metaValue) && !is_array($metaValue))) { |
118 | 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__); |
119 | } |
120 | $metaValue = is_scalar($metaValue) ? (is_numeric($metaValue) || is_bool($metaValue) ? $metaValue : trim($metaValue)) : $metaValue; |
121 | if (is_scalar($metaValue) || is_array($metaValue)) { |
122 | if (!array_key_exists($metaName, $this->meta)) { |
123 | $this->meta[$metaName] = $metaValue; |
124 | } elseif (is_array($this->meta[$metaName]) && is_array($metaValue)) { |
125 | $this->meta[$metaName] = array_merge($this->meta[$metaName], $metaValue); |
126 | } elseif (is_array($this->meta[$metaName])) { |
127 | array_push($this->meta[$metaName], $metaValue); |
128 | } elseif (array_key_exists($metaName, $this->meta) && $metaValue !== $this->meta[$metaName]) { |
129 | $this->meta[$metaName] = [ |
130 | $this->meta[$metaName], |
131 | $metaValue, |
132 | ]; |
133 | } else { |
134 | $this->meta[$metaName] = $metaValue; |
135 | } |
136 | ksort($this->meta); |
137 | } |
138 | |
139 | return $this; |
140 | } |
141 | |
142 | public function setDocumentation(string $documentation): self |
143 | { |
144 | return $this->addMeta(self::META_DOCUMENTATION, is_array($documentation) ? $documentation : [ |
145 | $documentation, |
146 | ]); |
147 | } |
148 | |
149 | public function getMetaValue(string $metaName, $fallback = null) |
150 | { |
151 | $meta = $this->getMeta(); |
152 | |
153 | return array_key_exists($metaName, $meta) ? $meta[$metaName] : $fallback; |
154 | } |
155 | |
156 | public function getMetaValueFirstSet(array $names, $fallback = null) |
157 | { |
158 | $meta = $this->getMeta(); |
159 | foreach ($names as $name) { |
160 | if (array_key_exists($name, $meta)) { |
161 | return $meta[$name]; |
162 | } |
163 | } |
164 | |
165 | return $fallback; |
166 | } |
167 | |
168 | public function getName() |
169 | { |
170 | return $this->name; |
171 | } |
172 | |
173 | public function setName($name): self |
174 | { |
175 | $this->name = $name; |
176 | |
177 | return $this; |
178 | } |
179 | |
180 | public function getCleanName(bool $keepMultipleUnderscores = true): string |
181 | { |
182 | return self::cleanString($this->getName(), $keepMultipleUnderscores); |
183 | } |
184 | |
185 | public function getOwner(): ?AbstractModel |
186 | { |
187 | return $this->owner; |
188 | } |
189 | |
190 | public function setOwner(?AbstractModel $owner = null): self |
191 | { |
192 | $this->owner = $owner; |
193 | |
194 | return $this; |
195 | } |
196 | |
197 | public function isAbstract(): bool |
198 | { |
199 | return $this->isAbstract; |
200 | } |
201 | |
202 | public function setAbstract(bool $isAbstract): self |
203 | { |
204 | $this->isAbstract = $isAbstract; |
205 | |
206 | return $this; |
207 | } |
208 | |
209 | public function nameIsClean(): bool |
210 | { |
211 | return '' !== $this->getName() && $this->getName() === $this->getCleanName(); |
212 | } |
213 | |
214 | public function getPackagedName(bool $namespaced = false): string |
215 | { |
216 | $nameParts = []; |
217 | if ($namespaced && !empty($this->getNamespace())) { |
218 | $nameParts[] = sprintf('\%s\\', $this->getNamespace()); |
219 | } |
220 | |
221 | $cleanName = $this->getCleanName(); |
222 | if (!empty($this->getGenerator()->getOptionPrefix())) { |
223 | $nameParts[] = $this->getGenerator()->getOptionPrefix(); |
224 | } else { |
225 | $cleanName = self::replacePhpReservedKeyword($cleanName); |
226 | } |
227 | |
228 | $nameParts[] = ucfirst(self::uniqueName($cleanName, $this->getContextualPart())); |
229 | if (!empty($this->getGenerator()->getOptionSuffix())) { |
230 | $nameParts[] = $this->getGenerator()->getOptionSuffix(); |
231 | } |
232 | |
233 | return implode('', $nameParts); |
234 | } |
235 | |
236 | /** |
237 | * Allows to define the contextual part of the class name for the package. |
238 | */ |
239 | public function getContextualPart(): string |
240 | { |
241 | return ''; |
242 | } |
243 | |
244 | public function getExtends(bool $short = false): ?string |
245 | { |
246 | return ''; |
247 | } |
248 | |
249 | public function getNamespace(): string |
250 | { |
251 | $namespaces = []; |
252 | $namespace = $this->getGenerator()->getOptionNamespacePrefix(); |
253 | |
254 | if (!empty($namespace)) { |
255 | $namespaces[] = $namespace; |
256 | } |
257 | |
258 | if (!empty($this->getSubDirectory())) { |
259 | $namespaces[] = str_replace('/', '\\', $this->getSubDirectory()); |
260 | } |
261 | |
262 | return implode('\\', $namespaces); |
263 | } |
264 | |
265 | public function getSubDirectory(): string |
266 | { |
267 | $subDirectory = ''; |
268 | if (GeneratorOptions::VALUE_CAT === $this->getGenerator()->getOptionCategory()) { |
269 | $subDirectory = $this->getContextualPart(); |
270 | } |
271 | |
272 | return $subDirectory; |
273 | } |
274 | |
275 | /** |
276 | * Returns the sub package name which the model belongs to |
277 | * Must be overridden by sub classes. |
278 | */ |
279 | public function getDocSubPackages(): array |
280 | { |
281 | return []; |
282 | } |
283 | |
284 | public static function cleanString(string $string, bool $keepMultipleUnderscores = true): string |
285 | { |
286 | return GeneratorUtils::cleanString($string, $keepMultipleUnderscores); |
287 | } |
288 | |
289 | public static function replacePhpReservedKeyword(string $keyword, ?string $context = null): string |
290 | { |
291 | if (PhpReservedKeyword::instance()->is($keyword)) { |
292 | if (!is_null($context)) { |
293 | $keywordKey = $keyword.'_'.$context; |
294 | if (!array_key_exists($keywordKey, self::$replacedPhpReservedKeywords)) { |
295 | self::$replacedPhpReservedKeywords[$keywordKey] = 0; |
296 | } else { |
297 | ++self::$replacedPhpReservedKeywords[$keywordKey]; |
298 | } |
299 | |
300 | return '_'.$keyword.(self::$replacedPhpReservedKeywords[$keywordKey] ? '_'.self::$replacedPhpReservedKeywords[$keywordKey] : ''); |
301 | } |
302 | |
303 | return '_'.$keyword; |
304 | } |
305 | |
306 | return $keyword; |
307 | } |
308 | |
309 | public function getReservedMethodsInstance(): AbstractReservedWord |
310 | { |
311 | throw new \InvalidArgumentException(sprintf('The method %s should be defined in the class %s', __FUNCTION__, static::class)); |
312 | } |
313 | |
314 | public function replaceReservedMethod(string $methodName, ?string $context = null): string |
315 | { |
316 | if ($this->getReservedMethodsInstance()->is($methodName)) { |
317 | if (!is_null($context)) { |
318 | $methodKey = $methodName.'_'.$context; |
319 | if (!array_key_exists($methodKey, $this->replacedReservedMethods)) { |
320 | $this->replacedReservedMethods[$methodKey] = 0; |
321 | } else { |
322 | ++$this->replacedReservedMethods[$methodKey]; |
323 | } |
324 | |
325 | return '_'.$methodName.($this->replacedReservedMethods[$methodKey] ? '_'.$this->replacedReservedMethods[$methodKey] : ''); |
326 | } |
327 | |
328 | return '_'.$methodName; |
329 | } |
330 | |
331 | return $methodName; |
332 | } |
333 | |
334 | /** |
335 | * Gives the availability for test purpose and multiple package generation to purge unique names. |
336 | */ |
337 | public static function purgeUniqueNames() |
338 | { |
339 | self::$uniqueNames = []; |
340 | } |
341 | |
342 | /** |
343 | * Gives the availability for test purpose and multiple package generation to purge reserved keywords usage. |
344 | */ |
345 | public static function purgePhpReservedKeywords() |
346 | { |
347 | self::$replacedPhpReservedKeywords = []; |
348 | } |
349 | |
350 | public function jsonSerialize(): array |
351 | { |
352 | return array_merge($this->toJsonSerialize(), [ |
353 | 'inheritance' => $this->inheritance, |
354 | 'abstract' => $this->isAbstract, |
355 | 'meta' => $this->meta, |
356 | 'name' => $this->name, |
357 | '__CLASS__' => static::class, |
358 | ]); |
359 | } |
360 | |
361 | public static function instanceFromSerializedJson(Generator $generator, array $args): self |
362 | { |
363 | self::checkSerializedJson($args); |
364 | $class = $args['__CLASS__']; |
365 | $instance = new $class($generator, $args['name']); |
366 | unset($args['name'], $args['__CLASS__']); |
367 | foreach ($args as $arg => $value) { |
368 | $setFromSerializedJson = sprintf('set%sFromSerializedJson', ucfirst($arg)); |
369 | $set = sprintf('set%s', ucfirst($arg)); |
370 | if (method_exists($instance, $setFromSerializedJson)) { |
371 | $instance->{$setFromSerializedJson}($value); |
372 | } elseif (method_exists($instance, $set)) { |
373 | $instance->{$set}($value); |
374 | } |
375 | } |
376 | |
377 | return $instance; |
378 | } |
379 | |
380 | /** |
381 | * Allows to merge meta from different sources and ensure consistency of their order |
382 | * Must be passed as less important (at first position) to most important (last position). |
383 | */ |
384 | protected function mergeMeta(): array |
385 | { |
386 | $meta = func_get_args(); |
387 | $mergedMeta = []; |
388 | $metaDocumentation = []; |
389 | // gather meta |
390 | foreach ($meta as $metaItem) { |
391 | foreach ($metaItem as $metaName => $metaValue) { |
392 | if (self::META_DOCUMENTATION === $metaName) { |
393 | $metaDocumentation = array_merge($metaDocumentation, $metaValue); |
394 | } elseif (!array_key_exists($metaName, $mergedMeta)) { |
395 | $mergedMeta[$metaName] = $metaValue; |
396 | } elseif (is_array($mergedMeta[$metaName]) && is_array($metaValue)) { |
397 | $mergedMeta[$metaName] = array_merge($mergedMeta[$metaName], $metaValue); |
398 | } elseif (is_array($mergedMeta[$metaName])) { |
399 | $mergedMeta[$metaName][] = $metaValue; |
400 | } else { |
401 | $mergedMeta[$metaName] = $metaValue; |
402 | } |
403 | } |
404 | } |
405 | |
406 | // sort by key |
407 | ksort($mergedMeta); |
408 | |
409 | // add documentation if any at first position |
410 | if (!empty($metaDocumentation)) { |
411 | $definitiveMeta = [ |
412 | self::META_DOCUMENTATION => array_unique(array_reverse($metaDocumentation)), |
413 | ]; |
414 | foreach ($mergedMeta as $metaName => $metaValue) { |
415 | $definitiveMeta[$metaName] = $metaValue; |
416 | } |
417 | $mergedMeta = $definitiveMeta; |
418 | unset($definitiveMeta); |
419 | } |
420 | unset($meta, $metaDocumentation); |
421 | |
422 | return $mergedMeta; |
423 | } |
424 | |
425 | /** |
426 | * Static method which returns a unique name case sensitively |
427 | * Useful to name methods case sensitively distinct, see http://the-echoplex.net/log/php-case-sensitivity. |
428 | * |
429 | * @param string $name the original name |
430 | * @param string $context the context where the name is needed unique |
431 | */ |
432 | protected static function uniqueName(string $name, string $context): string |
433 | { |
434 | $insensitiveKey = mb_strtolower($name.'_'.$context); |
435 | $sensitiveKey = $name.'_'.$context; |
436 | if (array_key_exists($sensitiveKey, self::$uniqueNames)) { |
437 | return self::$uniqueNames[$sensitiveKey]; |
438 | } |
439 | |
440 | if (!array_key_exists($insensitiveKey, self::$uniqueNames)) { |
441 | self::$uniqueNames[$insensitiveKey] = 0; |
442 | } else { |
443 | ++self::$uniqueNames[$insensitiveKey]; |
444 | } |
445 | |
446 | $uniqueName = $name.(self::$uniqueNames[$insensitiveKey] ? '_'.self::$uniqueNames[$insensitiveKey] : ''); |
447 | self::$uniqueNames[$sensitiveKey] = $uniqueName; |
448 | |
449 | return $uniqueName; |
450 | } |
451 | |
452 | /** |
453 | * Must return the properties of the inherited class. |
454 | */ |
455 | abstract protected function toJsonSerialize(): array; |
456 | |
457 | protected static function checkSerializedJson(array $args): void |
458 | { |
459 | if (!array_key_exists('__CLASS__', $args)) { |
460 | throw new \InvalidArgumentException(sprintf('__CLASS__ key is missing from "%s"', var_export($args, true))); |
461 | } |
462 | |
463 | if (!class_exists($args['__CLASS__'])) { |
464 | throw new \InvalidArgumentException(sprintf('Class "%s" is unknown', $args['__CLASS__'])); |
465 | } |
466 | |
467 | if (!array_key_exists('name', $args)) { |
468 | throw new \InvalidArgumentException(sprintf('name key is missing from "%s"', var_export($args, true))); |
469 | } |
470 | } |
471 | } |