Failed Conditions
Push — release/1.0.0 ( 254c29...0a806c )
by Yo
01:19
created

DocTypeHelper::guess()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper;
3
4
use Symfony\Component\Validator\Constraint;
5
use Symfony\Component\Validator\Constraints as Assert;
6
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ArrayDoc;
7
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\BooleanDoc;
8
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\CollectionDoc;
9
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\FloatDoc;
10
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\IntegerDoc;
11
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\NumberDoc;
12
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ObjectDoc;
13
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ScalarDoc;
14
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\StringDoc;
15
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\TypeDoc;
16
17
/**
18
 * Class DocTypeHelper
19
 */
20
class DocTypeHelper
21
{
22
    /** @var ConstraintPayloadDocHelper */
23
    private $constraintPayloadDocHelper;
24
25
    /**
26
     * @param ConstraintPayloadDocHelper $constraintPayloadDocHelper
27
     */
28 69
    public function __construct(ConstraintPayloadDocHelper $constraintPayloadDocHelper)
29
    {
30 69
        $this->constraintPayloadDocHelper = $constraintPayloadDocHelper;
31 69
    }
32
33
    /**
34
     * @param Constraint[] $constraintList
35
     *
36
     * @return TypeDoc
37
     */
38 69
    public function guess(array $constraintList) : TypeDoc
39
    {
40 69
        return $this->getDocFromTypeConstraintOrPayloadDocIfExist($constraintList)
41 52
        ?? $this->guessTypeFromConstraintList($constraintList)
42 69
        ?? new TypeDoc();
43
    }
44
45
    /**
46
     * @param Constraint[] $constraintList
47
     *
48
     * @return TypeDoc|null
49
     */
50 69
    private function getDocFromTypeConstraintOrPayloadDocIfExist(array $constraintList)
51
    {
52 69
        $doc = null;
53
        // Check if a Type constraint exist or if a constraint have a type documentation
54 69
        foreach ($constraintList as $constraint) {
55 68
            if (null !== ($typeFromPayload = $this->constraintPayloadDocHelper->getTypeIfExist($constraint))) {
56 1
                $doc = $this->getDocFromType($typeFromPayload);
57 67
            } elseif ($constraint instanceof Assert\Type) {
58 19
                $doc = $this->getDocFromType(strtolower($constraint->type));
59
            }
60
61 68
            if (null !== $doc) {
62 68
                break;
63
            }
64
        }
65
66 69
        return $doc;
67
    }
68
69
    /**
70
     * @param array $constraintList
71
     *
72
     * @return TypeDoc|null
73
     */
74 52
    private function guessTypeFromConstraintList(array $constraintList)
75
    {
76 52
        $doc = $abstractTypeFound = null;
77 52
        foreach ($constraintList as $constraint) {
78 51
            $doc = $this->guessTypeFromConstraint($constraint);
79 51
            if (null !== $doc) {
80 50
                if ($this->isAbstractType($doc)) {
81
                     // Abstract type => continue to see if better type can be found
82 14
                     $abstractTypeFound = $doc;
83 14
                     $doc = null;
84
                } else {
85 51
                     break;
86
                }
87
            }
88
        }
89
        // Try to fallback on abstractType if found
90 52
        if (null === $doc && null !== $abstractTypeFound) {
91 10
            $doc = $abstractTypeFound;
92
        }
93
94 52
        return $doc;
95
    }
96
97
    /**
98
     * @param Constraint $constraint
99
     *
100
     * @return TypeDoc|null
101
     */
102 51
    private function guessTypeFromConstraint(Constraint $constraint)
103
    {
104 51
        static $stringConstraintClassList = [
105
            Assert\Length::class, // << Applied on string only
106
            Assert\Date::class,  // << validator expect a string with specific format
107
            Assert\Time::class,  // << validator expect a string with specific format
108
            Assert\Bic::class,
109
            Assert\CardScheme::class,
110
            Assert\Country::class,
111
            Assert\Currency::class,
112
            Assert\Email::class,
113
            Assert\File::class,
114
            Assert\Iban::class,
115
            Assert\Ip::class,
116
            Assert\Isbn::class,
117
            Assert\Issn::class,
118
            Assert\Language::class,
119
            Assert\Locale::class,
120
            Assert\Luhn::class,
121
            Assert\Regex::class,
122
            Assert\Url::class,
123
            Assert\Uuid::class,
124
        ];
125 51
        static $booleanConstraintClassList = [
126
            Assert\IsTrue::class,
127
            Assert\IsFalse::class,
128
        ];
129 51
        $constraintClass = get_class($constraint);
130
131
        // Try to guess primary types
132 51
        if (in_array($constraintClass, $stringConstraintClassList)) {
133 20
            return new StringDoc();
134 32
        } elseif (in_array($constraintClass, $booleanConstraintClassList)) {
135 3
            return new BooleanDoc();
136 30
        } elseif ($constraint instanceof Assert\DateTime) {
137 6
            if ('U' === $constraint->format) {
138 5
                return new ScalarDoc();// Don't know if value will be an number as string or as integer
139
            }
140
141 1
            return new StringDoc();
142 25
        } elseif ($constraint instanceof Assert\Collection) {
143
            // If only integer => array, else object
144 2
            $integerKeyList = array_filter(array_keys($constraint->fields), 'is_int');
145 2
            if (count($constraint->fields) === count($integerKeyList)) {
146 1
                return new ArrayDoc();
147
            }
148
149 1
            return new ObjectDoc();
150 23
        } elseif (Assert\All::class === $constraintClass // << Applied only on array
151 22
            || ($constraint instanceof Assert\Choice
152 23
                && true === $constraint->multiple // << expect an array multiple choices
153
            )
154
        ) {
155 2
            return new ArrayDoc();
156
        }
157
158
        // If primary type is still not defined
159 21
        static $numberOrFloatConstraintClassList = [
160
            Assert\GreaterThan::class,
161
            Assert\GreaterThanOrEqual::class,
162
            Assert\LessThan::class,
163
            Assert\LessThanOrEqual::class,
164
        ];
165 21
        if ($constraint instanceof Assert\Range) {
166 8
            return $this->floatOrNumber([$constraint->min, $constraint->max]);
167 15
        } elseif (in_array($constraintClass, $numberOrFloatConstraintClassList)) {
168 10
            return $this->floatOrNumber([$constraint->value]);
169 5
        } elseif (Assert\Count::class == $constraintClass) {
170 2
            return new CollectionDoc();
171 3
        } elseif ($constraint instanceof Assert\Existence) {
172 2
            return $this->guess($constraint->constraints);
173
        }
174
175 1
        return null;
176
    }
177
178
    /**
179
     * @param string $type
180
     *
181
     * @return TypeDoc|null
182
     */
183 20
    private function getDocFromType(string $type)
184
    {
185 20
        if ('scalar' === $type) {
186 1
            return new ScalarDoc();
187 19
        } elseif ('string' === $type) {
188 1
            return new StringDoc();
189 18
        } elseif ('bool' === $type || 'boolean' === $type) {
190 2
            return new BooleanDoc();
191 16
        } elseif ('int' === $type || 'integer' === $type) {
192 3
            return new IntegerDoc();
193 13
        } elseif (in_array($type, ['float', 'long', 'double', 'real', 'numeric'])) {
194 7
            return new FloatDoc();
195 6
        } elseif ('array' === $type) {
196 3
            return new ArrayDoc();
197 3
        } elseif ('object' === $type) {
198 2
            return new ObjectDoc();
199
        }
200
201 1
        return null;
202
    }
203
204
    /**
205
     * @param TypeDoc $doc
206
     *
207
     * @return bool
208
     */
209 50
    private function isAbstractType(TypeDoc $doc) : bool
210
    {
211
        // use get_class to avoid inheritance issue
212 50
        $class = get_class($doc);
213
214 50
        return CollectionDoc::class === $class
215 48
            || NumberDoc::class === $class
216 50
            || ScalarDoc::class === $class
217
        ;
218
    }
219
220
    /**
221
     * @param array $basedOnList
222
     *
223
     * @return FloatDoc|NumberDoc
224
     */
225 16
    private function floatOrNumber(array $basedOnList)
226
    {
227 16
        foreach ($basedOnList as $value) {
228 16
            if (null !== $value && is_float($value)) {
229 16
                return new FloatDoc();
230
            }
231
        }
232
233 8
        return new NumberDoc();
234
    }
235
}
236