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