Completed
Push — 1.x ( 49b0c7...0c8d84 )
by Aleksei
19s queued 16s
created

RegistryModifier::addUuidColumn()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 10
c 1
b 0
f 0
nc 3
nop 3
dl 0
loc 18
ccs 0
cts 0
cp 0
crap 12
rs 9.9332
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Entity\Behavior\Schema;
6
7
use Cycle\Database\Schema\AbstractColumn;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractColumn was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Cycle\Database\Schema\AbstractTable;
9
use Cycle\ORM\Entity\Behavior\Exception\BehaviorCompilationException;
10
use Cycle\ORM\Parser\Typecast;
11
use Cycle\ORM\Parser\TypecastInterface;
12
use Cycle\ORM\SchemaInterface;
13
use Cycle\Schema\Defaults;
14
use Cycle\Schema\Definition\Entity;
15
use Cycle\Schema\Definition\Field;
16
use Cycle\Schema\Definition\Map\FieldMap;
17
use Cycle\Schema\Registry;
18
19
/**
20
 * @internal
21
 */
22
class RegistryModifier
23
{
24
    protected const DEFINITION = '/(?P<type>[a-z]+)(?: *\((?P<options>[^\)]+)\))?/i';
25
    protected const INTEGER_TYPES = [
26
        'int',
27
        'smallint',
28
        'tinyint',
29
        'bigint',
30
        'integer',
31 160
        'tinyInteger',
32
        'smallInteger',
33 160
        'bigInteger',
34 160
    ];
35 160
    protected const BIG_INTEGER_TYPES = [
36
        'bigint',
37
        'bigInteger',
38 112
    ];
39
    protected const DATETIME_TYPES = ['datetime', 'datetime2'];
40 112
    protected const INT_COLUMN = AbstractColumn::INT;
41 64
    protected const STRING_COLUMN = AbstractColumn::STRING;
42 8
    protected const BIG_INTEGER_COLUMN = 'bigInteger';
43
    protected const DATETIME_COLUMN = 'datetime';
44 56
    protected const SNOWFLAKE_COLUMN = 'snowflake';
45
    protected const ULID_COLUMN = 'ulid';
46 48
    protected const UUID_COLUMN = 'uuid';
47
48
    protected FieldMap $fields;
49 96
    protected AbstractTable $table;
50
    protected Entity $entity;
51 96
    protected Defaults $defaults;
52
53
    public function __construct(Registry $registry, string $role)
54 96
    {
55
        $this->entity = $registry->getEntity($role);
56
        $this->fields = $this->entity->getFields();
57 56
        $this->table = $registry->getTableSchema($this->entity);
58
        $this->defaults = $registry->getDefaults();
59 56
    }
60 40
61
    public static function isIntegerType(string $type): bool
62
    {
63 40
        \preg_match(self::DEFINITION, $type, $matches);
64
65 40
        return \in_array($matches['type'], self::INTEGER_TYPES, true);
66
    }
67
68 56
    public static function isBigIntegerType(string $type): bool
69
    {
70 56
        \preg_match(self::DEFINITION, $type, $matches);
71
72
        return \in_array($matches['type'], self::BIG_INTEGER_TYPES, true);
73 48
    }
74
75 48
    public static function isDatetimeType(string $type): bool
76 40
    {
77
        \preg_match(self::DEFINITION, $type, $matches);
78
79 40
        return \in_array($matches['type'], self::DATETIME_TYPES, true);
80
    }
81 40
82
    public static function isStringType(string $type): bool
83
    {
84 48
        \preg_match(self::DEFINITION, $type, $matches);
85
86 48
        return $matches['type'] === 'string';
87
    }
88
89
    public static function isSnowflakeType(string $type): bool
90
    {
91
        \preg_match(self::DEFINITION, $type, $matches);
92 32
93
        return $matches['type'] === self::SNOWFLAKE_COLUMN;
94 32
    }
95
96
    public static function isUlidType(string $type): bool
97
    {
98
        \preg_match(self::DEFINITION, $type, $matches);
99
100
        return $matches['type'] === self::ULID_COLUMN;
101
    }
102
103 32
    public static function isUuidType(string $type): bool
104
    {
105 32
        \preg_match(self::DEFINITION, $type, $matches);
106
107
        return $matches['type'] === self::UUID_COLUMN;
108 104
    }
109
110 104
    public function addDatetimeColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
111 64
    {
112
        if ($this->fields->has($fieldName)) {
113
            if (!static::isDatetimeType($this->fields->get($fieldName)->getType())) {
114 88
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type datetime.', $fieldName));
115
            }
116
            $this->validateColumnName($fieldName, $columnName);
117
            $this->fields->get($fieldName)->setGenerated($generated);
118
119
            return $this->table->column($columnName);
120 24
        }
121
122 24
        $field = (new Field())
123 16
            ->setColumn($columnName)
124
            ->setType('datetime')
125
            ->setTypecast('datetime')
126 24
            ->setGenerated($generated);
127 24
        $this->fields->set($fieldName, $field);
128 16
129 16
        return $this->table->column($columnName)->type(self::DATETIME_COLUMN);
130
    }
131
132 16
    public function addIntegerColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
133 16
    {
134 16
        if ($this->fields->has($fieldName)) {
135
            if (!static::isIntegerType($this->fields->get($fieldName)->getType())) {
136 16
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type integer.', $fieldName));
137
            }
138
            $this->validateColumnName($fieldName, $columnName);
139
            $this->fields->get($fieldName)->setGenerated($generated);
140
141
            return $this->table->column($columnName);
142 96
        }
143
144 96
        $field = (new Field())
145
            ->setColumn($columnName)
146 96
            ->setType('integer')
147 8
            ->setTypecast('int')
148 8
            ->setGenerated($generated);
149 8
        $this->fields->set($fieldName, $field);
150 8
151
        return $this->table->column($columnName)->type(self::INT_COLUMN);
152 8
    }
153
154
    public function addBigIntegerColumn(
155
        string $columnName,
156
        string $fieldName,
157
        int|null $generated = null,
158
    ): AbstractColumn {
159 104
        if ($this->fields->has($fieldName)) {
160
            if (! static::isBigIntegerType($this->fields->get($fieldName)->getType())) {
161 104
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type big integer.', $fieldName));
162
            }
163 64
            $this->validateColumnName($fieldName, $columnName);
164 64
            $this->fields->get($fieldName)->setGenerated($generated);
165
166
            return $this->table->column($columnName);
167 40
        }
168 40
169
        $field = (new Field())
170
            ->setColumn($columnName)
171 40
            ->setType(self::BIG_INTEGER_COLUMN)
172
            ->setTypecast('int')
173
            ->setGenerated($generated);
174
175
        $this->fields->set($fieldName, $field);
176
177 40
        return $this->table->column($columnName)->type(self::BIG_INTEGER_COLUMN);
178
    }
179
180
    public function addStringColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
181
    {
182
        if ($this->fields->has($fieldName)) {
183
            if (!static::isStringType($this->fields->get($fieldName)->getType())) {
184
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type string.', $fieldName));
185
            }
186
            $this->validateColumnName($fieldName, $columnName);
187
            $this->fields->get($fieldName)->setGenerated($generated);
188
189
            return $this->table->column($columnName);
190
        }
191
192
        $field = (new Field())->setColumn($columnName)->setType('string')->setGenerated($generated);
193
        $this->fields->set($fieldName, $field);
194
195
        return $this->table->column($columnName)->type(self::STRING_COLUMN);
196
    }
197
198
    /**
199
     * @param non-empty-string $columnName
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
200
     * @throws BehaviorCompilationException
201
     */
202
    public function addSnowflakeColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
203
    {
204
        if ($this->fields->has($fieldName)) {
205
            if (!static::isSnowflakeType($this->fields->get($fieldName)->getType())) {
206
                throw new BehaviorCompilationException(
207
                    \sprintf('Field %s must be of type %s.', $fieldName, self::SNOWFLAKE_COLUMN),
208
                );
209
            }
210
            $this->validateColumnName($fieldName, $columnName);
211
            $this->fields->get($fieldName)->setGenerated($generated);
212
213
            return $this->table->column($columnName);
214
        }
215
216
        $field = (new Field())->setColumn($columnName)->setType(self::SNOWFLAKE_COLUMN)->setGenerated($generated);
217
        $this->fields->set($fieldName, $field);
218
219
        return $this->table->column($columnName)->type(self::SNOWFLAKE_COLUMN);
220
    }
221
222
    /**
223
     * @param non-empty-string $columnName
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
224
     * @throws BehaviorCompilationException
225
     */
226
    public function addUlidColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
227
    {
228
        if ($this->fields->has($fieldName)) {
229
            if (!static::isUlidType($this->fields->get($fieldName)->getType())) {
230
                throw new BehaviorCompilationException(
231
                    \sprintf('Field %s must be of type %s.', $fieldName, self::ULID_COLUMN),
232
                );
233
            }
234
            $this->validateColumnName($fieldName, $columnName);
235
            $this->fields->get($fieldName)->setGenerated($generated);
236
237
            return $this->table->column($columnName);
238
        }
239
240
        $field = (new Field())->setColumn($columnName)->setType(self::ULID_COLUMN)->setGenerated($generated);
241
        $this->fields->set($fieldName, $field);
242
243
        return $this->table->column($columnName)->type(self::ULID_COLUMN);
244
    }
245
246
    /**
247
     * @param non-empty-string $columnName
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
248
     * @throws BehaviorCompilationException
249
     */
250
    public function addUuidColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
251
    {
252
        if ($this->fields->has($fieldName)) {
253
            if (!static::isUuidType($this->fields->get($fieldName)->getType())) {
254
                throw new BehaviorCompilationException(
255
                    \sprintf('Field %s must be of type %s.', $fieldName, self::UUID_COLUMN),
256
                );
257
            }
258
            $this->validateColumnName($fieldName, $columnName);
259
            $this->fields->get($fieldName)->setGenerated($generated);
260
261
            return $this->table->column($columnName);
262
        }
263
264
        $field = (new Field())->setColumn($columnName)->setType(self::UUID_COLUMN)->setGenerated($generated);
265
        $this->fields->set($fieldName, $field);
266
267
        return $this->table->column($columnName)->type(self::UUID_COLUMN);
268
    }
269
270
    public function findColumnName(string $fieldName, ?string $columnName): ?string
271
    {
272
        if ($columnName !== null) {
273
            return $columnName;
274
        }
275
276
        return $this->fields->has($fieldName) ? $this->fields->get($fieldName)->getColumn() : null;
277
    }
278
279
    /**
280
     * @param class-string<TypecastInterface> $handler
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<TypecastInterface> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<TypecastInterface>.
Loading history...
281
     */
282
    public function setTypecast(Field $field, array|string|null $rule, string $handler = Typecast::class): Field
283
    {
284
        if ($field->getTypecast() === null) {
285
            $field->setTypecast($rule);
286
        }
287
288
        $defaultHandlers = $this->defaults[SchemaInterface::TYPECAST_HANDLER] ?? [];
289
        if (!\is_array($defaultHandlers)) {
290
            $defaultHandlers = [$defaultHandlers];
291
        }
292
293
        $handlers = $this->entity->getTypecast() ?? [];
294
        if (!\is_array($handlers)) {
295
            $handlers = [$handlers];
296
        }
297
298
        if (!\in_array($handler, $handlers, true) && !\in_array($handler, $defaultHandlers, true)) {
0 ignored issues
show
Bug introduced by
It seems like $handlers can also be of type string; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

298
        if (!\in_array($handler, /** @scrutinizer ignore-type */ $handlers, true) && !\in_array($handler, $defaultHandlers, true)) {
Loading history...
299
            $this->entity->setTypecast(\array_merge($handlers, [$handler]));
0 ignored issues
show
Bug introduced by
It seems like $handlers can also be of type string; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

299
            $this->entity->setTypecast(\array_merge(/** @scrutinizer ignore-type */ $handlers, [$handler]));
Loading history...
300
        }
301
302
        return $field;
303
    }
304
305
    /**
306
     * @throws BehaviorCompilationException
307
     */
308
    protected function validateColumnName(string $fieldName, string $columnName): void
309
    {
310
        $field = $this->fields->get($fieldName);
311
312
        if ($field->getColumn() !== $columnName) {
313
            throw new BehaviorCompilationException(
314
                \sprintf(
315
                    'Ambiguous column name definition. '
316
                    . 'The `%s` field already linked with the `%s` column but the behavior expects `%s`.',
317
                    $fieldName,
318
                    $field->getColumn(),
319
                    $columnName,
320
                ),
321
            );
322
        }
323
    }
324
325
    /**
326
     * @deprecated since v1.2
327
     *
328
     * @param non-empty-string $type
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
329
     * @param non-empty-string $fieldName
330
     * @param non-empty-string $columnName
331
     */
332
    protected function isType(string $type, string $fieldName, string $columnName): bool
333
    {
334
        if ($type === self::DATETIME_COLUMN) {
335
            return
336
                $this->table->column($columnName)->getInternalType() === self::DATETIME_COLUMN ||
337
                $this->fields->get($fieldName)->getType() === self::DATETIME_COLUMN;
338
        }
339
340
        if ($type === self::INT_COLUMN) {
341
            return $this->table->column($columnName)->getType() === self::INT_COLUMN;
342
        }
343
344
        if ($type === self::UUID_COLUMN) {
345
            return
346
                $this->table->column($columnName)->getInternalType() === self::UUID_COLUMN ||
347
                $this->fields->get($fieldName)->getType() === self::UUID_COLUMN;
348
        }
349
350
        return $this->table->column($columnName)->getType() === $type;
351
    }
352
}
353