Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.18% covered (success)
98.18%
162 / 165
90.32% covered (success)
90.32%
28 / 31
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractModelFile
98.18% covered (success)
98.18%
162 / 165
90.32% covered (success)
90.32%
28 / 31
90
0.00% covered (danger)
0.00%
0 / 1
 getFileDestination
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getDestinationFolder
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
6
 writeFile
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 setModel
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getModel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getModelFromStructAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRestrictionFromStructAttribute
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
7
 getStructAttributeType
95.24% covered (success)
95.24%
20 / 21
0.00% covered (danger)
0.00%
0 / 1
14
 getStructAttributeTypeAsPhpType
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 getValidType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getPhpType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 addAnnotationBlock
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getModelByName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 definePackageAnnotations
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getPackageName
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 defineGeneralAnnotations
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getClassAnnotationBlock
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getClassDeclarationLine
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getClassDeclarationLineText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 defineModelAnnotationsFromWsdl
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 addClassElement
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 addDeclareDirective
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 defineNamespace
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 defineUseStatements
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 defineConstants
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 defineProperties
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 defineMethods
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 fillClassConstants
n/a
0 / 0
n/a
0 / 0
0
 getConstantAnnotationBlock
n/a
0 / 0
n/a
0 / 0
0
 fillClassProperties
n/a
0 / 0
n/a
0 / 0
0
 getPropertyAnnotationBlock
n/a
0 / 0
n/a
0 / 0
0
 fillClassMethods
n/a
0 / 0
n/a
0 / 0
0
 getMethodAnnotationBlock
n/a
0 / 0
n/a
0 / 0
0
 getStructAttribute
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 getStructAttributeTypeGetAnnotation
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
8
 getStructAttributeTypeSetAnnotation
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 useBrackets
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace WsdlToPhp\PackageGenerator\File;
6
7use WsdlToPhp\PackageGenerator\ConfigurationReader\XsdTypes;
8use WsdlToPhp\PackageGenerator\Container\PhpElement\Constant;
9use WsdlToPhp\PackageGenerator\Container\PhpElement\Method;
10use WsdlToPhp\PackageGenerator\Container\PhpElement\Property;
11use WsdlToPhp\PackageGenerator\File\Utils as FileUtils;
12use WsdlToPhp\PackageGenerator\Generator\Utils as GeneratorUtils;
13use WsdlToPhp\PackageGenerator\Model\AbstractModel;
14use WsdlToPhp\PackageGenerator\Model\Struct as StructModel;
15use WsdlToPhp\PackageGenerator\Model\StructAttribute as StructAttributeModel;
16use WsdlToPhp\PhpGenerator\Component\PhpClass;
17use WsdlToPhp\PhpGenerator\Element\PhpAnnotation;
18use WsdlToPhp\PhpGenerator\Element\PhpAnnotationBlock;
19use WsdlToPhp\PhpGenerator\Element\PhpConstant;
20use WsdlToPhp\PhpGenerator\Element\PhpDeclare;
21use WsdlToPhp\PhpGenerator\Element\PhpMethod;
22use WsdlToPhp\PhpGenerator\Element\PhpProperty;
23
24abstract class AbstractModelFile extends AbstractFile
25{
26    public const ANNOTATION_META_LENGTH = 250;
27    public const ANNOTATION_LONG_LENGTH = 1000;
28    public const ANNOTATION_PACKAGE = 'package';
29    public const ANNOTATION_SUB_PACKAGE = 'subpackage';
30    public const ANNOTATION_RETURN = 'return';
31    public const ANNOTATION_USES = 'uses';
32    public const ANNOTATION_PARAM = 'param';
33    public const ANNOTATION_VAR = 'var';
34    public const ANNOTATION_SEE = 'see';
35    public const ANNOTATION_THROWS = 'throws';
36    public const METHOD_CONSTRUCT = '__construct';
37    public const TYPE_ARRAY = 'array';
38    public const TYPE_BOOL = 'bool';
39    public const TYPE_STRING = 'string';
40    public const TYPE_SELF = 'self';
41
42    protected Method $methods;
43
44    private ?AbstractModel $model = null;
45
46    public function getFileDestination(bool $withSrc = true): string
47    {
48        return sprintf(
49            '%s%s%s',
50            $this->getDestinationFolder($withSrc),
51            $this->getModel()->getSubDirectory(),
52            !empty($this->getModel()->getSubDirectory()) ? '/' : ''
53        );
54    }
55
56    public function getDestinationFolder(bool $withSrc = true): string
57    {
58        $src = rtrim($this->generator->getOptionSrcDirname(), DIRECTORY_SEPARATOR);
59
60        return sprintf(
61            '%s%s%s%s',
62            $this->getGenerator()->getOptionDestination(),
63            $withSrc && !empty($src) ? $src.DIRECTORY_SEPARATOR : '',
64            $this->getGenerator()->getOptionNamespaceDictatesDirectories() ? str_replace('\\', DIRECTORY_SEPARATOR, $this->getGenerator()->getOptionNamespacePrefix()) : '',
65            $this->getGenerator()->getOptionNamespacePrefix() && $this->getGenerator()->getOptionNamespaceDictatesDirectories() ? DIRECTORY_SEPARATOR : ''
66        );
67    }
68
69    public function writeFile(bool $withSrc = true): void
70    {
71        if (!$this->getModel()) {
72            throw new \InvalidArgumentException('You MUST define the model before being able to generate the file', __LINE__);
73        }
74
75        GeneratorUtils::createDirectory($this->getFileDestination($withSrc));
76
77        $this
78            ->addDeclareDirective()
79            ->defineNamespace()
80            ->defineUseStatements()
81            ->addAnnotationBlock()
82            ->addClassElement()
83        ;
84
85        parent::writeFile();
86    }
87
88    public function setModel(AbstractModel $model): self
89    {
90        $this->model = $model;
91
92        $this
93            ->getFile()
94            ->getMainElement()
95            ->setName($model->getPackagedName())
96        ;
97
98        return $this;
99    }
100
101    public function getModel(): ?AbstractModel
102    {
103        return $this->model;
104    }
105
106    public function getModelFromStructAttribute(StructAttributeModel $attribute = null): ?StructModel
107    {
108        return $this->getStructAttribute($attribute)->getTypeStruct();
109    }
110
111    public function getRestrictionFromStructAttribute(StructAttributeModel $attribute = null): ?StructModel
112    {
113        $model = $this->getModelFromStructAttribute($attribute);
114        if ($model instanceof StructModel) {
115            // list are mainly scalar values of basic types (string, int, etc.) or of Restriction values
116            if ($model->isList()) {
117                $subModel = $this->getModelByName($model->getList());
118                if ($subModel && $subModel->isRestriction()) {
119                    $model = $subModel;
120                } elseif (!$model->isRestriction()) {
121                    $model = null;
122                }
123            } elseif (!$model->isRestriction()) {
124                $model = null;
125            }
126        }
127
128        return $model;
129    }
130
131    public function getStructAttributeType(StructAttributeModel $attribute = null, bool $namespaced = false, bool $returnArrayType = true): string
132    {
133        $attribute = $this->getStructAttribute($attribute);
134
135        if (!$attribute instanceof StructAttributeModel) {
136            throw new \InvalidArgumentException('Could not find any valid StructAttribute');
137        }
138
139        if ($returnArrayType && $attribute->isArray()) {
140            return self::TYPE_ARRAY;
141        }
142
143        $inheritance = $attribute->getInheritance();
144        $type = empty($inheritance) ? $attribute->getType() : $inheritance;
145
146        if (!empty($type) && ($struct = $this->getGenerator()->getStructByName($type))) {
147            $inheritance = $struct->getTopInheritance();
148            if (!empty($inheritance)) {
149                $type = str_replace('[]', '', $inheritance);
150            } else {
151                $type = $struct->getPackagedName($namespaced);
152            }
153        }
154
155        $model = $this->getModelFromStructAttribute($attribute);
156        if ($model instanceof StructModel) {
157            // issue #84: union is considered as string as it would be difficult to have a method that accepts multiple object types.
158            // If the property has to be an object of multiple types => new issue...
159            if ($model->isRestriction() || $model->isUnion()) {
160                $type = self::TYPE_STRING;
161            } elseif ($model->isStruct()) {
162                $type = $model->getPackagedName($namespaced);
163            } elseif ($model->isArray() && ($inheritanceStruct = $model->getInheritanceStruct()) instanceof StructModel) {
164                $type = $inheritanceStruct->getPackagedName($namespaced);
165            }
166        }
167
168        return $type;
169    }
170
171    public function getStructAttributeTypeAsPhpType(StructAttributeModel $fromAttribute = null, bool $returnArrayType = true): string
172    {
173        $attribute = $this->getStructAttribute($fromAttribute);
174
175        if (!$attribute instanceof StructAttributeModel) {
176            throw new \InvalidArgumentException('Could not find any valid StructAttribute');
177        }
178
179        $attributeType = $this->getStructAttributeType($attribute, true, $returnArrayType);
180        if (XsdTypes::instance($this->getGenerator()->getOptionXsdTypesPath())->isXsd($attributeType)) {
181            $attributeType = self::getPhpType($attributeType, $this->getGenerator()->getOptionXsdTypesPath());
182        }
183
184        return $attributeType;
185    }
186
187    /**
188     * See http://php.net/manual/fr/language.oop5.typehinting.php for these cases
189     * Also see http://www.w3schools.com/schema/schema_dtypes_numeric.asp.
190     *
191     * @param mixed $type
192     * @param null  $xsdTypesPath
193     * @param mixed $fallback
194     *
195     * @return mixed
196     */
197    public static function getValidType($type, $xsdTypesPath = null, $fallback = null)
198    {
199        return XsdTypes::instance($xsdTypesPath)->isXsd(str_replace('[]', '', $type)) ? $fallback : $type;
200    }
201
202    /**
203     * See http://php.net/manual/fr/language.oop5.typehinting.php for these cases
204     * Also see http://www.w3schools.com/schema/schema_dtypes_numeric.asp.
205     *
206     * @param mixed $type
207     * @param null  $xsdTypesPath
208     * @param mixed $fallback
209     *
210     * @return mixed
211     */
212    public static function getPhpType($type, $xsdTypesPath = null, $fallback = self::TYPE_STRING)
213    {
214        return XsdTypes::instance($xsdTypesPath)->isXsd(str_replace('[]', '', $type)) ? XsdTypes::instance($xsdTypesPath)->phpType($type) : $fallback;
215    }
216
217    protected function addAnnotationBlock(): AbstractModelFile
218    {
219        $this->getFile()->addAnnotationBlockElement($this->getClassAnnotationBlock());
220
221        return $this;
222    }
223
224    protected function getModelByName(string $name): ?StructModel
225    {
226        return $this->getGenerator()->getStructByName($name);
227    }
228
229    protected function definePackageAnnotations(PhpAnnotationBlock $block): self
230    {
231        $packageName = $this->getPackageName();
232        if (!empty($packageName)) {
233            $block->addChild(new PhpAnnotation(self::ANNOTATION_PACKAGE, $packageName));
234        }
235        if (count($this->getModel()->getDocSubPackages()) > 0) {
236            $block->addChild(new PhpAnnotation(self::ANNOTATION_SUB_PACKAGE, implode(',', $this->getModel()->getDocSubPackages())));
237        }
238
239        return $this;
240    }
241
242    protected function getPackageName(): string
243    {
244        $packageName = '';
245        if (!empty($this->getGenerator()->getOptionPrefix())) {
246            $packageName = $this->getGenerator()->getOptionPrefix();
247        } elseif (!empty($this->getGenerator()->getOptionSuffix())) {
248            $packageName = $this->getGenerator()->getOptionSuffix();
249        }
250
251        return $packageName;
252    }
253
254    protected function defineGeneralAnnotations(PhpAnnotationBlock $block): self
255    {
256        foreach ($this->getGenerator()->getOptionAddComments() as $tagName => $tagValue) {
257            $block->addChild(new PhpAnnotation($tagName, $tagValue));
258        }
259
260        return $this;
261    }
262
263    protected function getClassAnnotationBlock(): PhpAnnotationBlock
264    {
265        $block = new PhpAnnotationBlock();
266        $block->addChild($this->getClassDeclarationLine());
267        $this->defineModelAnnotationsFromWsdl($block)->definePackageAnnotations($block)->defineGeneralAnnotations($block);
268
269        return $block;
270    }
271
272    protected function getClassDeclarationLine(): string
273    {
274        return sprintf($this->getClassDeclarationLineText(), $this->getModel()->getName(), $this->getModel()->getContextualPart());
275    }
276
277    protected function getClassDeclarationLineText(): string
278    {
279        return 'This class stands for %s %s';
280    }
281
282    protected function defineModelAnnotationsFromWsdl(PhpAnnotationBlock $block, AbstractModel $model = null): self
283    {
284        FileUtils::defineModelAnnotationsFromWsdl($block, $model instanceof AbstractModel ? $model : $this->getModel());
285
286        return $this;
287    }
288
289    protected function addClassElement(): AbstractModelFile
290    {
291        $class = new PhpClass($this->getModel()->getPackagedName(), $this->getModel()->isAbstract(), '' === $this->getModel()->getExtendsClassName() ? null : $this->getModel()->getExtendsClassName());
292        $this
293            ->defineConstants($class)
294            ->defineProperties($class)
295            ->defineMethods($class)
296            ->getFile()
297            ->addClassComponent($class)
298        ;
299
300        return $this;
301    }
302
303    protected function addDeclareDirective(): self
304    {
305        $this->getFile()->setDeclare(PhpDeclare::DIRECTIVE_STRICT_TYPES, 1);
306
307        return $this;
308    }
309
310    protected function defineNamespace(): self
311    {
312        if (!empty($this->getModel()->getNamespace())) {
313            $this->getFile()->setNamespace($this->getModel()->getNamespace());
314        }
315
316        return $this;
317    }
318
319    protected function defineUseStatements(): self
320    {
321        if (!empty($this->getModel()->getExtends())) {
322            $this->getFile()->addUse($this->getModel()->getExtends(), null, true);
323        }
324
325        return $this;
326    }
327
328    protected function defineConstants(PhpClass $class): self
329    {
330        $constants = new Constant($this->getGenerator());
331        $this->fillClassConstants($constants);
332        foreach ($constants as $constant) {
333            $annotationBlock = $this->getConstantAnnotationBlock($constant);
334            if (!empty($annotationBlock)) {
335                $class->addAnnotationBlockElement($annotationBlock);
336            }
337            $class->addConstantElement($constant);
338        }
339
340        return $this;
341    }
342
343    protected function defineProperties(PhpClass $class): self
344    {
345        $properties = new Property($this->getGenerator());
346        $this->fillClassProperties($properties);
347        foreach ($properties as $property) {
348            $annotationBlock = $this->getPropertyAnnotationBlock($property);
349            if (!empty($annotationBlock)) {
350                $class->addAnnotationBlockElement($annotationBlock);
351            }
352            $class->addPropertyElement($property);
353        }
354
355        return $this;
356    }
357
358    protected function defineMethods(PhpClass $class): self
359    {
360        $this->methods = new Method($this->getGenerator());
361        $this->fillClassMethods();
362        foreach ($this->methods as $method) {
363            $annotationBlock = $this->getMethodAnnotationBlock($method);
364            if (!empty($annotationBlock)) {
365                $class->addAnnotationBlockElement($annotationBlock);
366            }
367            $class->addMethodElement($method);
368        }
369
370        return $this;
371    }
372
373    abstract protected function fillClassConstants(Constant $constants): void;
374
375    abstract protected function getConstantAnnotationBlock(PhpConstant $constant): ?PhpAnnotationBlock;
376
377    abstract protected function fillClassProperties(Property $properties): void;
378
379    abstract protected function getPropertyAnnotationBlock(PhpProperty $property): ?PhpAnnotationBlock;
380
381    abstract protected function fillClassMethods(): void;
382
383    abstract protected function getMethodAnnotationBlock(PhpMethod $method): ?PhpAnnotationBlock;
384
385    protected function getStructAttribute(StructAttributeModel $attribute = null): ?StructAttributeModel
386    {
387        $struct = $this->getModel();
388        if (empty($attribute) && $struct instanceof StructModel && 1 === $struct->getAttributes()->count()) {
389            $attribute = $struct->getAttributes()->offsetGet(0);
390        }
391
392        return $attribute;
393    }
394
395    protected function getStructAttributeTypeGetAnnotation(StructAttributeModel $attribute = null, bool $returnArrayType = true, bool $nullableItemType = false): string
396    {
397        $attribute = $this->getStructAttribute($attribute);
398
399        if ($attribute->isXml()) {
400            return '\\DOMDocument|string|null';
401        }
402
403        return sprintf(
404            '%s%s%s',
405            $this->getStructAttributeTypeAsPhpType($attribute, false),
406            $this->useBrackets($attribute, $returnArrayType) ? '[]' : '',
407            !$nullableItemType && !$attribute->isNullable() && ($attribute->isRequired() || $attribute->isArray() || $attribute->isList()) ? '' : '|null'
408        );
409    }
410
411    protected function getStructAttributeTypeSetAnnotation(StructAttributeModel $attribute, bool $returnArrayType = true, bool $itemType = false): string
412    {
413        if ($attribute->isXml()) {
414            return '\\DOMDocument|string|null';
415        }
416
417        if ($attribute->isList()) {
418            return 'array|string';
419        }
420
421        return sprintf(
422            '%s%s',
423            $this->getStructAttributeTypeAsPhpType($attribute, $returnArrayType),
424            $this->useBrackets($attribute, !$itemType) ? '[]' : ''
425        );
426    }
427
428    protected function useBrackets(StructAttributeModel $attribute, bool $returnArrayType = true): bool
429    {
430        return $returnArrayType && $attribute->isArray();
431    }
432}