Passed
Push — develop ( 8d04e4...f8d7bf )
by Freddie
03:08
created

SchemaAttribute::default()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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