Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.56% covered (success)
95.56%
129 / 135
84.00% covered (warning)
84.00%
21 / 25
CRAP
0.00% covered (danger)
0.00%
0 / 1
Service
95.56% covered (success)
95.56%
129 / 135
84.00% covered (warning)
84.00%
21 / 25
58
0.00% covered (danger)
0.00%
0 / 1
 getOperationMethodReturnType
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
7
 setModel
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getModel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fillClassConstants
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getConstantAnnotationBlock
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 fillClassProperties
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPropertyAnnotationBlock
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 defineUseStatements
85.71% covered (warning)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
6.10
 getClassDeclarationLineText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 fillClassMethods
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 addSoapHeaderMethods
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addSoapHeaderFromMethod
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
8
 getSoapHeaderMethod
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
3.02
 getTypeFromName
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getSoapHeaderMethodName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addOperationsMethods
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addGetResultMethod
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addMainMethod
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getMethodAnnotationBlock
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 addAnnotationBlockForSoapHeaderMethod
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
4
 addAnnotationBlockForOperationMethod
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 addAnnotationBlockForgetResultMethod
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getServiceReturnTypes
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getModelFromMethod
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 setModelFromMethod
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace WsdlToPhp\PackageGenerator\File;
6
7use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
8use WsdlToPhp\PackageGenerator\Container\PhpElement\Constant as ConstantContainer;
9use WsdlToPhp\PackageGenerator\Container\PhpElement\Property as PropertyContainer;
10use WsdlToPhp\PackageGenerator\File\Element\PhpFunctionParameter;
11use WsdlToPhp\PackageGenerator\File\Validation\Rules;
12use WsdlToPhp\PackageGenerator\Generator\Generator;
13use WsdlToPhp\PackageGenerator\Model\AbstractModel;
14use WsdlToPhp\PackageGenerator\Model\Method;
15use WsdlToPhp\PackageGenerator\Model\Method as MethodModel;
16use WsdlToPhp\PackageGenerator\Model\Service as ServiceModel;
17use WsdlToPhp\PackageGenerator\Model\Struct as StructModel;
18use WsdlToPhp\PackageGenerator\Model\StructAttribute as StructAttributeModel;
19use WsdlToPhp\PackageGenerator\Parser\Wsdl\TagHeader;
20use WsdlToPhp\PhpGenerator\Element\PhpAnnotation;
21use WsdlToPhp\PhpGenerator\Element\PhpAnnotationBlock;
22use WsdlToPhp\PhpGenerator\Element\PhpConstant;
23use WsdlToPhp\PhpGenerator\Element\PhpFunctionParameter as PhpFunctionParameterBase;
24use WsdlToPhp\PhpGenerator\Element\PhpMethod;
25use WsdlToPhp\PhpGenerator\Element\PhpProperty;
26
27final class Service extends AbstractModelFile
28{
29    public const METHOD_SET_HEADER_PREFIX = 'setSoapHeader';
30    public const PARAM_SET_HEADER_NAMESPACE = 'namespace';
31    public const PARAM_SET_HEADER_MUSTUNDERSTAND = 'mustUnderstand';
32    public const PARAM_SET_HEADER_ACTOR = 'actor';
33    public const METHOD_GET_RESULT = 'getResult';
34
35    /**
36     * Method model can't be found in case the original method's name is unclean:
37     * - ex: my.operation.name becomes my_operation_name
38     * thus the Model from Model\Service::getMethod() can't be found
39     * So we store the generated name associated to the original method object.
40     */
41    protected array $methodNames = [];
42
43    public static function getOperationMethodReturnType(MethodModel $method, Generator $generator): string
44    {
45        $returnType = $method->getReturnType();
46
47        if (is_null($returnType)) {
48            return 'null';
49        }
50
51        if ((($struct = $generator->getStructByName($returnType)) instanceof StructModel) && !$struct->isRestriction()) {
52            if ($struct->isStruct()) {
53                $returnType = $struct->getPackagedName(true);
54            } elseif ($struct->isArray()) {
55                if (($structInheritance = $struct->getInheritanceStruct()) instanceof StructModel) {
56                    $returnType = sprintf('%s[]', $structInheritance->getPackagedName(true));
57                } else {
58                    $returnType = $struct->getInheritance();
59                }
60            }
61        }
62
63        return $returnType;
64    }
65
66    public function setModel(AbstractModel $model): self
67    {
68        if (!$model instanceof ServiceModel) {
69            throw new \InvalidArgumentException('Model must be an instance of a Service', __LINE__);
70        }
71
72        return parent::setModel($model);
73    }
74
75    public function getModel(): ?ServiceModel
76    {
77        return parent::getModel();
78    }
79
80    protected function fillClassConstants(ConstantContainer $constants): void
81    {
82    }
83
84    protected function getConstantAnnotationBlock(PhpConstant $constant): ?PhpAnnotationBlock
85    {
86        return null;
87    }
88
89    protected function fillClassProperties(PropertyContainer $properties): void
90    {
91    }
92
93    protected function getPropertyAnnotationBlock(PhpProperty $property): ?PhpAnnotationBlock
94    {
95        return null;
96    }
97
98    protected function defineUseStatements(): AbstractModelFile
99    {
100        $this->getFile()->addUse(\SoapFault::class);
101
102        /** @var Method $method */
103        foreach ($this->getModel()->getMethods() as $method) {
104            $soapHeaderTypes = $method->getMetaValue(TagHeader::META_SOAP_HEADER_TYPES, []);
105            if (!is_array($soapHeaderTypes)) {
106                continue;
107            }
108            foreach ($soapHeaderTypes as $soapHeaderType) {
109                $model = $this->getModelByName($soapHeaderType);
110                if (!$model instanceof StructModel) {
111                    continue;
112                }
113                if (!$model->isRestriction()) {
114                    continue;
115                }
116
117                $this->getFile()->addUse(\InvalidArgumentException::class);
118
119                break 2;
120            }
121        }
122
123        return parent::defineUseStatements();
124    }
125
126    protected function getClassDeclarationLineText(): string
127    {
128        return GeneratorOptions::VALUE_NONE === $this->getGenerator()->getOptionGatherMethods() ? 'This class stands for all operations' : parent::getClassDeclarationLineText();
129    }
130
131    protected function fillClassMethods(): void
132    {
133        $this
134            ->addSoapHeaderMethods()
135            ->addOperationsMethods()
136            ->addGetResultMethod()
137        ;
138    }
139
140    protected function addSoapHeaderMethods(): self
141    {
142        foreach ($this->getModel()->getMethods() as $method) {
143            $this->addSoapHeaderFromMethod($method);
144        }
145
146        return $this;
147    }
148
149    protected function addSoapHeaderFromMethod(MethodModel $method): self
150    {
151        $soapHeaderNames = $method->getMetaValue(TagHeader::META_SOAP_HEADER_NAMES, []);
152        $soapHeaderNamespaces = $method->getMetaValue(TagHeader::META_SOAP_HEADER_NAMESPACES, []);
153        $soapHeaderTypes = $method->getMetaValue(TagHeader::META_SOAP_HEADER_TYPES, []);
154        if (is_array($soapHeaderNames) && is_array($soapHeaderNamespaces) && is_array($soapHeaderTypes)) {
155            foreach ($soapHeaderNames as $index => $soapHeaderName) {
156                $methodName = $this->getSoapHeaderMethodName($soapHeaderName);
157                if (is_null($this->methods->get($methodName))) {
158                    $soapHeaderNamespace = array_key_exists($index, $soapHeaderNamespaces) ? $soapHeaderNamespaces[$index] : null;
159                    $soapHeaderType = array_key_exists($index, $soapHeaderTypes) ? $soapHeaderTypes[$index] : null;
160                    $this->methods->add($this->getSoapHeaderMethod($methodName, $soapHeaderName, $soapHeaderNamespace, $soapHeaderType));
161                }
162            }
163        }
164
165        return $this;
166    }
167
168    protected function getSoapHeaderMethod(string $methodName, string $soapHeaderName, string $soapHeaderNamespace, string $soapHeaderType): PhpMethod
169    {
170        try {
171            $method = new PhpMethod($methodName, [
172                $firstParameter = new PhpFunctionParameter(lcfirst($soapHeaderName), PhpFunctionParameterBase::NO_VALUE, $this->getTypeFromName($soapHeaderType)),
173                new PhpFunctionParameterBase(self::PARAM_SET_HEADER_NAMESPACE, $soapHeaderNamespace, self::TYPE_STRING),
174                new PhpFunctionParameterBase(self::PARAM_SET_HEADER_MUSTUNDERSTAND, false, self::TYPE_BOOL),
175                new PhpFunctionParameterBase(self::PARAM_SET_HEADER_ACTOR, null, '?'.self::TYPE_STRING),
176            ], self::TYPE_SELF);
177
178            $model = $this->getModelByName($soapHeaderType);
179            if ($model instanceof StructModel) {
180                $rules = new Rules($this, $method, new StructAttributeModel($model->getGenerator(), $soapHeaderType, $model->getName(), $model), $this->methods);
181                $rules->applyRules(lcfirst($soapHeaderName));
182                $firstParameter->setModel($model);
183            }
184            $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));
185        } catch (\InvalidArgumentException $exception) {
186            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);
187        }
188
189        return $method;
190    }
191
192    protected function getTypeFromName(string $name): ?string
193    {
194        return self::getPhpType(
195            $this->getStructAttributeTypeAsPhpType(new StructAttributeModel($this->generator, 'any', $name)),
196            $this->getGenerator()->getOptionXsdTypesPath(),
197            $this->getStructAttributeTypeAsPhpType(new StructAttributeModel($this->generator, 'any', $name))
198        );
199    }
200
201    protected function getSoapHeaderMethodName(string $soapHeaderName): string
202    {
203        return sprintf('%s%s', self::METHOD_SET_HEADER_PREFIX, ucfirst($soapHeaderName));
204    }
205
206    protected function addOperationsMethods(): self
207    {
208        foreach ($this->getModel()->getMethods() as $method) {
209            $this->addMainMethod($method);
210        }
211
212        return $this;
213    }
214
215    protected function addGetResultMethod(): self
216    {
217        $method = new PhpMethod(self::METHOD_GET_RESULT);
218        $method->addChild('return parent::getResult();');
219        $this->methods->add($method);
220
221        return $this;
222    }
223
224    protected function addMainMethod(MethodModel $method): self
225    {
226        $methodFile = new Operation($method, $this->getGenerator());
227        $mainMethod = $methodFile->getMainMethod();
228        $this->methods->add($mainMethod);
229        $this->setModelFromMethod($mainMethod, $method);
230
231        return $this;
232    }
233
234    protected function getMethodAnnotationBlock(PhpMethod $method): PhpAnnotationBlock
235    {
236        $annotationBlock = new PhpAnnotationBlock();
237        if (0 === mb_stripos($method->getName(), self::METHOD_SET_HEADER_PREFIX)) {
238            $this->addAnnotationBlockForSoapHeaderMethod($annotationBlock, $method);
239        } elseif (self::METHOD_GET_RESULT === $method->getName()) {
240            $this->addAnnotationBlockForgetResultMethod($annotationBlock);
241        } else {
242            $this->addAnnotationBlockForOperationMethod($annotationBlock, $method);
243        }
244
245        return $annotationBlock;
246    }
247
248    protected function addAnnotationBlockForSoapHeaderMethod(PhpAnnotationBlock $annotationBlock, PhpMethod $method): self
249    {
250        $methodParameters = $method->getParameters();
251        $firstParameter = array_shift($methodParameters);
252        if ($firstParameter instanceof PhpFunctionParameter) {
253            $annotationBlock->addChild(sprintf('Sets the %s SoapHeader param', ucfirst($firstParameter->getName())));
254            $firstParameterType = $firstParameter->getType();
255            if ($firstParameter->getModel() instanceof StructModel) {
256                $firstParameterType = $this->getStructAttributeTypeAsPhpType(new StructAttributeModel($firstParameter->getModel()->getGenerator(), $firstParameter->getName(), $firstParameter->getModel()->getName(), $firstParameter->getModel()));
257                if ($firstParameter->getModel()->isRestriction()) {
258                    $annotationBlock
259                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $firstParameter->getModel()->getPackagedName(true), StructEnum::METHOD_VALUE_IS_VALID)))
260                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $firstParameter->getModel()->getPackagedName(true), StructEnum::METHOD_GET_VALID_VALUES)))
261                        ->addChild(new PhpAnnotation(self::ANNOTATION_THROWS, \InvalidArgumentException::class))
262                    ;
263                }
264            }
265            $annotationBlock
266                ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $this->getModel()->getExtends(true), self::METHOD_SET_HEADER_PREFIX)))
267                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', $firstParameterType, $firstParameter->getName())))
268                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', self::TYPE_STRING, self::PARAM_SET_HEADER_NAMESPACE)))
269                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', self::TYPE_BOOL, self::PARAM_SET_HEADER_MUSTUNDERSTAND)))
270                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s|null $%s', self::TYPE_STRING, self::PARAM_SET_HEADER_ACTOR)))
271                ->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $this->getModel()->getPackagedName(true)))
272            ;
273        }
274
275        return $this;
276    }
277
278    protected function addAnnotationBlockForOperationMethod(PhpAnnotationBlock $annotationBlock, PhpMethod $method): self
279    {
280        if (($model = $this->getModelFromMethod($method)) instanceof MethodModel) {
281            $operationAnnotationBlock = new OperationAnnotationBlock($model, $this->getGenerator());
282            $operationAnnotationBlock->addAnnotationBlockForOperationMethod($annotationBlock);
283        }
284
285        return $this;
286    }
287
288    protected function addAnnotationBlockForgetResultMethod(PhpAnnotationBlock $annotationBlock): self
289    {
290        $annotationBlock
291            ->addChild('Returns the result')->addChild(new PhpAnnotation(self::ANNOTATION_SEE, sprintf('%s::getResult()', $this->getModel()->getExtends(true))))
292            ->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $this->getServiceReturnTypes()))
293        ;
294
295        return $this;
296    }
297
298    protected function getServiceReturnTypes(): string
299    {
300        $returnTypes = [];
301        foreach ($this->getModel()->getMethods() as $method) {
302            $returnTypes[] = self::getOperationMethodReturnType($method, $this->getGenerator());
303        }
304        natcasesort($returnTypes);
305
306        return implode('|', array_unique($returnTypes));
307    }
308
309    protected function getModelFromMethod(PhpMethod $method): ?MethodModel
310    {
311        $model = $this->getGenerator()->getServiceMethod($method->getName());
312        if (!$model instanceof MethodModel) {
313            $model = array_key_exists($method->getName(), $this->methodNames) ? $this->methodNames[$method->getName()] : null;
314        }
315
316        return $model;
317    }
318
319    protected function setModelFromMethod(PhpMethod $phpMethod, MethodModel $methodModel): self
320    {
321        $this->methodNames[$phpMethod->getName()] = $methodModel;
322
323        return $this;
324    }
325}