RegistryModifier::findColumnName()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 3
nop 2
dl 0
loc 7
ccs 3
cts 3
cp 1
crap 3
rs 10
c 0
b 0
f 0
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 DATETIME_TYPES = ['datetime', 'datetime2'];
36
    protected const INT_COLUMN = AbstractColumn::INT;
37
    protected const STRING_COLUMN = AbstractColumn::STRING;
38 112
    protected const DATETIME_COLUMN = 'datetime';
39
    protected const UUID_COLUMN = 'uuid';
40 112
41 64
    protected FieldMap $fields;
42 8
    protected AbstractTable $table;
43
    protected Entity $entity;
44 56
    protected Defaults $defaults;
45
46 48
    public function __construct(Registry $registry, string $role)
47
    {
48
        $this->entity = $registry->getEntity($role);
49 96
        $this->fields = $this->entity->getFields();
50
        $this->table = $registry->getTableSchema($this->entity);
51 96
        $this->defaults = $registry->getDefaults();
52
    }
53
54 96
    public static function isIntegerType(string $type): bool
55
    {
56
        \preg_match(self::DEFINITION, $type, $matches);
57 56
58
        return \in_array($matches['type'], self::INTEGER_TYPES, true);
59 56
    }
60 40
61
    public static function isDatetimeType(string $type): bool
62
    {
63 40
        \preg_match(self::DEFINITION, $type, $matches);
64
65 40
        return \in_array($matches['type'], self::DATETIME_TYPES, true);
66
    }
67
68 56
    public static function isStringType(string $type): bool
69
    {
70 56
        \preg_match(self::DEFINITION, $type, $matches);
71
72
        return $matches['type'] === 'string';
73 48
    }
74
75 48
    public static function isUuidType(string $type): bool
76 40
    {
77
        \preg_match(self::DEFINITION, $type, $matches);
78
79 40
        return $matches['type'] === 'uuid';
80
    }
81 40
82
    public function addDatetimeColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
83
    {
84 48
        if ($this->fields->has($fieldName)) {
85
            if (!static::isDatetimeType($this->fields->get($fieldName)->getType())) {
86 48
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type datetime.', $fieldName));
87
            }
88
            $this->validateColumnName($fieldName, $columnName);
89
            $this->fields->get($fieldName)->setGenerated($generated);
90
91
            return $this->table->column($columnName);
92 32
        }
93
94 32
        $field = (new Field())
95
            ->setColumn($columnName)
96
            ->setType('datetime')
97
            ->setTypecast('datetime')
98
            ->setGenerated($generated);
99
        $this->fields->set($fieldName, $field);
100
101
        return $this->table->column($columnName)->type(self::DATETIME_COLUMN);
102
    }
103 32
104
    public function addIntegerColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
105 32
    {
106
        if ($this->fields->has($fieldName)) {
107
            if (!static::isIntegerType($this->fields->get($fieldName)->getType())) {
108 104
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type integer.', $fieldName));
109
            }
110 104
            $this->validateColumnName($fieldName, $columnName);
111 64
            $this->fields->get($fieldName)->setGenerated($generated);
112
113
            return $this->table->column($columnName);
114 88
        }
115
116
        $field = (new Field())
117
            ->setColumn($columnName)
118
            ->setType('integer')
119
            ->setTypecast('int')
120 24
            ->setGenerated($generated);
121
        $this->fields->set($fieldName, $field);
122 24
123 16
        return $this->table->column($columnName)->type(self::INT_COLUMN);
124
    }
125
126 24
    public function addStringColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
127 24
    {
128 16
        if ($this->fields->has($fieldName)) {
129 16
            if (!static::isStringType($this->fields->get($fieldName)->getType())) {
130
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type string.', $fieldName));
131
            }
132 16
            $this->validateColumnName($fieldName, $columnName);
133 16
            $this->fields->get($fieldName)->setGenerated($generated);
134 16
135
            return $this->table->column($columnName);
136 16
        }
137
138
        $field = (new Field())->setColumn($columnName)->setType('string')->setGenerated($generated);
139
        $this->fields->set($fieldName, $field);
140
141
        return $this->table->column($columnName)->type(self::STRING_COLUMN);
142 96
    }
143
144 96
    /**
145
     * @throws BehaviorCompilationException
146 96
     */
147 8
    public function addUuidColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn
148 8
    {
149 8
        if ($this->fields->has($fieldName)) {
150 8
            if (!static::isUuidType($this->fields->get($fieldName)->getType())) {
151
                throw new BehaviorCompilationException(\sprintf('Field %s must be of type uuid.', $fieldName));
152 8
            }
153
            $this->validateColumnName($fieldName, $columnName);
154
            $this->fields->get($fieldName)->setGenerated($generated);
155
156
            return $this->table->column($columnName);
157
        }
158
159 104
        $field = (new Field())->setColumn($columnName)->setType('uuid')->setGenerated($generated);
160
        $this->fields->set($fieldName, $field);
161 104
162
        return $this->table->column($columnName)->type(self::UUID_COLUMN);
163 64
    }
164 64
165
    public function findColumnName(string $fieldName, ?string $columnName): ?string
166
    {
167 40
        if ($columnName !== null) {
168 40
            return $columnName;
169
        }
170
171 40
        return $this->fields->has($fieldName) ? $this->fields->get($fieldName)->getColumn() : null;
172
    }
173
174
    /**
175
     * @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...
176
     */
177 40
    public function setTypecast(Field $field, array|string|null $rule, string $handler = Typecast::class): Field
178
    {
179
        if ($field->getTypecast() === null) {
180
            $field->setTypecast($rule);
181
        }
182
183
        $defaultHandlers = $this->defaults[SchemaInterface::TYPECAST_HANDLER] ?? [];
184
        if (!\is_array($defaultHandlers)) {
185
            $defaultHandlers = [$defaultHandlers];
186
        }
187
188
        $handlers = $this->entity->getTypecast() ?? [];
189
        if (!\is_array($handlers)) {
190
            $handlers = [$handlers];
191
        }
192
193
        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

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

194
            $this->entity->setTypecast(\array_merge(/** @scrutinizer ignore-type */ $handlers, [$handler]));
Loading history...
195
        }
196
197
        return $field;
198
    }
199
200
    /**
201
     * @throws BehaviorCompilationException
202
     */
203
    protected function validateColumnName(string $fieldName, string $columnName): void
204
    {
205
        $field = $this->fields->get($fieldName);
206
207
        if ($field->getColumn() !== $columnName) {
208
            throw new BehaviorCompilationException(
209
                \sprintf(
210
                    'Ambiguous column name definition. '
211
                    . 'The `%s` field already linked with the `%s` column but the behavior expects `%s`.',
212
                    $fieldName,
213
                    $field->getColumn(),
214
                    $columnName,
215
                ),
216
            );
217
        }
218
    }
219
220
    /**
221
     * @deprecated since v1.2
222
     */
223
    protected function isType(string $type, string $fieldName, string $columnName): bool
224
    {
225
        if ($type === self::DATETIME_COLUMN) {
226
            return
227
                $this->table->column($columnName)->getInternalType() === self::DATETIME_COLUMN ||
228
                $this->fields->get($fieldName)->getType() === self::DATETIME_COLUMN;
229
        }
230
231
        if ($type === self::INT_COLUMN) {
232
            return $this->table->column($columnName)->getType() === self::INT_COLUMN;
233
        }
234
235
        if ($type === self::UUID_COLUMN) {
236
            return
237
                $this->table->column($columnName)->getInternalType() === self::UUID_COLUMN ||
238
                $this->fields->get($fieldName)->getType() === self::UUID_COLUMN;
239
        }
240
241
        return $this->table->column($columnName)->getType() === $type;
242
    }
243
}
244