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

DocTypeHelper   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 229
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 229
ccs 91
cts 91
cp 1
rs 8.3999
c 0
b 0
f 0
wmc 46

9 Methods

Rating   Name   Duplication   Size   Complexity  
B guessTypeFromConstraintList() 0 21 6
A __construct() 0 3 1
A guess() 0 5 1
B getDocFromTypeConstraintOrPayloadDocIfExist() 0 17 5
C guessPrimaryTypeFromConstraint() 0 57 10
A floatOrNumber() 0 9 4
B guessTypeFromConstraint() 0 25 6
A isAbstractType() 0 8 3
B getDocFromType() 0 19 10

How to fix   Complexity   

Complex Class

Complex classes like DocTypeHelper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocTypeHelper, and based on these observations, apply Extract Interface, too.

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\IntegerDoc;
11
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\NumberDoc;
12
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ObjectDoc;
13
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ScalarDoc;
14
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\StringDoc;
15
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\TypeDoc;
16
17
/**
18
 * Class DocTypeHelper
19
 */
20
class DocTypeHelper
21
{
22
    /** @var ConstraintPayloadDocHelper */
23
    private $constraintPayloadDocHelper;
24
25
    /**
26
     * @param ConstraintPayloadDocHelper $constraintPayloadDocHelper
27
     */
28 69
    public function __construct(ConstraintPayloadDocHelper $constraintPayloadDocHelper)
29
    {
30 69
        $this->constraintPayloadDocHelper = $constraintPayloadDocHelper;
31 69
    }
32
33
    /**
34
     * @param Constraint[] $constraintList
35
     *
36
     * @return TypeDoc
37
     */
38 69
    public function guess(array $constraintList) : TypeDoc
39
    {
40 69
        return $this->getDocFromTypeConstraintOrPayloadDocIfExist($constraintList)
41 52
        ?? $this->guessTypeFromConstraintList($constraintList)
42 69
        ?? new TypeDoc();
43
    }
44
45
    /**
46
     * @param Constraint[] $constraintList
47
     *
48
     * @return TypeDoc|null
49
     */
50 69
    private function getDocFromTypeConstraintOrPayloadDocIfExist(array $constraintList)
51
    {
52 69
        $doc = null;
53
        // Check if a Type constraint exist or if a constraint have a type documentation
54 69
        foreach ($constraintList as $constraint) {
55 68
            if (null !== ($typeFromPayload = $this->constraintPayloadDocHelper->getTypeIfExist($constraint))) {
56 1
                $doc = $this->getDocFromType($typeFromPayload);
57 67
            } elseif ($constraint instanceof Assert\Type) {
58 19
                $doc = $this->getDocFromType(strtolower($constraint->type));
59
            }
60
61 68
            if (null !== $doc) {
62 68
                break;
63
            }
64
        }
65
66 69
        return $doc;
67
    }
68
69
    /**
70
     * @param array $constraintList
71
     *
72
     * @return TypeDoc|null
73
     */
74 52
    private function guessTypeFromConstraintList(array $constraintList)
75
    {
76 52
        $doc = $abstractTypeFound = null;
77 52
        foreach ($constraintList as $constraint) {
78 51
            $doc = $this->guessTypeFromConstraint($constraint);
79 51
            if (null !== $doc) {
80 50
                if ($this->isAbstractType($doc)) {
81
                     // Abstract type => continue to see if better type can be found
82 14
                     $abstractTypeFound = $doc;
83 14
                     $doc = null;
84
                } else {
85 51
                     break;
86
                }
87
            }
88
        }
89
        // Try to fallback on abstractType if found
90 52
        if (null === $doc && null !== $abstractTypeFound) {
91 10
            $doc = $abstractTypeFound;
92
        }
93
94 52
        return $doc;
95
    }
96
97
    /**
98
     * @param Constraint $constraint
99
     *
100
     * @return TypeDoc|null
101
     */
102 51
    private function guessTypeFromConstraint(Constraint $constraint)
103
    {
104 51
        if (null !== ($type = $this->guessPrimaryTypeFromConstraint($constraint))) {
105 31
            return $type;
106
        }
107
108
        // If primary type is still not defined
109 21
        static $numberOrFloatConstraintClassList = [
110
            Assert\GreaterThan::class,
111
            Assert\GreaterThanOrEqual::class,
112
            Assert\LessThan::class,
113
            Assert\LessThanOrEqual::class,
114
        ];
115 21
        $constraintClass = get_class($constraint);
116 21
        if ($constraint instanceof Assert\Range) {
117 8
            return $this->floatOrNumber([$constraint->min, $constraint->max]);
118 15
        } elseif (in_array($constraintClass, $numberOrFloatConstraintClassList)) {
119 10
            return $this->floatOrNumber([$constraint->value]);
120 5
        } elseif (Assert\Count::class == $constraintClass) {
121 2
            return new CollectionDoc();
122 3
        } elseif ($constraint instanceof Assert\Existence) {
123 2
            return $this->guess($constraint->constraints);
124
        }
125
126 1
        return null;
127
    }
128
129
    /**
130
     * @param string $type
131
     *
132
     * @return TypeDoc|null
133
     */
134 20
    private function getDocFromType(string $type)
135
    {
136 20
        if ('scalar' === $type) {
137 1
            return new ScalarDoc();
138 19
        } elseif ('string' === $type) {
139 1
            return new StringDoc();
140 18
        } elseif ('bool' === $type || 'boolean' === $type) {
141 2
            return new BooleanDoc();
142 16
        } elseif ('int' === $type || 'integer' === $type) {
143 3
            return new IntegerDoc();
144 13
        } elseif (in_array($type, ['float', 'long', 'double', 'real', 'numeric'])) {
145 7
            return new FloatDoc();
146 6
        } elseif ('array' === $type) {
147 3
            return new ArrayDoc();
148 3
        } elseif ('object' === $type) {
149 2
            return new ObjectDoc();
150
        }
151
152 1
        return null;
153
    }
154
155
    /**
156
     * @param TypeDoc $doc
157
     *
158
     * @return bool
159
     */
160 50
    private function isAbstractType(TypeDoc $doc) : bool
161
    {
162
        // use get_class to avoid inheritance issue
163 50
        $class = get_class($doc);
164
165 50
        return CollectionDoc::class === $class
166 48
            || NumberDoc::class === $class
167 50
            || ScalarDoc::class === $class
168
        ;
169
    }
170
171
    /**
172
     * @param array $basedOnList
173
     *
174
     * @return FloatDoc|NumberDoc
175
     */
176 16
    private function floatOrNumber(array $basedOnList)
177
    {
178 16
        foreach ($basedOnList as $value) {
179 16
            if (null !== $value && is_float($value)) {
180 16
                return new FloatDoc();
181
            }
182
        }
183
184 8
        return new NumberDoc();
185
    }
186
187
    /**
188
     * @param Constraint $constraint
189
     *
190
     * @return null|ArrayDoc|BooleanDoc|ObjectDoc|ScalarDoc|StringDoc
191
     */
192 51
    private function guessPrimaryTypeFromConstraint(Constraint $constraint)
193
    {
194 51
        static $stringConstraintClassList = [
195
            Assert\Length::class, // << Applied on string only
196
            Assert\Date::class,  // << validator expect a string with specific format
197
            Assert\Time::class,  // << validator expect a string with specific format
198
            Assert\Bic::class,
199
            Assert\CardScheme::class,
200
            Assert\Country::class,
201
            Assert\Currency::class,
202
            Assert\Email::class,
203
            Assert\File::class,
204
            Assert\Iban::class,
205
            Assert\Ip::class,
206
            Assert\Isbn::class,
207
            Assert\Issn::class,
208
            Assert\Language::class,
209
            Assert\Locale::class,
210
            Assert\Luhn::class,
211
            Assert\Regex::class,
212
            Assert\Url::class,
213
            Assert\Uuid::class,
214
        ];
215 51
        static $booleanConstraintClassList = [
216
            Assert\IsTrue::class,
217
            Assert\IsFalse::class,
218
        ];
219 51
        $constraintClass = get_class($constraint);
220
221
        // Try to guess primary types
222 51
        if (in_array($constraintClass, $stringConstraintClassList)) {
223 20
            return new StringDoc();
224 32
        } elseif (in_array($constraintClass, $booleanConstraintClassList)) {
225 3
            return new BooleanDoc();
226 30
        } elseif ($constraint instanceof Assert\DateTime) {
227 6
            if ('U' === $constraint->format) {
228 5
                return new ScalarDoc();// Don't know if value will be an number as string or as integer
229
            }
230
231 1
            return new StringDoc();
232 25
        } elseif ($constraint instanceof Assert\Collection) {
233
            // If only integer => array, else object
234 2
            $integerKeyList = array_filter(array_keys($constraint->fields), 'is_int');
235 2
            if (count($constraint->fields) === count($integerKeyList)) {
236 1
                return new ArrayDoc();
237
            }
238
239 1
            return new ObjectDoc();
240 23
        } elseif (Assert\All::class === $constraintClass // << Applied only on array
241 22
            || ($constraint instanceof Assert\Choice
242 23
                && true === $constraint->multiple // << expect an array multiple choices
243
            )
244
        ) {
245 2
            return new ArrayDoc();
246
        }
247
248 21
        return null;
249
    }
250
}
251