Completed
Push — 1.x ( e8ec08...799fa1 )
by
unknown
18s queued 15s
created

RegistryModifier::addDatetimeColumn()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 13
c 0
b 0
f 0
nc 3
nop 3
dl 0
loc 20
ccs 7
cts 7
cp 1
crap 3
rs 9.8333
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 ULID_COLUMN = 'ulid';
45
    protected const UUID_COLUMN = 'uuid';
46 48
47
    protected FieldMap $fields;
48
    protected AbstractTable $table;
49 96
    protected Entity $entity;
50
    protected Defaults $defaults;
51 96
52
    public function __construct(Registry $registry, string $role)
53
    {
54 96
        $this->entity = $registry->getEntity($role);
55
        $this->fields = $this->entity->getFields();
56
        $this->table = $registry->getTableSchema($this->entity);
57 56
        $this->defaults = $registry->getDefaults();
58
    }
59 56
60 40
    public static function isIntegerType(string $type): bool
61
    {
62
        \preg_match(self::DEFINITION, $type, $matches);
63 40
64
        return \in_array($matches['type'], self::INTEGER_TYPES, true);
65 40
    }
66
67
    public static function isBigIntegerType(string $type): bool
68 56
    {
69
        \preg_match(self::DEFINITION, $type, $matches);
70 56
71
        return \in_array($matches['type'], self::BIG_INTEGER_TYPES, true);
72
    }
73 48
74
    public static function isDatetimeType(string $type): bool
75 48
    {
76 40
        \preg_match(self::DEFINITION, $type, $matches);
77
78
        return \in_array($matches['type'], self::DATETIME_TYPES, true);
79 40
    }
80
81 40
    public static function isStringType(string $type): bool
82
    {
83
        \preg_match(self::DEFINITION, $type, $matches);
84 48
85
        return $matches['type'] === 'string';
86 48
    }
87
88
    public static function isUlidType(string $type): bool
89
    {
90
        \preg_match(self::DEFINITION, $type, $matches);
91
92 32
        return $matches['type'] === self::ULID_COLUMN;
93
    }
94 32
95
    public static function isUuidType(string $type): bool
96
    {
97
        \preg_match(self::DEFINITION, $type, $matches);
98
99
        return $matches['type'] === self::UUID_COLUMN;
100
    }
101
102
    public function addDatetimeColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
103 32
    {
104
        if ($this->fields->has($fieldName)) {
105 32
            if (!static::isDatetimeType($this->fields->get($fieldName)->getType())) {
106
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type datetime.', $fieldName));
107
            }
108 104
            $this->validateColumnName($fieldName, $columnName);
109
            $this->fields->get($fieldName)->setGenerated($generated);
110 104
111 64
            return $this->table->column($columnName);
112
        }
113
114 88
        $field = (new Field())
115
            ->setColumn($columnName)
116
            ->setType('datetime')
117
            ->setTypecast('datetime')
118
            ->setGenerated($generated);
119
        $this->fields->set($fieldName, $field);
120 24
121
        return $this->table->column($columnName)->type(self::DATETIME_COLUMN);
122 24
    }
123 16
124
    public function addIntegerColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
125
    {
126 24
        if ($this->fields->has($fieldName)) {
127 24
            if (!static::isIntegerType($this->fields->get($fieldName)->getType())) {
128 16
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type integer.', $fieldName));
129 16
            }
130
            $this->validateColumnName($fieldName, $columnName);
131
            $this->fields->get($fieldName)->setGenerated($generated);
132 16
133 16
            return $this->table->column($columnName);
134 16
        }
135
136 16
        $field = (new Field())
137
            ->setColumn($columnName)
138
            ->setType('integer')
139
            ->setTypecast('int')
140
            ->setGenerated($generated);
141
        $this->fields->set($fieldName, $field);
142 96
143
        return $this->table->column($columnName)->type(self::INT_COLUMN);
144 96
    }
145
146 96
    public function addBigIntegerColumn(
147 8
        string $columnName,
148 8
        string $fieldName,
149 8
        int|null $generated = null,
150 8
    ): AbstractColumn {
151
        if ($this->fields->has($fieldName)) {
152 8
            if (! static::isBigIntegerType($this->fields->get($fieldName)->getType())) {
153
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type big integer.', $fieldName));
154
            }
155
            $this->validateColumnName($fieldName, $columnName);
156
            $this->fields->get($fieldName)->setGenerated($generated);
157
158
            return $this->table->column($columnName);
159 104
        }
160
161 104
        $field = (new Field())
162
            ->setColumn($columnName)
163 64
            ->setType(self::BIG_INTEGER_COLUMN)
164 64
            ->setTypecast('int')
165
            ->setGenerated($generated);
166
167 40
        $this->fields->set($fieldName, $field);
168 40
169
        return $this->table->column($columnName)->type(self::BIG_INTEGER_COLUMN);
170
    }
171 40
172
    public function addStringColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
173
    {
174
        if ($this->fields->has($fieldName)) {
175
            if (!static::isStringType($this->fields->get($fieldName)->getType())) {
176
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type string.', $fieldName));
177 40
            }
178
            $this->validateColumnName($fieldName, $columnName);
179
            $this->fields->get($fieldName)->setGenerated($generated);
180
181
            return $this->table->column($columnName);
182
        }
183
184
        $field = (new Field())->setColumn($columnName)->setType('string')->setGenerated($generated);
185
        $this->fields->set($fieldName, $field);
186
187
        return $this->table->column($columnName)->type(self::STRING_COLUMN);
188
    }
189
190
    /**
191
     * @throws BehaviorCompilationException
192
     */
193
    public function addUlidColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
194
    {
195
        if ($this->fields->has($fieldName)) {
196
            if (!static::isUlidType($this->fields->get($fieldName)->getType())) {
197
                throw new BehaviorCompilationException(
198
                    \sprintf('Field %s must be of type %s.', $fieldName, self::ULID_COLUMN),
199
                );
200
            }
201
            $this->validateColumnName($fieldName, $columnName);
202
            $this->fields->get($fieldName)->setGenerated($generated);
203
204
            return $this->table->column($columnName);
205
        }
206
207
        $field = (new Field())->setColumn($columnName)->setType(self::ULID_COLUMN)->setGenerated($generated);
208
        $this->fields->set($fieldName, $field);
209
210
        return $this->table->column($columnName)->type(self::ULID_COLUMN);
211
    }
212
213
    /**
214
     * @throws BehaviorCompilationException
215
     */
216
    public function addUuidColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
217
    {
218
        if ($this->fields->has($fieldName)) {
219
            if (!static::isUuidType($this->fields->get($fieldName)->getType())) {
220
                throw new BehaviorCompilationException(
221
                    \sprintf('Field %s must be of type %s.', $fieldName, self::UUID_COLUMN),
222
                );
223
            }
224
            $this->validateColumnName($fieldName, $columnName);
225
            $this->fields->get($fieldName)->setGenerated($generated);
226
227
            return $this->table->column($columnName);
228
        }
229
230
        $field = (new Field())->setColumn($columnName)->setType(self::UUID_COLUMN)->setGenerated($generated);
231
        $this->fields->set($fieldName, $field);
232
233
        return $this->table->column($columnName)->type(self::UUID_COLUMN);
234
    }
235
236
    public function findColumnName(string $fieldName, ?string $columnName): ?string
237
    {
238
        if ($columnName !== null) {
239
            return $columnName;
240
        }
241
242
        return $this->fields->has($fieldName) ? $this->fields->get($fieldName)->getColumn() : null;
243
    }
244
245
    /**
246
     * @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...
247
     */
248
    public function setTypecast(Field $field, array|string|null $rule, string $handler = Typecast::class): Field
249
    {
250
        if ($field->getTypecast() === null) {
251
            $field->setTypecast($rule);
252
        }
253
254
        $defaultHandlers = $this->defaults[SchemaInterface::TYPECAST_HANDLER] ?? [];
255
        if (!\is_array($defaultHandlers)) {
256
            $defaultHandlers = [$defaultHandlers];
257
        }
258
259
        $handlers = $this->entity->getTypecast() ?? [];
260
        if (!\is_array($handlers)) {
261
            $handlers = [$handlers];
262
        }
263
264
        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

264
        if (!\in_array($handler, /** @scrutinizer ignore-type */ $handlers, true) && !\in_array($handler, $defaultHandlers, true)) {
Loading history...
265
            $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

265
            $this->entity->setTypecast(\array_merge(/** @scrutinizer ignore-type */ $handlers, [$handler]));
Loading history...
266
        }
267
268
        return $field;
269
    }
270
271
    /**
272
     * @throws BehaviorCompilationException
273
     */
274
    protected function validateColumnName(string $fieldName, string $columnName): void
275
    {
276
        $field = $this->fields->get($fieldName);
277
278
        if ($field->getColumn() !== $columnName) {
279
            throw new BehaviorCompilationException(
280
                \sprintf(
281
                    'Ambiguous column name definition. '
282
                    . 'The `%s` field already linked with the `%s` column but the behavior expects `%s`.',
283
                    $fieldName,
284
                    $field->getColumn(),
285
                    $columnName,
286
                ),
287
            );
288
        }
289
    }
290
291
    /**
292
     * @deprecated since v1.2
293
     *
294
     * @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...
295
     * @param non-empty-string $fieldName
296
     * @param non-empty-string $columnName
297
     */
298
    protected function isType(string $type, string $fieldName, string $columnName): bool
299
    {
300
        if ($type === self::DATETIME_COLUMN) {
301
            return
302
                $this->table->column($columnName)->getInternalType() === self::DATETIME_COLUMN ||
303
                $this->fields->get($fieldName)->getType() === self::DATETIME_COLUMN;
304
        }
305
306
        if ($type === self::INT_COLUMN) {
307
            return $this->table->column($columnName)->getType() === self::INT_COLUMN;
308
        }
309
310
        if ($type === self::UUID_COLUMN) {
311
            return
312
                $this->table->column($columnName)->getInternalType() === self::UUID_COLUMN ||
313
                $this->fields->get($fieldName)->getType() === self::UUID_COLUMN;
314
        }
315
316
        return $this->table->column($columnName)->getType() === $type;
317
    }
318
}
319