TypeGuesser   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 173
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 72
c 1
b 0
f 0
dl 0
loc 173
ccs 57
cts 57
cp 1
rs 10
wmc 28

8 Methods

Rating   Name   Duplication   Size   Complexity  
A isArrayConstraint() 0 5 3
A guessSimplePrimaryTypeFromConstraint() 0 13 5
A guessTypeFromConstraint() 0 13 3
A isAbstractType() 0 8 3
A guessDateTimeType() 0 7 2
A guestCollectionType() 0 9 2
A guessTypeFromConstraintList() 0 21 6
A guessPrimaryTypeFromConstraint() 0 12 4
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 106
    public function guessTypeFromConstraintList(array $constraintList) : ?TypeDoc
53
    {
54 106
        $doc = $abstractTypeFound = null;
55 106
        foreach ($constraintList as $constraint) {
56 106
            $doc = $this->guessTypeFromConstraint($constraint);
57 106
            if (null !== $doc) {
58 82
                if ($this->isAbstractType($doc)) {
59
                     // Abstract type => continue to see if better type can be found
60 6
                     $abstractTypeFound = $doc;
61 6
                     $doc = null;
62
                } else {
63 76
                     break;
64
                }
65
            }
66
        }
67
        // Try to fallback on abstractType if found
68 106
        if (null === $doc && null !== $abstractTypeFound) {
69 6
            $doc = $abstractTypeFound;
70
        }
71
72 106
        return $doc;
73
    }
74
75
    /**
76
     * @param Constraint $constraint
77
     *
78
     * @return TypeDoc|null
79
     */
80 106
    protected function guessTypeFromConstraint(Constraint $constraint) : ?TypeDoc
81
    {
82 106
        if (null !== ($type = $this->guessPrimaryTypeFromConstraint($constraint))) {
83 79
            return $type;
84
        }
85
86
        // If primary type is still not defined
87 27
        $constraintClass = get_class($constraint);
88 27
        if (Assert\Count::class == $constraintClass) {
89 3
            return new CollectionDoc();
90
        }
91
92 24
        return null;
93
    }
94
95
    /**
96
     * @param TypeDoc $doc
97
     *
98
     * @return bool
99
     */
100 82
    private function isAbstractType(TypeDoc $doc) : bool
101
    {
102
        // use get_class to avoid inheritance issue
103 82
        $class = get_class($doc);
104
105 82
        return CollectionDoc::class === $class
106 82
            || NumberDoc::class === $class
107 82
            || ScalarDoc::class === $class
108 82
        ;
109
    }
110
111
    /**
112
     * @param Constraint $constraint
113
     *
114
     * @return null|ArrayDoc|BooleanDoc|ObjectDoc|ScalarDoc|StringDoc
115
     */
116 106
    private function guessPrimaryTypeFromConstraint(Constraint $constraint) : ?TypeDoc
117
    {
118
        // Try to guess primary types
119 106
        if (null !== ($type = $this->guessSimplePrimaryTypeFromConstraint($constraint))) {
120 69
            return $type;
121 37
        } elseif ($constraint instanceof Assert\DateTime) {
122 4
            return $this->guessDateTimeType($constraint);
123 33
        } elseif ($constraint instanceof Assert\Collection) {
124 6
            return $this->guestCollectionType($constraint);
125
        }
126
127 27
        return null;
128
    }
129
130
    /**
131
     * @param Assert\DateTime $constraint
132
     *
133
     * @return ScalarDoc|StringDoc
134
     */
135 4
    private function guessDateTimeType(Assert\DateTime $constraint) : TypeDoc
136
    {
137 4
        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 3
        return new StringDoc();
142
    }
143
144
    /**
145
     * @param Assert\Collection $constraint
146
     *
147
     * @return ArrayDoc|ObjectDoc
148
     */
149 6
    private function guestCollectionType(Assert\Collection $constraint) : TypeDoc
150
    {
151
        // If only integer => array, else object
152 6
        $integerKeyList = array_filter(array_keys($constraint->fields), 'is_int');
153 6
        if (count($constraint->fields) === count($integerKeyList)) {
154 1
            return new ArrayDoc();
155
        }
156
157 5
        return new ObjectDoc();
158
    }
159
160
    /**
161
     * @param Constraint $constraint
162
     *
163
     * @return bool
164
     */
165 44
    private function isArrayConstraint(Constraint $constraint): bool
166
    {
167 44
        return $constraint instanceof Assert\All // << Applied only on array
168 44
            || ($constraint instanceof Assert\Choice
169 44
                && true === $constraint->multiple // << expect an array multiple choices
170 44
            );
171
    }
172
173
    /**
174
     * @param Constraint $constraint
175
     *
176
     * @return TypeDoc|null
177
     */
178 106
    private function guessSimplePrimaryTypeFromConstraint(Constraint $constraint) : ?TypeDoc
179
    {
180 106
        if (null !== $this->getMatchingClassNameIn($constraint, self::STRING_CONSTRAINT_CLASS_LIST)) {
181 54
            return new StringDoc();
182 52
        } elseif (null !== $this->getMatchingClassNameIn($constraint, self::BOOLEAN_CONSTRAINT_CLASS_LIST)) {
183 6
            return new BooleanDoc();
184 46
        } elseif ($constraint instanceof Assert\Regex) {
185 2
            return new ScalarDoc();
186 44
        } elseif ($this->isArrayConstraint($constraint)) {
187 7
            return new ArrayDoc();
188
        }
189
190 37
        return null;
191
    }
192
}
193