Passed
Pull Request — feature/fix (#19)
by Yo
01:35
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
    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 !== $this->getMatchingClassNameIn($constraint, self::STRING_CONSTRAINT_CLASS_LIST)) {
120 18
            return new StringDoc();
121 11
        } elseif (null !== $this->getMatchingClassNameIn($constraint, self::BOOLEAN_CONSTRAINT_CLASS_LIST)) {
122 2
            return new BooleanDoc();
123 9
        } elseif ($constraint instanceof Assert\DateTime) {
124 2
            return $this->guessDateTimeType($constraint);
125 7
        } elseif ($constraint instanceof Assert\Collection) {
126 2
            return $this->guestCollectionType($constraint);
127 5
        } elseif ($constraint instanceof Assert\Regex) {
128 1
            return new ScalarDoc();
129 4
        } elseif ($this->isArrayConstraint($constraint)) {
130 2
            return new ArrayDoc();
131
        }
132
133 2
        return null;
134
    }
135
136
    /**
137
     * @param Assert\DateTime $constraint
138
     *
139
     * @return ScalarDoc|StringDoc
140
     */
141 2
    private function guessDateTimeType(Assert\DateTime $constraint) : TypeDoc
142
    {
143 2
        if ('U' === $constraint->format) {
144 1
            return new ScalarDoc();// Don't know if value will be an number as string or as integer
145
        }
146
147 1
        return new StringDoc();
148
    }
149
150
    /**
151
     * @param Assert\Collection $constraint
152
     *
153
     * @return ArrayDoc|ObjectDoc
154
     */
155 2
    private function guestCollectionType(Assert\Collection $constraint) : TypeDoc
156
    {
157
        // If only integer => array, else object
158 2
        $integerKeyList = array_filter(array_keys($constraint->fields), 'is_int');
159 2
        if (count($constraint->fields) === count($integerKeyList)) {
160 1
            return new ArrayDoc();
161
        }
162
163 1
        return new ObjectDoc();
164
    }
165
166
    /**
167
     * @param Constraint $constraint
168
     *
169
     * @return bool
170
     */
171 4
    private function isArrayConstraint(Constraint $constraint): bool
172
    {
173 4
        return $constraint instanceof Assert\All // << Applied only on array
174 3
            || ($constraint instanceof Assert\Choice
175 4
                && true === $constraint->multiple // << expect an array multiple choices
176
            );
177
    }
178
}
179