Failed Conditions
Pull Request — release/1.0.0 (#7)
by Yo
02:47
created

TypeGuesser::guessPrimaryTypeFromConstraint()   B

Complexity

Conditions 9
Paths 7

Size

Total Lines 47
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9

Importance

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