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    /**
67     * @throws \InvalidArgumentException
68     */
69    public function setModel(AbstractModel $model): self
70    {
71        if (!$model instanceof ServiceModel) {
72            throw new \InvalidArgumentException('Model must be an instance of a Service', __LINE__);
73        }
74
75        return parent::setModel($model);
76    }
77
78    public function getModel(): ?ServiceModel
79    {
80        return parent::getModel();
81    }
82
83    protected function fillClassConstants(ConstantContainer $constants): void
84    {
85    }
86
87    protected function getConstantAnnotationBlock(PhpConstant $constant): ?PhpAnnotationBlock
88    {
89        return null;
90    }
91
92    protected function fillClassProperties(PropertyContainer $properties): void
93    {
94    }
95
96    protected function getPropertyAnnotationBlock(PhpProperty $property): ?PhpAnnotationBlock
97    {
98        return null;
99    }
100
101    protected function defineUseStatements(): AbstractModelFile
102    {
103        $this->getFile()->addUse(\SoapFault::class);
104
105        /** @var Method $method */
106        foreach ($this->getModel()->getMethods() as $method) {
107            $soapHeaderTypes = $method->getMetaValue(TagHeader::META_SOAP_HEADER_TYPES, []);
108            if (!is_array($soapHeaderTypes)) {
109                continue;
110            }
111            foreach ($soapHeaderTypes as $soapHeaderType) {
112                $model = $this->getModelByName($soapHeaderType);
113                if (!$model instanceof StructModel) {
114                    continue;
115                }
116                if (!$model->isRestriction()) {
117                    continue;
118                }
119
120                $this->getFile()->addUse(\InvalidArgumentException::class);
121
122                break 2;
123            }
124        }
125
126        return parent::defineUseStatements();
127    }
128
129    protected function getClassDeclarationLineText(): string
130    {
131        return GeneratorOptions::VALUE_NONE === $this->getGenerator()->getOptionGatherMethods() ? 'This class stands for all operations' : parent::getClassDeclarationLineText();
132    }
133
134    protected function fillClassMethods(): void
135    {
136        $this
137            ->addSoapHeaderMethods()
138            ->addOperationsMethods()
139            ->addGetResultMethod()
140        ;
141    }
142
143    protected function addSoapHeaderMethods(): self
144    {
145        foreach ($this->getModel()->getMethods() as $method) {
146            $this->addSoapHeaderFromMethod($method);
147        }
148
149        return $this;
150    }
151
152    protected function addSoapHeaderFromMethod(MethodModel $method): self
153    {
154        $soapHeaderNames = $method->getMetaValue(TagHeader::META_SOAP_HEADER_NAMES, []);
155        $soapHeaderNamespaces = $method->getMetaValue(TagHeader::META_SOAP_HEADER_NAMESPACES, []);
156        $soapHeaderTypes = $method->getMetaValue(TagHeader::META_SOAP_HEADER_TYPES, []);
157        if (is_array($soapHeaderNames) && is_array($soapHeaderNamespaces) && is_array($soapHeaderTypes)) {
158            foreach ($soapHeaderNames as $index => $soapHeaderName) {
159                $methodName = $this->getSoapHeaderMethodName($soapHeaderName);
160                if (is_null($this->methods->get($methodName))) {
161                    $soapHeaderNamespace = array_key_exists($index, $soapHeaderNamespaces) ? $soapHeaderNamespaces[$index] : null;
162                    $soapHeaderType = array_key_exists($index, $soapHeaderTypes) ? $soapHeaderTypes[$index] : null;
163                    $this->methods->add($this->getSoapHeaderMethod($methodName, $soapHeaderName, $soapHeaderNamespace, $soapHeaderType));
164                }
165            }
166        }
167
168        return $this;
169    }
170
171    protected function getSoapHeaderMethod(string $methodName, string $soapHeaderName, string $soapHeaderNamespace, string $soapHeaderType): PhpMethod
172    {
173        try {
174            $method = new PhpMethod($methodName, [
175                $firstParameter = new PhpFunctionParameter(lcfirst($soapHeaderName), PhpFunctionParameterBase::NO_VALUE, $this->getTypeFromName($soapHeaderType)),
176                new PhpFunctionParameterBase(self::PARAM_SET_HEADER_NAMESPACE, $soapHeaderNamespace, self::TYPE_STRING),
177                new PhpFunctionParameterBase(self::PARAM_SET_HEADER_MUSTUNDERSTAND, false, self::TYPE_BOOL),
178                new PhpFunctionParameterBase(self::PARAM_SET_HEADER_ACTOR, null, self::TYPE_STRING),
179            ], self::TYPE_SELF);
180
181            $model = $this->getModelByName($soapHeaderType);
182            if ($model instanceof StructModel) {
183                $rules = new Rules($this, $method, new StructAttributeModel($model->getGenerator(), $soapHeaderType, $model->getName(), $model), $this->methods);
184                $rules->applyRules(lcfirst($soapHeaderName));
185                $firstParameter->setModel($model);
186            }
187            $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));
188        } catch (\InvalidArgumentException $exception) {
189            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);
190        }
191
192        return $method;
193    }
194
195    protected function getTypeFromName(string $name): ?string
196    {
197        return self::getPhpType(
198            $this->getStructAttributeTypeAsPhpType(new StructAttributeModel($this->generator, 'any', $name)),
199            $this->getGenerator()->getOptionXsdTypesPath(),
200            $this->getStructAttributeTypeAsPhpType(new StructAttributeModel($this->generator, 'any', $name))
201        );
202    }
203
204    protected function getSoapHeaderMethodName(string $soapHeaderName): string
205    {
206        return sprintf('%s%s', self::METHOD_SET_HEADER_PREFIX, ucfirst($soapHeaderName));
207    }
208
209    protected function addOperationsMethods(): self
210    {
211        foreach ($this->getModel()->getMethods() as $method) {
212            $this->addMainMethod($method);
213        }
214
215        return $this;
216    }
217
218    protected function addGetResultMethod(): self
219    {
220        $method = new PhpMethod(self::METHOD_GET_RESULT);
221        $method->addChild('return parent::getResult();');
222        $this->methods->add($method);
223
224        return $this;
225    }
226
227    protected function addMainMethod(MethodModel $method): self
228    {
229        $methodFile = new Operation($method, $this->getGenerator());
230        $mainMethod = $methodFile->getMainMethod();
231        $this->methods->add($mainMethod);
232        $this->setModelFromMethod($mainMethod, $method);
233
234        return $this;
235    }
236
237    protected function getMethodAnnotationBlock(PhpMethod $method): PhpAnnotationBlock
238    {
239        $annotationBlock = new PhpAnnotationBlock();
240        if (0 === mb_stripos($method->getName(), self::METHOD_SET_HEADER_PREFIX)) {
241            $this->addAnnotationBlockForSoapHeaderMethod($annotationBlock, $method);
242        } elseif (self::METHOD_GET_RESULT === $method->getName()) {
243            $this->addAnnotationBlockForgetResultMethod($annotationBlock);
244        } else {
245            $this->addAnnotationBlockForOperationMethod($annotationBlock, $method);
246        }
247
248        return $annotationBlock;
249    }
250
251    protected function addAnnotationBlockForSoapHeaderMethod(PhpAnnotationBlock $annotationBlock, PhpMethod $method): self
252    {
253        $methodParameters = $method->getParameters();
254        $firstParameter = array_shift($methodParameters);
255        if ($firstParameter instanceof PhpFunctionParameter) {
256            $annotationBlock->addChild(sprintf('Sets the %s SoapHeader param', ucfirst($firstParameter->getName())));
257            $firstParameterType = $firstParameter->getType();
258            if ($firstParameter->getModel() instanceof StructModel) {
259                $firstParameterType = $this->getStructAttributeTypeAsPhpType(new StructAttributeModel($firstParameter->getModel()->getGenerator(), $firstParameter->getName(), $firstParameter->getModel()->getName(), $firstParameter->getModel()));
260                if ($firstParameter->getModel()->isRestriction()) {
261                    $annotationBlock
262                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $firstParameter->getModel()->getPackagedName(true), StructEnum::METHOD_VALUE_IS_VALID)))
263                        ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $firstParameter->getModel()->getPackagedName(true), StructEnum::METHOD_GET_VALID_VALUES)))
264                        ->addChild(new PhpAnnotation(self::ANNOTATION_THROWS, \InvalidArgumentException::class))
265                    ;
266                }
267            }
268            $annotationBlock
269                ->addChild(new PhpAnnotation(self::ANNOTATION_USES, sprintf('%s::%s()', $this->getModel()->getExtends(true), self::METHOD_SET_HEADER_PREFIX)))
270                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', $firstParameterType, $firstParameter->getName())))
271                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', self::TYPE_STRING, self::PARAM_SET_HEADER_NAMESPACE)))
272                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s $%s', self::TYPE_BOOL, self::PARAM_SET_HEADER_MUSTUNDERSTAND)))
273                ->addChild(new PhpAnnotation(self::ANNOTATION_PARAM, sprintf('%s|null $%s', self::TYPE_STRING, self::PARAM_SET_HEADER_ACTOR)))
274                ->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $this->getModel()->getPackagedName(true)))
275            ;
276        }
277
278        return $this;
279    }
280
281    protected function addAnnotationBlockForOperationMethod(PhpAnnotationBlock $annotationBlock, PhpMethod $method): self
282    {
283        if (($model = $this->getModelFromMethod($method)) instanceof MethodModel) {
284            $operationAnnotationBlock = new OperationAnnotationBlock($model, $this->getGenerator());
285            $operationAnnotationBlock->addAnnotationBlockForOperationMethod($annotationBlock);
286        }
287
288        return $this;
289    }
290
291    protected function addAnnotationBlockForgetResultMethod(PhpAnnotationBlock $annotationBlock): self
292    {
293        $annotationBlock
294            ->addChild('Returns the result')->addChild(new PhpAnnotation(self::ANNOTATION_SEE, sprintf('%s::getResult()', $this->getModel()->getExtends(true))))
295            ->addChild(new PhpAnnotation(self::ANNOTATION_RETURN, $this->getServiceReturnTypes()))
296        ;
297
298        return $this;
299    }
300
301    protected function getServiceReturnTypes(): string
302    {
303        $returnTypes = [];
304        foreach ($this->getModel()->getMethods() as $method) {
305            $returnTypes[] = self::getOperationMethodReturnType($method, $this->getGenerator());
306        }
307        natcasesort($returnTypes);
308
309        return implode('|', array_unique($returnTypes));
310    }
311
312    protected function getModelFromMethod(PhpMethod $method): ?MethodModel
313    {
314        $model = $this->getGenerator()->getServiceMethod($method->getName());
315        if (!$model instanceof MethodModel) {
316            $model = array_key_exists($method->getName(), $this->methodNames) ? $this->methodNames[$method->getName()] : null;
317        }
318
319        return $model;
320    }
321
322    protected function setModelFromMethod(PhpMethod $phpMethod, MethodModel $methodModel): self
323    {
324        $this->methodNames[$phpMethod->getName()] = $methodModel;
325
326        return $this;
327    }
328}