Completed
Pull Request — release/1.0.0 (#4)
by Yo
01:43
created

TypeGuesser::isAbstractType()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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