Completed
Push — develop ( 1dfcbc...c8e703 )
by Freddie
03:55
created

SchemaAttribute::validateLogic()   D

Complexity

Conditions 34
Paths 13

Size

Total Lines 58
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 34.0389

Importance

Changes 0
Metric Value
cc 34
eloc 30
c 0
b 0
f 0
nc 13
nop 0
dl 0
loc 58
rs 4.1666
ccs 30
cts 31
cp 0.9677
crap 34.0389

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of FlexPHP.
4
 *
5
 * (c) Freddie Gar <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace FlexPHP\Schema;
11
12
use FlexPHP\Schema\Constants\Keyword;
13
use FlexPHP\Schema\Constants\Rule;
14
use FlexPHP\Schema\Exception\InvalidSchemaAttributeException;
15
use FlexPHP\Schema\Validations\SchemaAttributeValidation;
16
17
final class SchemaAttribute implements SchemaAttributeInterface
18
{
19
    /**
20
     * @var string
21
     */
22
    private $name;
23
24
    /**
25
     * @var string
26
     */
27
    private $dataType;
28
29
    /**
30
     * @var array<string, mixed>
31
     */
32
    private $constraints = [];
33
34
    /**
35
     * @param mixed $constraints
36
     */
37 116
    public function __construct(string $name, string $dataType, $constraints = null)
38
    {
39 116
        $this->setName($name);
40 116
        $this->setDataType($dataType);
41 116
        $this->setConstraints($constraints);
42
43 116
        $this->validate();
44 113
        $this->validateLogic();
45 84
    }
46
47 116
    public function name(): string
48
    {
49 116
        return $this->name;
50
    }
51
52 116
    public function dataType(): string
53
    {
54 116
        return $this->dataType;
55
    }
56
57 116
    public function constraints(): array
58
    {
59 116
        return $this->constraints;
60
    }
61
62 6
    public function type(): ?string
63
    {
64 6
        return $this->constraints[Rule::TYPE] ?? null;
65
    }
66
67 29
    public function isRequired(): bool
68
    {
69 29
        return (bool)($this->constraints[Rule::REQUIRED] ?? false);
70
    }
71
72 64
    public function minLength(): ?int
73
    {
74 64
        return $this->constraints[Rule::MINLENGTH] ?? null;
75
    }
76
77 59
    public function maxLength(): ?int
78
    {
79 59
        return $this->constraints[Rule::MAXLENGTH] ?? null;
80
    }
81
82 24
    public function minCheck(): ?int
83
    {
84 24
        return $this->constraints[Rule::MINCHECK] ?? null;
85
    }
86
87 23
    public function maxCheck(): ?int
88
    {
89 23
        return $this->constraints[Rule::MAXCHECK] ?? null;
90
    }
91
92 55
    public function min(): ?int
93
    {
94 55
        return $this->constraints[Rule::MIN] ?? null;
95
    }
96
97 53
    public function max(): ?int
98
    {
99 53
        return $this->constraints[Rule::MAX] ?? null;
100
    }
101
102 6
    public function equalTo(): ?string
103
    {
104 6
        return $this->constraints[Rule::EQUALTO] ?? null;
105
    }
106
107 97
    public function isPk(): bool
108
    {
109 97
        return (bool)($this->constraints[Rule::PRIMARYKEY] ?? false);
110
    }
111
112 94
    public function isAi(): bool
113
    {
114 94
        return (bool)($this->constraints[Rule::AUTOINCREMENT] ?? false);
115
    }
116
117 21
    public function isFk(): bool
118
    {
119 21
        return (bool)($this->constraints[Rule::FOREIGNKEY] ?? false);
120
    }
121
122 9
    public function fkTable(): ?string
123
    {
124 9
        return $this->constraints[Rule::FOREIGNKEY]['table'] ?? null;
125
    }
126
127 9
    public function fkId(): ?string
128
    {
129 9
        return $this->constraints[Rule::FOREIGNKEY]['id'] ?? null;
130
    }
131
132 9
    public function fkName(): ?string
133
    {
134 9
        return $this->constraints[Rule::FOREIGNKEY]['name'] ?? null;
135
    }
136
137 90
    public function isCa(): bool
138
    {
139 90
        return (bool)($this->constraints[Rule::CREATEDAT] ?? false);
140
    }
141
142 89
    public function isUa(): bool
143
    {
144 89
        return (bool)($this->constraints[Rule::UPDATEDAT] ?? false);
145
    }
146
147 90
    public function isBlame(): bool
148
    {
149 90
        return $this->isCa() || $this->isUa();
150
    }
151
152 116
    public function properties(): array
153
    {
154
        return [
155 116
            Keyword::NAME => $this->name(),
156 116
            Keyword::DATATYPE => $this->dataType(),
157 116
            Keyword::CONSTRAINTS => $this->constraints(),
158
        ];
159
    }
160
161 78
    public function typeHint(): string
162
    {
163
        $typeHintByDataType = [
164 78
            'smallint' => 'int',
165
            'integer' => 'int',
166
            'float' => 'float',
167
            'double' => 'float',
168
            'bool' => 'bool',
169
            'boolean' => 'bool',
170
            'date' => '\DateTime',
171
            'date_immutable' => '\DateTimeImmutable',
172
            'datetime' => '\DateTime',
173
            'datetime_immutable' => '\DateTimeImmutable',
174
            'datetimetz' => '\DateTime',
175
            'datetimetz_immutable' => '\DateTimeImmutable',
176
            'time' => '\DateTime',
177
            'time_immutable' => '\DateTimeImmutable',
178
            'array' => 'array',
179
            'simple_array' => 'array',
180
            'json_array' => 'array',
181
        ];
182
183 78
        if (isset($typeHintByDataType[$this->dataType()])) {
184 47
            return $typeHintByDataType[$this->dataType()];
185
        }
186
187 35
        return 'string';
188
    }
189
190 116
    private function validate(): void
191
    {
192 116
        (new SchemaAttributeValidation($this->properties()))->validate();
193 113
    }
194
195 113
    private function validateLogic(): void
196
    {
197 113
        if (empty($this->constraints())) {
198 19
            return;
199
        }
200
201 96
        $name = 'Logic: [' . $this->name() . '] ';
202
203 96
        if ($this->isPk() && !$this->isRequired()) {
204 3
            throw new InvalidSchemaAttributeException($name . 'Primary Key must be required.');
205
        }
206
207 93
        if ($this->isAi() && !$this->isPk()) {
208 1
            throw new InvalidSchemaAttributeException($name . 'Autoincrement must be Primary Key too.');
209
        }
210
211 92
        if (!$this->isAi() && $this->isPk() && \in_array($this->dataType(), ['smallint', 'integer', 'bigint'])) {
212 1
            throw new InvalidSchemaAttributeException($name . 'Primary Key numeric not autoincrement?. Use string.');
213
        }
214
215 91
        if ($this->isAi() && !\in_array($this->dataType(), ['smallint', 'integer', 'bigint'])) {
216 1
            throw new InvalidSchemaAttributeException($name . 'Autoincrement must be a numeric datatype.');
217
        }
218
219 90
        if ($this->isPk() && $this->isFk()) {
220 2
            throw new InvalidSchemaAttributeException($name . 'Primary Key cannot be Foreing Key too.');
221
        }
222
223 88
        if ($this->isAi() && $this->isFk()) {
224
            throw new InvalidSchemaAttributeException($name . 'Foreign Key cannot be autoincrement.');
225
        }
226
227 88
        if ($this->isBlame() && \strpos($this->typeHint(), '\Date') === false) {
228 2
            throw new InvalidSchemaAttributeException($name . 'Blame property must be date datetype valid.');
229
        }
230
231 86
        if ($this->isCa() && $this->isUa()) {
232 1
            throw new InvalidSchemaAttributeException($name . 'Created and Updated At in same property is not valid.');
233
        }
234
235 85
        if (\in_array($this->dataType(), ['smallint', 'integer', 'bigint', 'double', 'float'])
236 85
            && ($this->minLength() !== null || $this->maxLength() !== null)
237
        ) {
238 10
            throw new InvalidSchemaAttributeException($name . 'Numeric property use: min, max.');
239
        }
240
241 75
        if ($this->dataType() !== 'bigint' && $this->typeHint() === 'string'
242 75
            && ($this->min() !== null || $this->max() !== null)
243
        ) {
244 4
            throw new InvalidSchemaAttributeException($name . 'String properties use minlength, maxlength.');
245
        }
246
247 71
        if ((\strpos($this->typeHint(), '\Date') !== false || \in_array($this->dataType(), ['bool', 'blob']))
248 17
            && ($this->min() !== null || $this->max() !== null
249 17
                || $this->minLength() !== null || $this->maxLength() !== null
250 71
                || $this->minCheck() !== null || $this->maxCheck() !== null)
251
        ) {
252 4
            throw new InvalidSchemaAttributeException($name . 'Date, bool, blob properties not use min, max, etc');
253
        }
254 67
    }
255
256 116
    private function setName(string $name): void
257
    {
258 116
        $this->name = $name;
259 116
    }
260
261 116
    private function setDataType(string $dataType): void
262
    {
263 116
        $this->dataType = $dataType;
264 116
    }
265
266
    /**
267
     * @param mixed $constraints
268
     */
269 116
    private function setConstraints($constraints): void
270
    {
271 116
        if (!empty($constraints)) {
272 96
            if (\is_string($constraints)) {
273 61
                $this->setConstraintsFromString($constraints);
274
            } else {
275 35
                $this->setConstraintsFromArray($constraints);
276
            }
277
        }
278 116
    }
279
280 61
    private function setConstraintsFromString(string $constraints): void
281
    {
282 61
        $this->setConstraintsFromArray($this->getConstraintsFromString($constraints));
283 61
    }
284
285 96
    private function setConstraintsFromArray(array $constraints): void
286
    {
287 96
        $this->constraints = $this->getConstraintsCast($constraints);
288 96
    }
289
290 61
    private function getConstraintsFromString(string $constraints): array
291
    {
292 61
        $_constraints = \explode('|', $constraints);
293
294
        /** @var mixed $_constraint */
295 61
        foreach ($_constraints as $index => $_constraint) {
296 61
            $_rule = \explode(':', $_constraint);
297
298 61
            if (\count($_rule) === 2) {
299 49
                [$_name, $_options] = $_rule;
300
301 49
                if (Rule::FOREIGNKEY !== $_name && \strpos($_options, ',') !== false) { // Range
302 2
                    [$min, $max] = \explode(',', $_options);
303 2
                    $_options = \compact('min', 'max');
304 47
                } elseif (\preg_match('/^false$/i', $_options)) { // False as string
305 9
                    $_options = false;
306 41
                } elseif (\preg_match('/^true$/i', $_options)) { // True as string
307 9
                    $_options = true;
308
                }
309
310 49
                $_constraints[$_name] = $_options;
311
            } else {
312 23
                $_constraints[$_rule[0]] = true;
313
            }
314
315 61
            unset($_constraints[$index]);
316
        }
317
318 61
        return $_constraints;
319
    }
320
321 96
    private function getConstraintsCast(array $constraints): array
322
    {
323 96
        foreach ($constraints as $name => $value) {
324 96
            if (\is_int($name)) {
325 10
                $constraints[$value] = true;
326 10
                unset($constraints[$name]);
327 91
            } elseif ($name === Rule::CHECK || $name === Rule::LENGTH) {
328 4
                $constraints['min' . $name] = (int)$value['min'];
329 4
                $constraints['max' . $name] = (int)$value['max'];
330 4
                unset($constraints[$name]);
331 87
            } elseif ($name === Rule::FOREIGNKEY && \is_string($value)) {
332 10
                $constraints[$name] = $this->getFkOptions($value);
333
            } else {
334 96
                $constraints[$name] = \is_numeric($value) ? (int)$value : $value;
335
            }
336
        }
337
338 96
        return $constraints;
339
    }
340
341 10
    private function getFkOptions(string $constraint): array
342
    {
343 10
        $_vars = \explode(',', $constraint);
344 10
        $fkName = 'name';
345 10
        $fkId = 'id';
346
347 10
        switch (\count($_vars)) {
348 10
            case 3:
349 2
                [$fkTable, $fkName, $fkId] = $_vars;
350
351 2
                break;
352 8
            case 2:
353 4
                [$fkTable, $fkName] = $_vars;
354
355 4
                break;
356
            default:
357 4
                [$fkTable] = $_vars;
358
359 4
                break;
360
        }
361
362
        return [
363 10
            'table' => $fkTable,
364 10
            'name' => $fkName,
365 10
            'id' => $fkId,
366
        ];
367
    }
368
}
369