Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
59 / 59
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
UnionRule
100.00% covered (success)
100.00%
59 / 59
100.00% covered (success)
100.00%
5 / 5
18
100.00% covered (success)
100.00%
1 / 1
 name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 testConditions
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 exceptionMessageOnTestFailure
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getErrorMessageVariableName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addValidationMethod
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
1 / 1
12
1<?php
2
3declare(strict_types=1);
4
5namespace WsdlToPhp\PackageGenerator\File\Validation;
6
7use WsdlToPhp\PackageGenerator\File\AbstractModelFile;
8use WsdlToPhp\PackageGenerator\Model\StructAttribute;
9use WsdlToPhp\PhpGenerator\Element\PhpFunctionParameter;
10use WsdlToPhp\PhpGenerator\Element\PhpMethod;
11
12/**
13 * @see https://www.w3.org/TR/xmlschema-2/#union-datatypes
14 */
15final class UnionRule extends AbstractRule
16{
17    public const NAME = 'union';
18
19    public function name(): string
20    {
21        return self::NAME;
22    }
23
24    public function testConditions(string $parameterName, $value, bool $itemType = false): string
25    {
26        $test = '';
27        if (is_array($value) && 0 < count($value)) {
28            $this->addValidationMethod($parameterName, $value);
29            $test = sprintf('\'\' !== (%s = self::%s($%s))', self::getErrorMessageVariableName($parameterName), $this->getValidationMethodName($parameterName), $parameterName);
30        }
31
32        return $test;
33    }
34
35    public function exceptionMessageOnTestFailure(string $parameterName, $value, bool $itemType = false): string
36    {
37        return self::getErrorMessageVariableName($parameterName);
38    }
39
40    public static function getErrorMessageVariableName(string $parameterName): string
41    {
42        return sprintf('$%sUnionErrorMessage', $parameterName);
43    }
44
45    protected function addValidationMethod(string $parameterName, array $unionValues): void
46    {
47        $method = new PhpMethod('temp');
48        $rules = clone $this->getRules();
49        $rules->setMethod($method);
50
51        // gather validation rules
52        foreach ($unionValues as $unionValue) {
53            $attribute = new StructAttribute($this->getGenerator(), 'any', $unionValue);
54            $attribute->setOwner($this->getAttribute()->getOwner());
55            $rules
56                ->setAttribute($attribute)
57                ->applyRules('value')
58            ;
59            unset($attribute);
60        }
61
62        // Adapt content, remove duplicated rules
63        // The duplicated rules are based on the fact that validation rules are composed by 4 lines so we check existing rule every 4-line block of text
64        $exceptions = 0;
65        $exceptionsTests = [];
66        $exceptionsArray = [];
67        $children = [];
68        $methodChildren = $method->getChildren();
69        $childrenCount = count($methodChildren);
70        $existingValidationRules = [];
71        for ($i = 0; $i < $childrenCount; $i += 4) {
72            $validationRules = array_slice($methodChildren, ((int) ($i / 4)) * 4, 4);
73            if (!in_array($validationRules, $existingValidationRules)) {
74                foreach ($validationRules as $validationRuleIndex => $validationRule) {
75                    // avoid having a validation rule that has already been applied to the attribute within the method which is calling the validate method
76                    if (0 === $validationRuleIndex) {
77                        $ruleParts = [];
78                        preg_match(sprintf('/%s\s(\w*)(.*)?/', self::VALIDATION_RULE_COMMENT_SENTENCE), $validationRule, $ruleParts);
79                        if (3 === count($ruleParts) && !empty($ruleParts[1]) && $rules->getRule($ruleParts[1]) instanceof AbstractRule && Rules::hasRuleBeenAppliedToAttribute($rules->getRule($ruleParts[1]), $ruleParts[2], $this->getAttribute())) {
80                            continue 2;
81                        }
82                    }
83
84                    if (is_string($validationRule) && false !== mb_strpos($validationRule, 'throw new')) {
85                        $exceptionName = sprintf('$exception%d', $exceptions++);
86                        $validationRule = str_replace('throw new', sprintf('%s = new', $exceptionName), $validationRule);
87                        $exceptionsTests[] = sprintf('isset(%s)', $exceptionName);
88                        $exceptionsArray[] = $exceptionName;
89                    }
90
91                    $children[] = $validationRule;
92                }
93            }
94            $existingValidationRules[] = $validationRules;
95        }
96
97        // populate final validation method
98        $method = new PhpMethod($this->getValidationMethodName($parameterName), [
99            new PhpFunctionParameter('value', PhpFunctionParameter::NO_VALUE),
100        ], AbstractModelFile::TYPE_STRING, PhpMethod::ACCESS_PUBLIC, false, true);
101        $method->addChild('$message = \'\';');
102        array_walk($children, [
103            $method,
104            'addChild',
105        ]);
106
107        $method
108            ->addChild(sprintf('if (%s) {', implode(' && ', $exceptionsTests)))
109            ->addChild($method->getIndentedString(sprintf('$message = sprintf("The value %%s does not match any of the union rules: %s. See following errors:\n%%s", var_export($value, true), implode("\n", array_map(function(InvalidArgumentException $e) { return sprintf(\' - %%s\', $e->getMessage()); }, [%s])));', implode(', ', $unionValues), implode(', ', $exceptionsArray)), 1))
110            ->addChild('}')
111            ->addChild(sprintf('unset(%s);', implode(', ', $exceptionsArray)))
112            ->addChild('')
113            ->addChild('return $message;')
114        ;
115        $this->getMethods()->add($method);
116    }
117}