Passed
Pull Request — feature/fix (#19)
by Yo
01:25
created

appendAllConstraintToDoc()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
namespace Yoanm\JsonRpcParamsSymfonyConstraintDoc\Infra\Transformer;
3
4
use Symfony\Component\Validator\Constraint;
5
use Symfony\Component\Validator\Constraints as Assert;
6
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\ConstraintPayloadDocHelper;
7
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\DocTypeHelper;
8
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\MinMaxHelper;
9
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\StringDocHelper;
10
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ArrayDoc;
11
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\CollectionDoc;
12
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\TypeDoc;
13
14
/**
15
 * Class ConstraintToParamsDocTransformer
16
 */
17
class ConstraintToParamsDocTransformer
18
{
19
    /** @var DocTypeHelper */
20
    private $docTypeHelper;
21
    /** @var StringDocHelper */
22
    private $stringDocHelper;
23
    /** @var MinMaxHelper */
24
    private $minMaxHelper;
25
    /** @var ConstraintPayloadDocHelper */
26
    private $constraintPayloadDocHelper;
27
28
    const CONSTRAINT_WITH_ALLOWED_VALUE_LIST = [
29
        Assert\IsTrue::class => [true, 1, '1'],
30
        Assert\IsFalse::class => [false, 0, '0'],
31
        Assert\IsNull::class => [null],
32
    ];
33
34
    const CONSTRAINT_WITH_ALLOWED_VALUE_LIST_FROM_PROPERTY = [
35
        Assert\IdenticalTo::class => 'value',
36
        Assert\EqualTo::class => 'value',
37
    ];
38
39
    /**
40
     * @param DocTypeHelper              $docTypeHelper
41
     * @param StringDocHelper            $stringDocHelper
42
     * @param MinMaxHelper               $minMaxHelper
43
     * @param ConstraintPayloadDocHelper $constraintPayloadDocHelper
44
     */
45 18
    public function __construct(
46
        DocTypeHelper $docTypeHelper,
47
        StringDocHelper $stringDocHelper,
48
        MinMaxHelper $minMaxHelper,
49
        ConstraintPayloadDocHelper $constraintPayloadDocHelper
50
    ) {
51 18
        $this->docTypeHelper = $docTypeHelper;
52 18
        $this->minMaxHelper = $minMaxHelper;
53 18
        $this->stringDocHelper = $stringDocHelper;
54 18
        $this->constraintPayloadDocHelper = $constraintPayloadDocHelper;
55 18
    }
56
57
    /**
58
     * @param Constraint $constraint
59
     *
60
     * @return TypeDoc
61
     *
62
     * @throws \ReflectionException
63
     */
64 18
    public function transform(Constraint $constraint) : TypeDoc
65
    {
66 18
        return $this->transformList([$constraint]);
67
    }
68
69
    /**
70
     * @param Constraint[] $constraintList
71
     *
72
     * @return TypeDoc
73
     *
74
     * @throws \ReflectionException
75
     */
76 18
    public function transformList(array $constraintList) : TypeDoc
77
    {
78 18
        $constraintDoc = $this->docTypeHelper->guess($constraintList);
79
80 18
        foreach ($constraintList as $constraint) {
81 18
            $this->appendToDoc($constraintDoc, $constraint);
82
        }
83
84 18
        return $constraintDoc;
85
    }
86
87
    /**
88
     * @param TypeDoc    $doc
89
     * @param Constraint $constraint
90
     *
91
     * @throws \ReflectionException
92
     */
93 18
    private function appendToDoc(TypeDoc $doc, Constraint $constraint) : void
94
    {
95 18
        if ($constraint instanceof Assert\Callback) {
96 1
            $callbackResult = call_user_func($constraint->callback);
97 1
            $callbackResultList = is_array($callbackResult) ? $callbackResult : [$callbackResult];
98 1
            foreach ($callbackResultList as $subConstraint) {
99 1
                $this->appendToDoc($doc, $subConstraint);
100
            }
101 18
        } elseif ($doc instanceof ArrayDoc && $constraint instanceof Assert\All) {
102 1
            $this->appendAllConstraintToDoc($doc, $constraint);
103
        } else {
104 18
            $this->basicAppendToDoc($doc, $constraint);
105
        }
106
        // /!\ Payload doc will override values even if already defined
107 18
        $this->constraintPayloadDocHelper->appendPayloadDoc($doc, $constraint);
108 18
    }
109
110
    /**
111
     * @param TypeDoc    $doc
112
     * @param Constraint $constraint
113
     *
114
     * @throws \ReflectionException
115
     */
116 18
    private function appendCollectionDoc(TypeDoc $doc, Constraint $constraint) : void
117
    {
118
        // If not a collection => give up
119 18
        if (!$doc instanceof CollectionDoc) {
120 18
            return;
121
        }
122
123 3
        if ($constraint instanceof Assert\Collection) {
124 3
            foreach ($constraint->fields as $fieldName => $subConstraint) {
125 3
                $sibling = $this->transform($subConstraint);
126 3
                $doc->addSibling(
127 3
                    $sibling->setName($fieldName)
128
                );
129
            }
130
131 3
            $doc->setAllowExtraSibling($constraint->allowExtraFields === true);
132 3
            $doc->setAllowMissingSibling($constraint->allowMissingFields === true);
133
        }
134 3
    }
135
136
    /**
137
     * @param TypeDoc $doc
138
     * @param Constraint $constraint
139
     */
140 18
    private function appendValidItemListDoc(TypeDoc $doc, Constraint $constraint) : void
141
    {
142 18
        $constraintClass = get_class($constraint);
143 18
        if ($constraint instanceof Assert\Choice) {
144 2
            $this->appendChoiceAllowedValue($doc, $constraint);
145 16
        } elseif (array_key_exists($constraintClass, self::CONSTRAINT_WITH_ALLOWED_VALUE_LIST_FROM_PROPERTY)) {
146 3
            $propertyName = self::CONSTRAINT_WITH_ALLOWED_VALUE_LIST_FROM_PROPERTY[$constraintClass];
147 3
            $this->addToAllowedValueListIfNotExist($doc, $constraint->{$propertyName});
148 13
        } elseif (array_key_exists($constraintClass, self::CONSTRAINT_WITH_ALLOWED_VALUE_LIST)) {
149 5
            $this->addListToAllowedValueListIfNotExist(
150 5
                $doc,
151 5
                self::CONSTRAINT_WITH_ALLOWED_VALUE_LIST[$constraintClass]
152
            );
153
        }
154 18
    }
155
156
    /**
157
     * @param ArrayDoc   $doc
158
     * @param Assert\All $constraint
159
     *
160
     * @throws \ReflectionException
161
     */
162 1
    private function appendAllConstraintToDoc(ArrayDoc $doc, Assert\All $constraint) : void
163
    {
164 1
        $itemDoc = $this->docTypeHelper->guess($constraint->constraints);
165 1
        foreach ($constraint->constraints as $subConstraint) {
166 1
            $this->appendToDoc($itemDoc, $subConstraint);
167
        }
168
169 1
        $doc->setItemValidation($itemDoc);
170 1
    }
171
172
    /**
173
     * @param       $object
174
     * @param array $classList
175
     *
176
     * @return bool
177
     */
178 18
    private function isInstanceOfOneClassIn($object, array $classList) : bool
179
    {
180 18
        $actualClassList = array_merge(
181 18
            [get_class($object)],
182 18
            class_implements($object),
183 18
            class_uses($object)
184
        );
185 18
        $parentClass = get_parent_class($object);
186 18
        while (false !== $parentClass) {
187 18
            $actualClassList[] = $parentClass;
188 18
            $parentClass = get_parent_class($parentClass);
189
        }
190
191 18
        return count(array_intersect($actualClassList, $classList)) > 0;
192
    }
193
194
    /**
195
     * @param TypeDoc $doc
196
     * @param mixed[] $valueList
197
     */
198 7
    private function addListToAllowedValueListIfNotExist(TypeDoc $doc, array $valueList) : void
199
    {
200 7
        foreach ($valueList as $value) {
201 7
            $this->addToAllowedValueListIfNotExist($doc, $value);
202
        }
203 7
    }
204
205
    /**
206
     * @param TypeDoc $doc
207
     * @param mixed   $value
208
     */
209 10
    private function addToAllowedValueListIfNotExist(TypeDoc $doc, $value) : void
210
    {
211 10
        if (!in_array($value, $doc->getAllowedValueList(), true)) {
212 10
            $doc->addAllowedValue($value);
213
        }
214 10
    }
215
216
    /**
217
     * @param TypeDoc    $doc
218
     * @param Constraint $constraint
219
     *
220
     * @throws \ReflectionException
221
     */
222 18
    private function basicAppendToDoc(TypeDoc $doc, Constraint $constraint): void
223
    {
224 18
        static $notNullConstraintList = [
225
            Assert\NotNull::class,
226
            Assert\IsTrue::class, // If it is true, it cannot be null ...
227
            Assert\IsFalse::class, // If it is false, it cannot be null ...
228
            // If should be identical to something, it cannot be null (but can be identical to null)
229
            Assert\IdenticalTo::class,
230
        ];
231
232 18
        $this->stringDocHelper->append($doc, $constraint);
233 18
        $this->appendCollectionDoc($doc, $constraint);
234
235 18
        $this->minMaxHelper->append($doc, $constraint);
236 18
        $this->appendValidItemListDoc($doc, $constraint);
237
238 18
        if ($constraint instanceof Assert\Existence) {
239 3
            $doc->setRequired($constraint instanceof Assert\Required);
240 3
            foreach ($constraint->constraints as $subConstraint) {
241 3
                $this->appendToDoc($doc, $subConstraint);
242
            }
243 18
        } elseif ($this->isInstanceOfOneClassIn($constraint, $notNullConstraintList)) {
244 10
            $doc->setNullable(
245 10
                ($constraint instanceof Assert\IdenticalTo)
246 2
                    ? is_null($constraint->value)
247 10
                    : false
248
            );
249 10
            $defaultValue = $exampleValue = null;
250
            switch (true) {
251 10
                case $constraint instanceof Assert\IsTrue:
252 2
                    $defaultValue = $exampleValue = true;
253 2
                    break;
254 8
                case $constraint instanceof Assert\IsFalse:
255 2
                    $defaultValue = $exampleValue = false;
256 2
                    break;
257 6
                case $constraint instanceof Assert\IdenticalTo:
258 2
                    $defaultValue = $exampleValue = $constraint->value;
259 2
                    break;
260
            }
261 10
            $doc->setDefault($doc->getDefault() ?? $defaultValue);
262 10
            $doc->setExample($doc->getExample() ?? $exampleValue);
263
        }
264 18
    }
265
266
    /**
267
     * @param TypeDoc       $doc
268
     * @param Assert\Choice $constraint
269
     */
270 2
    private function appendChoiceAllowedValue(TypeDoc $doc, Assert\Choice $constraint): void
271
    {
272 2
        if ($constraint->callback && is_callable($constraint->callback)) {
273 1
            $choiceList = call_user_func($constraint->callback);
274
        } else {
275 1
            $choiceList = $constraint->choices ?? [];
276
        }
277 2
        $this->addListToAllowedValueListIfNotExist($doc, $choiceList);
278 2
    }
279
}
280