Passed
Push — feature/issue-124 ( f27344...703846 )
by Mikaël
29:19
created

AbstractModelFile   F

Complexity

Total Complexity 86

Size/Duplication

Total Lines 390
Duplicated Lines 0 %

Test Coverage

Coverage 98.86%

Importance

Changes 4
Bugs 0 Features 1
Metric Value
eloc 164
dl 0
loc 390
ccs 174
cts 176
cp 0.9886
rs 2
c 4
b 0
f 1
wmc 86

33 Methods

Rating   Name   Duplication   Size   Complexity  
B getRestrictionFromStructAttribute() 0 18 7
A isAttributeAList() 0 3 1
A addAnnotationBlock() 0 5 1
A defineMethods() 0 13 3
A getModel() 0 3 1
B getStructAttributeType() 0 29 11
A getStructAttributeTypeAsPhpType() 0 9 2
A addDeclareDirective() 0 5 1
A definePackageAnnotations() 0 11 3
A getFileDestination() 0 7 2
A defineProperties() 0 13 3
A defineModelAnnotationsFromWsdl() 0 5 2
B getStructAttributeTypeGetAnnotation() 0 9 7
A getModelFromStructAttribute() 0 3 1
A getClassDeclarationLineText() 0 3 1
A getValidType() 0 3 2
A useBrackets() 0 3 2
A defineGeneralAnnotations() 0 7 2
A addClassElement() 0 12 2
A defineNamespace() 0 7 2
A getStructAttributeTypeHint() 0 5 4
A getStructAttributeTypeSetAnnotation() 0 5 2
A setModel() 0 11 1
A getPackageName() 0 10 3
A defineConstants() 0 13 3
A getStructAttribute() 0 8 4
A getClassAnnotationBlock() 0 7 1
A getDestinationFolder() 0 10 4
A getClassDeclarationLine() 0 3 1
A getPhpType() 0 3 2
A writeFile() 0 17 2
A defineUseStatements() 0 7 2
A getModelByName() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractModelFile often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractModelFile, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace WsdlToPhp\PackageGenerator\File;
6
7
use InvalidArgumentException;
8
use WsdlToPhp\PackageGenerator\ConfigurationReader\XsdTypes;
9
use WsdlToPhp\PackageGenerator\Container\PhpElement\Constant;
10
use WsdlToPhp\PackageGenerator\Container\PhpElement\Method;
11
use WsdlToPhp\PackageGenerator\Container\PhpElement\Property;
12
use WsdlToPhp\PackageGenerator\File\Utils as FileUtils;
13
use WsdlToPhp\PackageGenerator\Generator\Utils as GeneratorUtils;
14
use WsdlToPhp\PackageGenerator\Model\AbstractModel;
15
use WsdlToPhp\PackageGenerator\Model\Struct as StructModel;
16
use WsdlToPhp\PackageGenerator\Model\StructAttribute as StructAttributeModel;
17
use WsdlToPhp\PhpGenerator\Component\PhpClass;
18
use WsdlToPhp\PhpGenerator\Element\PhpAnnotation;
19
use WsdlToPhp\PhpGenerator\Element\PhpAnnotationBlock;
20
use WsdlToPhp\PhpGenerator\Element\PhpConstant;
21
use WsdlToPhp\PhpGenerator\Element\PhpDeclare;
22
use WsdlToPhp\PhpGenerator\Element\PhpMethod;
23
use WsdlToPhp\PhpGenerator\Element\PhpProperty;
24
25
abstract class AbstractModelFile extends AbstractFile
26
{
27
    public const ANNOTATION_META_LENGTH = 250;
28
    public const ANNOTATION_LONG_LENGTH = 1000;
29
    public const ANNOTATION_PACKAGE = 'package';
30
    public const ANNOTATION_SUB_PACKAGE = 'subpackage';
31
    public const ANNOTATION_RETURN = 'return';
32
    public const ANNOTATION_USES = 'uses';
33
    public const ANNOTATION_PARAM = 'param';
34
    public const ANNOTATION_VAR = 'var';
35
    public const ANNOTATION_SEE = 'see';
36
    public const ANNOTATION_THROWS = 'throws';
37
    public const METHOD_CONSTRUCT = '__construct';
38
    public const TYPE_ARRAY = 'array';
39
    public const TYPE_BOOL = 'bool';
40
    public const TYPE_STRING = 'string';
41
    public const TYPE_SELF = 'self';
42
43
    protected Method $methods;
44
45
    private ?AbstractModel $model = null;
46
47 177
    public function getFileDestination(bool $withSrc = true): string
48
    {
49 177
        return sprintf(
50 177
            '%s%s%s',
51 177
            $this->getDestinationFolder($withSrc),
52 177
            $this->getModel()->getSubDirectory(),
53 177
            !empty($this->getModel()->getSubDirectory()) ? '/' : ''
54
        );
55
    }
56
57 177
    public function getDestinationFolder(bool $withSrc = true): string
58
    {
59 177
        $src = rtrim($this->generator->getOptionSrcDirname(), DIRECTORY_SEPARATOR);
60
61 177
        return sprintf(
62 177
            '%s%s%s%s',
63 177
            $this->getGenerator()->getOptionDestination(),
64 177
            (bool) $withSrc && !empty($src) ? $src.DIRECTORY_SEPARATOR : '',
65 177
            str_replace('\\', DIRECTORY_SEPARATOR, $this->getGenerator()->getOptionNamespacePrefix()),
66 177
            $this->getGenerator()->getOptionNamespacePrefix() ? DIRECTORY_SEPARATOR : ''
67
        );
68
    }
69
70 167
    public function writeFile(bool $withSrc = true): void
71
    {
72 167
        if (!$this->getModel()) {
73 2
            throw new InvalidArgumentException('You MUST define the model before being able to generate the file', __LINE__);
74
        }
75
76 165
        GeneratorUtils::createDirectory($this->getFileDestination($withSrc));
77
78
        $this
79 165
            ->addDeclareDirective()
80 165
            ->defineNamespace()
81 165
            ->defineUseStatements()
82 165
            ->addAnnotationBlock()
83 165
            ->addClassElement()
84
        ;
85
86 165
        parent::writeFile();
87 165
    }
88
89 191
    public function setModel(AbstractModel $model): self
90
    {
91 191
        $this->model = $model;
92
93
        $this
94 191
            ->getFile()
95 191
            ->getMainElement()
96 191
            ->setName($model->getPackagedName())
97
        ;
98
99 191
        return $this;
100
    }
101
102 193
    public function getModel(): ?AbstractModel
103
    {
104 193
        return $this->model;
105
    }
106
107 113
    public function getModelFromStructAttribute(StructAttributeModel $attribute = null): ?StructModel
108
    {
109 113
        return $this->getStructAttribute($attribute)->getTypeStruct();
110
    }
111
112 110
    public function getRestrictionFromStructAttribute(StructAttributeModel $attribute = null): ?StructModel
113
    {
114 110
        $model = $this->getModelFromStructAttribute($attribute);
115 110
        if ($model instanceof StructModel) {
116
            // list are mainly scalar values of basic types (string, int, etc.) or of Restriction values
117 100
            if ($model->isList()) {
118 6
                $subModel = $this->getModelByName($model->getList());
119 6
                if ($subModel && $subModel->isRestriction()) {
120 4
                    $model = $subModel;
121 2
                } elseif (!$model->isRestriction()) {
122 6
                    $model = null;
123
                }
124 96
            } elseif (!$model->isRestriction()) {
125 92
                $model = null;
126
            }
127
        }
128
129 110
        return $model;
130
    }
131
132 101
    public function isAttributeAList(StructAttributeModel $attribute = null): bool
133
    {
134 101
        return $this->getStructAttribute($attribute)->isList();
135
    }
136
137 113
    public function getStructAttributeType(StructAttributeModel $attribute = null, bool $namespaced = false): string
138
    {
139 113
        $attribute = $this->getStructAttribute($attribute);
140 113
        $inheritance = $attribute->getInheritance();
141 113
        $type = empty($inheritance) ? $attribute->getType() : $inheritance;
142
143 113
        if (!empty($type) && ($struct = $this->getGenerator()->getStructByName($type))) {
144 97
            $inheritance = $struct->getTopInheritance();
145 97
            if (!empty($inheritance)) {
146 69
                $type = str_replace('[]', '', $inheritance);
147
            } else {
148 75
                $type = $struct->getPackagedName($namespaced);
149
            }
150
        }
151
152 113
        $model = $this->getModelFromStructAttribute($attribute);
153 113
        if ($model instanceof StructModel) {
154
            // issue #84: union is considered as string as it would be difficult to have a method that accepts multiple object types.
155
            // If the property has to be an object of multiple types => new issue...
156 103
            if ($model->isRestriction() || $model->isUnion()) {
157 51
                $type = self::TYPE_STRING;
158 99
            } elseif ($model->isStruct()) {
159 87
                $type = $model->getPackagedName($namespaced);
160 50
            } elseif ($model->isArray() && ($inheritanceStruct = $model->getInheritanceStruct()) instanceof StructModel) {
161 8
                $type = $inheritanceStruct->getPackagedName($namespaced);
162
            }
163
        }
164
165 113
        return $type;
166
    }
167
168 113
    public function getStructAttributeTypeAsPhpType(StructAttributeModel $attribute = null): string
169
    {
170 113
        $attribute = $this->getStructAttribute($attribute);
171 113
        $attributeType = $this->getStructAttributeType($attribute, true);
172 113
        if (XsdTypes::instance($this->getGenerator()->getOptionXsdTypesPath())->isXsd($attributeType)) {
0 ignored issues
show
Bug introduced by
The method isXsd() does not exist on WsdlToPhp\PackageGenerat...ader\AbstractYamlReader. It seems like you code against a sub-type of WsdlToPhp\PackageGenerat...ader\AbstractYamlReader such as WsdlToPhp\PackageGenerat...gurationReader\XsdTypes. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

172
        if (XsdTypes::instance($this->getGenerator()->getOptionXsdTypesPath())->/** @scrutinizer ignore-call */ isXsd($attributeType)) {
Loading history...
173 91
            $attributeType = self::getPhpType($attributeType, $this->getGenerator()->getOptionXsdTypesPath());
174
        }
175
176 113
        return $attributeType;
177
    }
178
179
    /**
180
     * See http://php.net/manual/fr/language.oop5.typehinting.php for these cases
181
     * Also see http://www.w3schools.com/schema/schema_dtypes_numeric.asp.
182
     *
183
     * @param mixed $type
184
     * @param null  $xsdTypesPath
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $xsdTypesPath is correct as it would always require null to be passed?
Loading history...
185
     * @param mixed $fallback
186
     *
187
     * @return mixed
188
     */
189
    public static function getValidType($type, $xsdTypesPath = null, $fallback = null)
190
    {
191
        return XsdTypes::instance($xsdTypesPath)->isXsd(str_replace('[]', '', $type)) ? $fallback : $type;
192
    }
193
194
    /**
195
     * See http://php.net/manual/fr/language.oop5.typehinting.php for these cases
196
     * Also see http://www.w3schools.com/schema/schema_dtypes_numeric.asp.
197
     *
198
     * @param mixed $type
199
     * @param null  $xsdTypesPath
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $xsdTypesPath is correct as it would always require null to be passed?
Loading history...
200
     * @param mixed $fallback
201
     *
202
     * @return mixed
203
     */
204 113
    public static function getPhpType($type, $xsdTypesPath = null, $fallback = self::TYPE_STRING)
205
    {
206 113
        return XsdTypes::instance($xsdTypesPath)->isXsd(str_replace('[]', '', $type)) ? XsdTypes::instance($xsdTypesPath)->phpType($type) : $fallback;
0 ignored issues
show
Bug introduced by
The method phpType() does not exist on WsdlToPhp\PackageGenerat...ader\AbstractYamlReader. It seems like you code against a sub-type of WsdlToPhp\PackageGenerat...ader\AbstractYamlReader such as WsdlToPhp\PackageGenerat...gurationReader\XsdTypes. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

206
        return XsdTypes::instance($xsdTypesPath)->isXsd(str_replace('[]', '', $type)) ? XsdTypes::instance($xsdTypesPath)->/** @scrutinizer ignore-call */ phpType($type) : $fallback;
Loading history...
207
    }
208
209 165
    protected function addAnnotationBlock(): AbstractModelFile
210
    {
211 165
        $this->getFile()->addAnnotationBlockElement($this->getClassAnnotationBlock());
212
213 165
        return $this;
214
    }
215
216 18
    protected function getModelByName(string $name): ?StructModel
217
    {
218 18
        return $this->getGenerator()->getStructByName($name);
219
    }
220
221 155
    protected function definePackageAnnotations(PhpAnnotationBlock $block): self
222
    {
223 155
        $packageName = $this->getPackageName();
224 155
        if (!empty($packageName)) {
225 137
            $block->addChild(new PhpAnnotation(self::ANNOTATION_PACKAGE, $packageName));
226
        }
227 155
        if (count($this->getModel()->getDocSubPackages()) > 0) {
228 155
            $block->addChild(new PhpAnnotation(self::ANNOTATION_SUB_PACKAGE, implode(',', $this->getModel()->getDocSubPackages())));
229
        }
230
231 155
        return $this;
232
    }
233
234 155
    protected function getPackageName(): string
235
    {
236 155
        $packageName = '';
237 155
        if (!empty($this->getGenerator()->getOptionPrefix())) {
238 133
            $packageName = $this->getGenerator()->getOptionPrefix();
239 22
        } elseif (!empty($this->getGenerator()->getOptionSuffix())) {
240 4
            $packageName = $this->getGenerator()->getOptionSuffix();
241
        }
242
243 155
        return $packageName;
244
    }
245
246 155
    protected function defineGeneralAnnotations(PhpAnnotationBlock $block): self
247
    {
248 155
        foreach ($this->getGenerator()->getOptionAddComments() as $tagName => $tagValue) {
249 143
            $block->addChild(new PhpAnnotation($tagName, $tagValue));
250
        }
251
252 155
        return $this;
253
    }
254
255 155
    protected function getClassAnnotationBlock(): PhpAnnotationBlock
256
    {
257 155
        $block = new PhpAnnotationBlock();
258 155
        $block->addChild($this->getClassDeclarationLine());
259 155
        $this->defineModelAnnotationsFromWsdl($block)->definePackageAnnotations($block)->defineGeneralAnnotations($block);
260
261 155
        return $block;
262
    }
263
264 155
    protected function getClassDeclarationLine(): string
265
    {
266 155
        return sprintf($this->getClassDeclarationLineText(), $this->getModel()->getName(), $this->getModel()->getContextualPart());
267
    }
268
269 147
    protected function getClassDeclarationLineText(): string
270
    {
271 147
        return 'This class stands for %s %s';
272
    }
273
274 155
    protected function defineModelAnnotationsFromWsdl(PhpAnnotationBlock $block, AbstractModel $model = null): self
275
    {
276 155
        FileUtils::defineModelAnnotationsFromWsdl($block, $model instanceof AbstractModel ? $model : $this->getModel());
0 ignored issues
show
Bug introduced by
It seems like $model instanceof WsdlTo...del : $this->getModel() can also be of type null; however, parameter $model of WsdlToPhp\PackageGenerat...elAnnotationsFromWsdl() does only seem to accept WsdlToPhp\PackageGenerator\Model\AbstractModel, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

276
        FileUtils::defineModelAnnotationsFromWsdl($block, /** @scrutinizer ignore-type */ $model instanceof AbstractModel ? $model : $this->getModel());
Loading history...
277
278 155
        return $this;
279
    }
280
281 165
    protected function addClassElement(): AbstractModelFile
282
    {
283 165
        $class = new PhpClass($this->getModel()->getPackagedName(), $this->getModel()->isAbstract(), '' === $this->getModel()->getExtendsClassName() ? null : $this->getModel()->getExtendsClassName());
284
        $this
285 165
            ->defineConstants($class)
286 165
            ->defineProperties($class)
287 165
            ->defineMethods($class)
288 165
            ->getFile()
289 165
            ->addClassComponent($class)
290
        ;
291
292 165
        return $this;
293
    }
294
295 165
    protected function addDeclareDirective(): self
296
    {
297 165
        $this->getFile()->setDeclare(PhpDeclare::DIRECTIVE_STRICT_TYPES, 1);
298
299 165
        return $this;
300
    }
301
302 165
    protected function defineNamespace(): self
303
    {
304 165
        if (!empty($this->getModel()->getNamespace())) {
305 161
            $this->getFile()->setNamespace($this->getModel()->getNamespace());
306
        }
307
308 165
        return $this;
309
    }
310
311 165
    protected function defineUseStatements(): self
312
    {
313 165
        if (!empty($this->getModel()->getExtends())) {
314 155
            $this->getFile()->addUse($this->getModel()->getExtends(), null, true);
315
        }
316
317 165
        return $this;
318
    }
319
320 165
    protected function defineConstants(PhpClass $class): self
321
    {
322 165
        $constants = new Constant($this->getGenerator());
323 165
        $this->fillClassConstants($constants);
324 165
        foreach ($constants as $constant) {
325 43
            $annotationBlock = $this->getConstantAnnotationBlock($constant);
326 43
            if (!empty($annotationBlock)) {
327 43
                $class->addAnnotationBlockElement($annotationBlock);
328
            }
329 43
            $class->addConstantElement($constant);
330
        }
331
332 165
        return $this;
333
    }
334
335 165
    protected function defineProperties(PhpClass $class): self
336
    {
337 165
        $properties = new Property($this->getGenerator());
338 165
        $this->fillClassProperties($properties);
339 165
        foreach ($properties as $property) {
340 101
            $annotationBlock = $this->getPropertyAnnotationBlock($property);
341 101
            if (!empty($annotationBlock)) {
342 101
                $class->addAnnotationBlockElement($annotationBlock);
343
            }
344 101
            $class->addPropertyElement($property);
345
        }
346
347 165
        return $this;
348
    }
349
350 165
    protected function defineMethods(PhpClass $class): self
351
    {
352 165
        $this->methods = new Method($this->getGenerator());
353 165
        $this->fillClassMethods();
354 165
        foreach ($this->methods as $method) {
355 161
            $annotationBlock = $this->getMethodAnnotationBlock($method);
356 161
            if (!empty($annotationBlock)) {
357 161
                $class->addAnnotationBlockElement($annotationBlock);
358
            }
359 161
            $class->addMethodElement($method);
360
        }
361
362 165
        return $this;
363
    }
364
365
    abstract protected function fillClassConstants(Constant $constants): void;
366
367
    abstract protected function getConstantAnnotationBlock(PhpConstant $constant): ?PhpAnnotationBlock;
368
369
    abstract protected function fillClassProperties(Property $properties): void;
370
371
    abstract protected function getPropertyAnnotationBlock(PhpProperty $property): ?PhpAnnotationBlock;
372
373
    abstract protected function fillClassMethods(): void;
374
375
    abstract protected function getMethodAnnotationBlock(PhpMethod $method): ?PhpAnnotationBlock;
376
377 113
    protected function getStructAttribute(StructAttributeModel $attribute = null): ?StructAttributeModel
378
    {
379 113
        $struct = $this->getModel();
380 113
        if (empty($attribute) && $struct instanceof StructModel && 1 === $struct->getAttributes()->count()) {
381 17
            $attribute = $struct->getAttributes()->offsetGet(0);
382
        }
383
384 113
        return $attribute;
385
    }
386
387 101
    protected function getStructAttributeTypeGetAnnotation(StructAttributeModel $attribute = null, bool $returnArrayType = true, bool $nullableItemType = false): string
388
    {
389 101
        $attribute = $this->getStructAttribute($attribute);
390
391 101
        if ($attribute->isXml()) {
392 2
            return '\\DOMDocument|string|null';
393
        }
394
395 101
        return sprintf('%s%s%s', $this->getStructAttributeTypeAsPhpType($attribute), $this->useBrackets($attribute, $returnArrayType) ? '[]' : '', !$nullableItemType && ($attribute->isRequired() || $attribute->isArray() || $attribute->isList()) ? '' : '|null');
396
    }
397
398 101
    protected function getStructAttributeTypeSetAnnotation(StructAttributeModel $attribute = null, bool $returnArrayType = true): string
399
    {
400 101
        $attribute = $this->getStructAttribute($attribute);
401
402 101
        return sprintf('%s%s', $this->getStructAttributeTypeAsPhpType($attribute), $this->useBrackets($attribute, $returnArrayType) ? '[]' : '');
403
    }
404
405 101
    protected function useBrackets(StructAttributeModel $attribute, bool $returnArrayType = true): bool
406
    {
407 101
        return $returnArrayType && $attribute->isArray();
408
    }
409
410 101
    protected function getStructAttributeTypeHint(StructAttributeModel $attribute = null, bool $returnArrayType = true): string
411
    {
412 101
        $attribute = $this->getStructAttribute($attribute);
413
414 101
        return ($returnArrayType && ($attribute->isArray() || $this->isAttributeAList($attribute))) ? self::TYPE_ARRAY : $this->getStructAttributeType($attribute, true);
415
    }
416
}
417