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

SchemaAttribute::maxLength()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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