Passed
Push — master ( d8fa97...555fd2 )
by Yo
01:32
created

TypeGuesser::guessPrimaryTypeFromConstraint()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

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