Test Failed
Push — develop ( eeff2c...6435cb )
by Freddie
05:45
created

SchemaAttribute::isPk()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 1
cts 1
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 Exception;
13
use FlexPHP\Schema\Constants\Action;
14
use FlexPHP\Schema\Constants\Keyword;
15
use FlexPHP\Schema\Constants\Rule;
16
use FlexPHP\Schema\Exception\InvalidSchemaAttributeException;
17
use FlexPHP\Schema\Validations\SchemaAttributeLogicValidation;
18
use FlexPHP\Schema\Validations\SchemaAttributeValidation;
19
20
final class SchemaAttribute implements SchemaAttributeInterface
21
{
22
    /**
23
     * @var string
24
     */
25
    private $name;
26
27
    /**
28
     * @var string
29
     */
30
    private $dataType;
31
32
    /**
33
     * @var array<string, mixed>
34
     */
35
    private $constraints = [];
36
37
    /**
38
     * @param mixed $constraints
39 137
     */
40
    public function __construct(string $name, string $dataType, $constraints = null)
41 137
    {
42 137
        $this->setName($name);
43 137
        $this->setDataType($dataType);
44
        $this->setConstraints($constraints);
45 137
46 99
        $this->validate();
47
    }
48 137
49
    public function name(): string
50 137
    {
51
        return $this->name;
52
    }
53 137
54
    public function dataType(): string
55 137
    {
56
        return $this->dataType;
57
    }
58 137
59
    public function constraints(): array
60 137
    {
61
        return $this->constraints;
62
    }
63 6
64
    public function type(): ?string
65 6
    {
66
        return $this->constraints[Rule::TYPE] ?? null;
67
    }
68 31
69
    public function isRequired(): bool
70 31
    {
71
        return (bool)($this->constraints[Rule::REQUIRED] ?? false);
72
    }
73 78
74
    public function minLength(): ?int
75 78
    {
76
        return $this->constraints[Rule::MINLENGTH] ?? null;
77
    }
78 73
79
    public function maxLength(): ?int
80 73
    {
81
        return $this->constraints[Rule::MAXLENGTH] ?? null;
82
    }
83 32
84
    public function minCheck(): ?int
85 32
    {
86
        return $this->constraints[Rule::MINCHECK] ?? null;
87
    }
88 31
89
    public function maxCheck(): ?int
90 31
    {
91
        return $this->constraints[Rule::MAXCHECK] ?? null;
92
    }
93 62
94
    public function min(): ?int
95 62
    {
96
        return $this->constraints[Rule::MIN] ?? null;
97
    }
98 59
99
    public function max(): ?int
100 59
    {
101
        return $this->constraints[Rule::MAX] ?? null;
102
    }
103 6
104
    public function equalTo(): ?string
105 6
    {
106
        return $this->constraints[Rule::EQUALTO] ?? null;
107
    }
108 116
109
    public function isPk(): bool
110 116
    {
111
        return (bool)($this->constraints[Rule::PRIMARYKEY] ?? false);
112
    }
113 111
114
    public function isAi(): bool
115 111
    {
116
        return (bool)($this->constraints[Rule::AUTOINCREMENT] ?? false);
117
    }
118 24
119
    public function isFk(): bool
120 24
    {
121
        return (bool)($this->constraints[Rule::FOREIGNKEY] ?? false);
122
    }
123 9
124
    public function fkTable(): ?string
125 9
    {
126
        return $this->constraints[Rule::FOREIGNKEY]['table'] ?? null;
127
    }
128 9
129
    public function fkId(): ?string
130 9
    {
131
        return $this->constraints[Rule::FOREIGNKEY]['id'] ?? null;
132
    }
133 9
134
    public function fkName(): ?string
135 9
    {
136
        return $this->constraints[Rule::FOREIGNKEY]['name'] ?? null;
137
    }
138 110
139
    public function isCa(): bool
140 110
    {
141
        return (bool)($this->constraints[Rule::CREATEDAT] ?? false);
142
    }
143 109
144
    public function isUa(): bool
145 109
    {
146
        return (bool)($this->constraints[Rule::UPDATEDAT] ?? false);
147
    }
148 107
149
    public function isCb(): bool
150 107
    {
151
        return (bool)($this->constraints[Rule::CREATEDBY] ?? false);
152
    }
153 105
154
    public function isUb(): bool
155 105
    {
156
        return (bool)($this->constraints[Rule::UPDATEDBY] ?? false);
157
    }
158 110
159
    public function isBlameAt(): bool
160 110
    {
161
        return $this->isCa() || $this->isUa();
162
    }
163 107
164
    public function isBlameBy(): bool
165 107
    {
166
        return $this->isCb() || $this->isUb();
167
    }
168 28
169
    public function isBlame(): bool
170 28
    {
171
        return $this->isBlameAt() || $this->isBlameBy();
172
    }
173 137
174
    public function filter(): ?string
175
    {
176 137
        return $this->constraints[Rule::FILTER] ?? null;
177 137
    }
178 137
179
    public function format(): ?string
180
    {
181
        return $this->constraints[Rule::FORMAT] ?? null;
182 91
    }
183
184
    public function isFormat(string $format): bool
185 91
    {
186
        return $this->format() === $format;
187
    }
188
189
    public function trim(): bool
190
    {
191
        return (bool)($this->constraints[Rule::TRIM] ?? false);
192
    }
193
194
    public function fchars(): ?int
195
    {
196
        return $this->constraints[Rule::FCHARS] ?? null;
197
    }
198
199
    public function fkcheck(): bool
200
    {
201
        return (bool)($this->constraints[Rule::FKCHECK] ?? false);
202
    }
203
204 91
    public function link(): bool
205 60
    {
206
        return (bool)($this->constraints[Rule::LINK] ?? false);
207
    }
208 36
209
    public function show(): array
210
    {
211 137
        $default = $this->isBlame()
212
            ? Action::READ
213
            : Action::ALL;
214 137
215 134
        $hideConstraint = $this->constraints[Rule::HIDE] ?? '';
216 39
217 39
        if (\strpos($hideConstraint, Action::ALL) !== false) {
218 39
            $default = \str_replace(Action::ALL, '', $default);
219
        }
220
221 99
        if (\strpos($hideConstraint, Action::READ) !== false) {
222
            $default = \str_replace(Action::READ, '', $default);
223 137
        }
224
225 137
        return \explode(',', $this->constraints[Rule::SHOW] ?? $default);
226 137
    }
227
228 137
    public function hide(): array
229
    {
230 137
        $default = $this->isBlame()
231 137
            ? Action::INDEX . Action::CREATE . Action::UPDATE . Action::DELETE
232
            : '';
233
234
        return \explode(',', $this->constraints[Rule::HIDE] ?? $default);
235
    }
236 137
237
    public function usedIn(string $action): bool
238 137
    {
239 115
        return \in_array($action, $this->show(), true);
240 74
    }
241
242 41
    public function usedInAll(): bool
243
    {
244
        return $this->usedIn(Action::ALL);
245 137
    }
246
247 74
    public function usedInIndex(): bool
248
    {
249 74
        return $this->usedIn(Action::INDEX);
250 74
    }
251
252 115
    public function usedInCreate(): bool
253
    {
254 115
        return $this->usedIn(Action::CREATE);
255 115
    }
256
257 74
    public function usedInRead(): bool
258
    {
259 74
        return $this->usedIn(Action::READ);
260
    }
261
262 74
    public function usedInUpdate(): bool
263 74
    {
264
        return $this->usedIn(Action::UPDATE);
265 74
    }
266 55
267
    public function usedInDelete(): bool
268 55
    {
269 2
        return $this->usedIn(Action::DELETE);
270 2
    }
271 53
272 12
    public function properties(): array
273 45
    {
274 12
        return [
275
            Keyword::NAME => $this->name(),
276
            Keyword::DATATYPE => $this->dataType(),
277 55
            Keyword::CONSTRAINTS => $this->constraints(),
278
        ];
279 31
    }
280
281
    public function typeHint(): string
282 74
    {
283
        $typeHintByDataType = [
284
            'smallint' => 'int',
285 74
            'integer' => 'int',
286
            'float' => 'float',
287
            'double' => 'float',
288 115
            'bool' => 'bool',
289
            'boolean' => 'bool',
290 115
            'date' => '\DateTime',
291 115
            'date_immutable' => '\DateTimeImmutable',
292 12
            'datetime' => '\DateTime',
293 12
            'datetime_immutable' => '\DateTimeImmutable',
294 108
            'datetimetz' => '\DateTime',
295 4
            'datetimetz_immutable' => '\DateTimeImmutable',
296 4
            'time' => '\DateTime',
297 4
            'time_immutable' => '\DateTimeImmutable',
298 104
            'array' => 'array',
299 10
            'simple_array' => 'array',
300
            'json' => 'array',
301 115
        ];
302
303
        if (isset($typeHintByDataType[$this->dataType()])) {
304
            return $typeHintByDataType[$this->dataType()];
305 115
        }
306
307
        return 'string';
308 10
    }
309
310 10
    private function validate(): void
311 10
    {
312 10
        try {
313
            (new SchemaAttributeValidation($this->properties()))->validate();
314 10
            (new SchemaAttributeLogicValidation($this))->validate();
315 10
        } catch (Exception $e) {
316 2
            throw new InvalidSchemaAttributeException(
317
                \sprintf('Attribute %s > %s', $this->name(), $e->getMessage())
318 2
            );
319 8
        }
320 4
    }
321
322 4
    private function setName(string $name): void
323
    {
324 4
        $this->name = $name;
325
    }
326 4
327
    private function setDataType(string $dataType): void
328
    {
329
        $this->dataType = $dataType;
330 10
    }
331 10
332 10
    /**
333
     * @param mixed $constraints
334
     */
335
    private function setConstraints($constraints): void
336
    {
337
        if (!empty($constraints)) {
338
            if (\is_string($constraints)) {
339
                $this->setConstraintsFromString($constraints);
340
            } else {
341
                $this->setConstraintsFromArray($constraints);
342
            }
343
        }
344
    }
345
346
    private function setConstraintsFromString(string $constraints): void
347
    {
348
        $this->setConstraintsFromArray($this->getConstraintsFromString($constraints));
349
    }
350
351
    private function setConstraintsFromArray(array $constraints): void
352
    {
353
        $this->constraints = $this->getConstraintsCast($constraints);
354
    }
355
356
    private function getConstraintsFromString(string $constraints): array
357
    {
358
        $_constraints = \explode('|', $constraints);
359
360
        /** @var mixed $_constraint */
361
        foreach ($_constraints as $index => $_constraint) {
362
            $_rule = \explode(':', $_constraint);
363
364
            if (\count($_rule) === 2) {
365
                [$_name, $_options] = $_rule;
366
367
                if (Rule::FOREIGNKEY !== $_name && \strpos($_options, ',') !== false) { // Range
368
                    [$min, $max] = \explode(',', $_options);
369
                    $_options = \compact('min', 'max');
370
                } elseif (\preg_match('/^false$/i', $_options)) { // False as string
371
                    $_options = false;
372
                } elseif (\preg_match('/^true$/i', $_options)) { // True as string
373
                    $_options = true;
374
                }
375
376
                $_constraints[$_name] = $_options;
377
            } else {
378
                $_constraints[$_rule[0]] = true;
379
            }
380
381
            unset($_constraints[$index]);
382
        }
383
384
        return $_constraints;
385
    }
386
387
    private function getConstraintsCast(array $constraints): array
388
    {
389
        foreach ($constraints as $name => $value) {
390
            if (\is_int($name)) {
391
                $constraints[$value] = true;
392
                unset($constraints[$name]);
393
            } elseif ($name === Rule::CHECK || $name === Rule::LENGTH) {
394
                $constraints['min' . $name] = (int)$value['min'];
395
                $constraints['max' . $name] = (int)$value['max'];
396
                unset($constraints[$name]);
397
            } elseif ($name === Rule::FOREIGNKEY && \is_string($value)) {
398
                $constraints[$name] = $this->getFkOptions($value);
399
            } else {
400
                $constraints[$name] = \is_numeric($value) ? (int)$value : $value;
401
            }
402
        }
403
404
        return $constraints;
405
    }
406
407
    private function getFkOptions(string $constraint): array
408
    {
409
        $_vars = \explode(',', $constraint);
410
        $fkName = 'name';
411
        $fkId = 'id';
412
413
        switch (\count($_vars)) {
414
            case 3:
415
                [$fkTable, $fkName, $fkId] = $_vars;
416
417
                break;
418
            case 2:
419
                [$fkTable, $fkName] = $_vars;
420
421
                break;
422
            default:
423
                [$fkTable] = $_vars;
424
425
                break;
426
        }
427
428
        return [
429
            'table' => $fkTable,
430
            'name' => $fkName,
431
            'id' => $fkId,
432
        ];
433
    }
434
}
435