Passed
Pull Request — release/1.0.0 (#7)
by Yo
01:22
created

TypeGuesser::isInstanceOfOneClassIn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

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