Passed
Push — master ( cd7aa7...ac033d )
by Yo
01:41
created

basicAppendToDoc()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 41
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 8

Importance

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