Passed
Push — develop ( 333d24...82ab60 )
by Mikaël
09:54
created

Service   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 297
Duplicated Lines 0 %

Test Coverage

Coverage 94.52%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 138
dl 0
loc 297
ccs 138
cts 146
cp 0.9452
rs 4.5599
c 1
b 1
f 0
wmc 58

25 Methods

Rating   Name   Duplication   Size   Complexity  
B getOperationMethodReturnType() 0 21 7
A setModel() 0 7 2
A fillClassConstants() 0 2 1
A getServiceReturnTypes() 0 9 2
A fillClassMethods() 0 6 1
A addSoapHeaderMethods() 0 7 2
A getModel() 0 3 1
A getSoapHeaderMethodName() 0 3 1
A getModelFromMethod() 0 8 3
A fillClassProperties() 0 2 1
A getSoapHeaderMethod() 0 22 3
A defineUseStatements() 0 26 6
A getMethodAnnotationBlock() 0 12 3
A getClassDeclarationLineText() 0 3 2
B addSoapHeaderFromMethod() 0 17 8
A getPropertyAnnotationBlock() 0 3 1
A getTypeFromName() 0 6 1
A addMainMethod() 0 8 1
A getConstantAnnotationBlock() 0 3 1
A addAnnotationBlockForOperationMethod() 0 8 2
A addAnnotationBlockForSoapHeaderMethod() 0 28 4
A addAnnotationBlockForgetResultMethod() 0 8 1
A addGetResultMethod() 0 7 1
A setModelFromMethod() 0 5 1
A addOperationsMethods() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like Service 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 Service, 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 SoapFault;
9
use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
10
use WsdlToPhp\PackageGenerator\Container\PhpElement\Constant as ConstantContainer;
11
use WsdlToPhp\PackageGenerator\Container\PhpElement\Property as PropertyContainer;
12
use WsdlToPhp\PackageGenerator\File\Element\PhpFunctionParameter;
13
use WsdlToPhp\PackageGenerator\File\Validation\Rules;
14
use WsdlToPhp\PackageGenerator\Generator\Generator;
15
use WsdlToPhp\PackageGenerator\Model\AbstractModel;
16
use WsdlToPhp\PackageGenerator\Model\Method;
17
use WsdlToPhp\PackageGenerator\Model\Method as MethodModel;
18
use WsdlToPhp\PackageGenerator\Model\Service as ServiceModel;
19
use WsdlToPhp\PackageGenerator\Model\Struct as StructModel;
20
use WsdlToPhp\PackageGenerator\Model\StructAttribute as StructAttributeModel;
21
use WsdlToPhp\PackageGenerator\Parser\Wsdl\TagHeader;
22
use WsdlToPhp\PhpGenerator\Element\PhpAnnotation;
23
use WsdlToPhp\PhpGenerator\Element\PhpAnnotationBlock;
24
use WsdlToPhp\PhpGenerator\Element\PhpConstant;
25
use WsdlToPhp\PhpGenerator\Element\PhpFunctionParameter as PhpFunctionParameterBase;
26
use WsdlToPhp\PhpGenerator\Element\PhpMethod;
27
use WsdlToPhp\PhpGenerator\Element\PhpProperty;
28
29
final class Service extends AbstractModelFile
30
{
31
    public const METHOD_SET_HEADER_PREFIX = 'setSoapHeader';
32
    public const PARAM_SET_HEADER_NAMESPACE = 'namespace';
33
    public const PARAM_SET_HEADER_MUSTUNDERSTAND = 'mustUnderstand';
34
    public const PARAM_SET_HEADER_ACTOR = 'actor';
35
    public const METHOD_GET_RESULT = 'getResult';
36
37
    /**
38
     * Method model can't be found in case the original method's name is unclean:
39
     * - ex: my.operation.name becomes my_operation_name
40
     * thus the Model from Model\Service::getMethod() can't be found
41
     * So we store the generated name associated to the original method object.
42
     */
43
    protected array $methodNames = [];
44
45 48
    public static function getOperationMethodReturnType(MethodModel $method, Generator $generator): string
46
    {
47 48
        $returnType = $method->getReturnType();
48
49 48
        if (is_null($returnType)) {
50 2
            return 'null';
51
        }
52
53 46
        if ((($struct = $generator->getStructByName($returnType)) instanceof StructModel) && !$struct->isRestriction()) {
54 40
            if ($struct->isStruct()) {
55 40
                $returnType = $struct->getPackagedName(true);
56 8
            } elseif ($struct->isArray()) {
57 8
                if (($structInheritance = $struct->getInheritanceStruct()) instanceof StructModel) {
58 8
                    $returnType = sprintf('%s[]', $structInheritance->getPackagedName(true));
59
                } else {
60 4
                    $returnType = $struct->getInheritance();
61
                }
62
            }
63
        }
64
65 46
        return $returnType;
66
    }
67
68 50
    public function setModel(AbstractModel $model): self
69
    {
70 50
        if (!$model instanceof ServiceModel) {
71 2
            throw new InvalidArgumentException('Model must be an instance of a Service', __LINE__);
72
        }
73
74 48
        return parent::setModel($model);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::setModel($model) returns the type WsdlToPhp\PackageGenerator\File\AbstractModelFile which includes types incompatible with the type-hinted return WsdlToPhp\PackageGenerator\File\Service.
Loading history...
75
    }
76
77 48
    public function getModel(): ?ServiceModel
78
    {
79 48
        return parent::getModel();
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::getModel() could return the type WsdlToPhp\PackageGenerator\Model\AbstractModel which includes types incompatible with the type-hinted return WsdlToPhp\PackageGenerator\Model\Service|null. Consider adding an additional type-check to rule them out.
Loading history...
80
    }
81
82
    protected function fillClassConstants(ConstantContainer $constants): void
83
    {
84
    }
85
86
    protected function getConstantAnnotationBlock(PhpConstant $constant): ?PhpAnnotationBlock
87
    {
88
        return null;
89
    }
90
91
    protected function fillClassProperties(PropertyContainer $properties): void
92
    {
93
    }
94
95
    protected function getPropertyAnnotationBlock(PhpProperty $property): ?PhpAnnotationBlock
96
    {
97
        return null;
98
    }
99
100 46
    protected function defineUseStatements(): AbstractModelFile
101
    {
102 46
        $this->getFile()->addUse(SoapFault::class);
103
104
        /** @var Method $method */
105 46
        foreach ($this->getModel()->getMethods() as $method) {
106 46
            $soapHeaderTypes = $method->getMetaValue(TagHeader::META_SOAP_HEADER_TYPES, []);
107 46
            if (!is_array($soapHeaderTypes)) {
108
                continue;
109
            }
110 46
            foreach ($soapHeaderTypes as $soapHeaderType) {
111 12
                $model = $this->getModelByName($soapHeaderType);
112 12
                if (!$model instanceof StructModel) {
113
                    continue;
114
                }
115 12
                if (!$model->isRestriction()) {
116 12
                    continue;
117
                }
118
119 2
                $this->getFile()->addUse(InvalidArgumentException::class);
120
121 2
                break 2;
122
            }
123
        }
124
125 46
        return parent::defineUseStatements();
126
    }
127
128 46
    protected function getClassDeclarationLineText(): string
129
    {
130 46
        return GeneratorOptions::VALUE_NONE === $this->getGenerator()->getOptionGatherMethods() ? 'This class stands for all operations' : parent::getClassDeclarationLineText();
131
    }
132
133 46
    protected function fillClassMethods(): void
134
    {
135
        $this
136 46
            ->addSoapHeaderMethods()
137 46
            ->addOperationsMethods()
138 46
            ->addGetResultMethod()
139
        ;
140
    }
141
142 46
    protected function addSoapHeaderMethods(): self
143
    {
144 46
        foreach ($this->getModel()->getMethods() as $method) {
145 46
            $this->addSoapHeaderFromMethod($method);
146
        }
147
148 46
        return $this;
149
    }
150
151 46
    protected function addSoapHeaderFromMethod(MethodModel $method): self
152
    {
153 46
        $soapHeaderNames = $method->getMetaValue(TagHeader::META_SOAP_HEADER_NAMES, []);
154 46
        $soapHeaderNamespaces = $method->getMetaValue(TagHeader::META_SOAP_HEADER_NAMESPACES, []);
155 46
        $soapHeaderTypes = $method->getMetaValue(TagHeader::META_SOAP_HEADER_TYPES, []);
156 46
        if (is_array($soapHeaderNames) && is_array($soapHeaderNamespaces) && is_array($soapHeaderTypes)) {
157 46
            foreach ($soapHeaderNames as $index => $soapHeaderName) {
158 12
                $methodName = $this->getSoapHeaderMethodName($soapHeaderName);
159 12
                if (is_null($this->methods->get($methodName))) {
160 12
                    $soapHeaderNamespace = array_key_exists($index, $soapHeaderNamespaces) ? $soapHeaderNamespaces[$index] : null;
161 12
                    $soapHeaderType = array_key_exists($index, $soapHeaderTypes) ? $soapHeaderTypes[$index] : null;
162 12
                    $this->methods->add($this->getSoapHeaderMethod($methodName, $soapHeaderName, $soapHeaderNamespace, $soapHeaderType));
0 ignored issues
show
Bug introduced by
It seems like $soapHeaderNamespace can also be of type null; however, parameter $soapHeaderNamespace of WsdlToPhp\PackageGenerat...::getSoapHeaderMethod() does only seem to accept string, 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

162
                    $this->methods->add($this->getSoapHeaderMethod($methodName, $soapHeaderName, /** @scrutinizer ignore-type */ $soapHeaderNamespace, $soapHeaderType));
Loading history...
Bug introduced by
It seems like $soapHeaderType can also be of type null; however, parameter $soapHeaderType of WsdlToPhp\PackageGenerat...::getSoapHeaderMethod() does only seem to accept string, 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

162
                    $this->methods->add($this->getSoapHeaderMethod($methodName, $soapHeaderName, $soapHeaderNamespace, /** @scrutinizer ignore-type */ $soapHeaderType));
Loading history...
163
                }
164
            }
165
        }
166
167 46
        return $this;
168
    }
169
170 12
    protected function getSoapHeaderMethod(string $methodName, string $soapHeaderName, string $soapHeaderNamespace, string $soapHeaderType): PhpMethod
171
    {
172
        try {
173 12
            $method = new PhpMethod($methodName, [
174 12
                $firstParameter = new PhpFunctionParameter(lcfirst($soapHeaderName), PhpFunctionParameterBase::NO_VALUE, $this->getTypeFromName($soapHeaderType)),
175 12
                new PhpFunctionParameterBase(self::PARAM_SET_HEADER_NAMESPACE, $soapHeaderNamespace, self::TYPE_STRING),
176 12
                new PhpFunctionParameterBase(self::PARAM_SET_HEADER_MUSTUNDERSTAND, false, self::TYPE_BOOL),
177 12
                new PhpFunctionParameterBase(self::PARAM_SET_HEADER_ACTOR, null, '?'.self::TYPE_STRING),
178
            ], self::TYPE_SELF);
179
180 12
            $model = $this->getModelByName($soapHeaderType);
181 12
            if ($model instanceof StructModel) {
182 12
                $rules = new Rules($this, $method, new StructAttributeModel($model->getGenerator(), $soapHeaderType, $model->getName(), $model), $this->methods);
183 12
                $rules->applyRules(lcfirst($soapHeaderName));
184 12
                $firstParameter->setModel($model);
185
            }
186 12
            $method->addChild(sprintf('return $this->%s($%s, \'%s\', $%s, $%s, $%s);', self::METHOD_SET_HEADER_PREFIX, self::PARAM_SET_HEADER_NAMESPACE, $soapHeaderName, lcfirst($soapHeaderName), self::PARAM_SET_HEADER_MUSTUNDERSTAND, self::PARAM_SET_HEADER_ACTOR));
187
        } catch (InvalidArgumentException $exception) {
188
            throw new InvalidArgumentException(sprintf('Unable to create function parameter for service "%s" with type "%s"', $this->getModel()->getName(), var_export($this->getTypeFromName($soapHeaderName), true)), __LINE__, $exception);
189
        }
190
191 12
        return $method;
192
    }
193
194 12
    protected function getTypeFromName(string $name): ?string
195
    {
196 12
        return self::getPhpType(
197 12
            $this->getStructAttributeTypeAsPhpType(new StructAttributeModel($this->generator, 'any', $name)),
198 12
            $this->getGenerator()->getOptionXsdTypesPath(),
199 12
            $this->getStructAttributeTypeAsPhpType(new StructAttributeModel($this->generator, 'any', $name))
200
        );
201
    }
202
203 12
    protected function getSoapHeaderMethodName(string $soapHeaderName): string
204
    {
205 12
        return sprintf('%s%s', self::METHOD_SET_HEADER_PREFIX, ucfirst($soapHeaderName));
206
    }
207
208 46
    protected function addOperationsMethods(): self
209
    {
210 46
        foreach ($this->getModel()->getMethods() as $method) {
211 46
            $this->addMainMethod($method);
212
        }
213
214 46
        return $this;
215
    }
216
217 46
    protected function addGetResultMethod(): self
218
    {
219 46
        $method = new PhpMethod(self::METHOD_GET_RESULT);
220 46
        $method->addChild('return parent::getResult();');
221 46
        $this->methods->add($method);
222
223 46
        return $this;
224
    }
225
226 46
    protected function addMainMethod(MethodModel $method): self
227
    {
228 46
        $methodFile = new Operation($method, $this->getGenerator());
229 46
        $mainMethod = $methodFile->getMainMethod();
230 46
        $this->methods->add($mainMethod);
231 46
        $this->setModelFromMethod($mainMethod, $method);
232
233 46
        return $this;
234
    }
235
236 46
    protected function getMethodAnnotationBlock(PhpMethod $method): PhpAnnotationBlock
237
    {
238 46
        $annotationBlock = new PhpAnnotationBlock();
239 46
        if (0 === mb_stripos($method->getName(), self::METHOD_SET_HEADER_PREFIX)) {
240 12
            $this->addAnnotationBlockForSoapHeaderMethod($annotationBlock, $method);
241 46
        } elseif (self::METHOD_GET_RESULT === $method->getName()) {
242 46
            $this->addAnnotationBlockForgetResultMethod($annotationBlock);
243
        } else {
244 46
            $this->addAnnotationBlockForOperationMethod($annotationBlock, $method);
245
        }
246
247 46
        return $annotationBlock;
248
    }
249
250 12
    protected function addAnnotationBlockForSoapHeaderMethod(PhpAnnotationBlock $annotationBlock, PhpMethod $method): self
251
    {
252 12
        $methodParameters = $method->getParameters();
253 12
        $firstParameter = array_shift($methodParameters);
254 12
        if ($firstParameter instanceof PhpFunctionParameter) {
255 12
            $annotationBlock->addChild(sprintf('Sets the %s SoapHeader param', ucfirst($firstParameter->getName())));
256 12
            $firstParameterType = $firstParameter->getType();
257 12
            if ($firstParameter->getModel() instanceof StructModel) {
258 12
                $firstParameterType = $this->getStructAttributeTypeAsPhpType(new StructAttributeModel($firstParameter->getModel()->getGenerator(), $firstParameter->getName(), $firstParameter->getModel()->getName(), $firstParameter->getModel()));
259 12
                if ($firstParameter->getModel()->isRestriction()) {
260
                    $annotationBlock
261 2
                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $firstParameter->getModel()->getPackagedName(true), StructEnum::METHOD_VALUE_IS_VALID)))
262 2
                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $firstParameter->getModel()->getPackagedName(true), StructEnum::METHOD_GET_VALID_VALUES)))
263 2
                        ->addChild(new PhpAnnotation(self::ANNOTATION_THROWS, InvalidArgumentException::class))
264
                    ;
265
                }
266
            }
267
            $annotationBlock
268 12
                ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $this->getModel()->getExtends(true), self::METHOD_SET_HEADER_PREFIX)))
269 12
                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', $firstParameterType, $firstParameter->getName())))
270 12
                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', self::TYPE_STRING, self::PARAM_SET_HEADER_NAMESPACE)))
271 12
                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', self::TYPE_BOOL, self::PARAM_SET_HEADER_MUSTUNDERSTAND)))
272 12
                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s|null $%s', self::TYPE_STRING, self::PARAM_SET_HEADER_ACTOR)))
273 12
                ->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $this->getModel()->getPackagedName(true)))
274
            ;
275
        }
276
277 12
        return $this;
278
    }
279
280 46
    protected function addAnnotationBlockForOperationMethod(PhpAnnotationBlock $annotationBlock, PhpMethod $method): self
281
    {
282 46
        if (($model = $this->getModelFromMethod($method)) instanceof MethodModel) {
283 46
            $operationAnnotationBlock = new OperationAnnotationBlock($model, $this->getGenerator());
284 46
            $operationAnnotationBlock->addAnnotationBlockForOperationMethod($annotationBlock);
285
        }
286
287 46
        return $this;
288
    }
289
290 46
    protected function addAnnotationBlockForgetResultMethod(PhpAnnotationBlock $annotationBlock): self
291
    {
292
        $annotationBlock
293 46
            ->addChild('Returns the result')->addChild(new PhpAnnotation(self::ANNOTATION_SEE, sprintf('%s::getResult()', $this->getModel()->getExtends(true))))
294 46
            ->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $this->getServiceReturnTypes()))
295
        ;
296
297 46
        return $this;
298
    }
299
300 46
    protected function getServiceReturnTypes(): string
301
    {
302 46
        $returnTypes = [];
303 46
        foreach ($this->getModel()->getMethods() as $method) {
304 46
            $returnTypes[] = self::getOperationMethodReturnType($method, $this->getGenerator());
305
        }
306 46
        natcasesort($returnTypes);
307
308 46
        return implode('|', array_unique($returnTypes));
309
    }
310
311 46
    protected function getModelFromMethod(PhpMethod $method): ?MethodModel
312
    {
313 46
        $model = $this->getGenerator()->getServiceMethod($method->getName());
314 46
        if (!$model instanceof MethodModel) {
315 8
            $model = array_key_exists($method->getName(), $this->methodNames) ? $this->methodNames[$method->getName()] : null;
316
        }
317
318 46
        return $model;
319
    }
320
321 46
    protected function setModelFromMethod(PhpMethod $phpMethod, MethodModel $methodModel): self
322
    {
323 46
        $this->methodNames[$phpMethod->getName()] = $methodModel;
324
325 46
        return $this;
326
    }
327
}
328