Passed
Push — master ( c12f94...3e1cf8 )
by Anton
03:42
created

ColumnSchema   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 179
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 54
dl 0
loc 179
rs 10
c 0
b 0
f 0
wmc 27

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getDefault() 0 7 2
A getType() 0 3 1
A render() 0 23 5
A castDefault() 0 21 6
A hasOption() 0 3 1
A parse() 0 15 3
A getName() 0 3 1
A isNullable() 0 7 4
A hasDefault() 0 7 2
A isPrimary() 0 3 2
1
<?php
2
declare(strict_types=1);
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
namespace Cycle\Schema\Table;
11
12
use Cycle\Schema\Definition\Field;
13
use Cycle\Schema\Exception\ColumnException;
14
use Spiral\Database\Schema\AbstractColumn;
15
16
/**
17
 * Carries information about column definition.
18
 *
19
 * @internal
20
 */
21
final class ColumnSchema
22
{
23
    // default column value
24
    public const OPT_DEFAULT = 'default';
25
26
    // column can automatically define default value
27
    public const OPT_CAST_DEFAULT = 'castDefault';
28
29
    // column can be nullable
30
    public const OPT_NULLABLE = 'nullable';
31
32
    // provides ability to define complex types using string notation, i.e. string(32)
33
    private const DEFINITION = '/(?P<type>[a-z]+)(?: *\((?P<options>[^\)]+)\))?/i';
34
35
    /** @var Field */
36
    private $field;
37
38
    /** @var string */
39
    private $type;
40
41
    /** @var array */
42
    private $typeOptions = [];
43
44
    /**
45
     * @return string
46
     */
47
    public function getName(): string
48
    {
49
        return $this->field->getColumn();
50
    }
51
52
    /**
53
     * Get column type.
54
     *
55
     * @return string
56
     */
57
    public function getType(): string
58
    {
59
        return $this->type;
60
    }
61
62
    /**
63
     * @return bool
64
     */
65
    public function isPrimary(): bool
66
    {
67
        return $this->field->isPrimary() || in_array($this->type, ['primary', 'bigPrimary']);
68
    }
69
70
    /**
71
     * @return bool
72
     */
73
    public function isNullable(): bool
74
    {
75
        if ($this->hasDefault() && $this->getDefault() === null) {
76
            return true;
77
        }
78
79
        return $this->hasOption(self::OPT_NULLABLE) && !$this->isPrimary();
80
    }
81
82
    /**
83
     * @return bool
84
     */
85
    public function hasDefault(): bool
86
    {
87
        if ($this->isPrimary()) {
88
            return false;
89
        }
90
91
        return $this->hasOption(self::OPT_DEFAULT);
92
    }
93
94
    /**
95
     * @return mixed
96
     *
97
     * @throws ColumnException
98
     */
99
    public function getDefault()
100
    {
101
        if (!$this->hasDefault()) {
102
            throw new ColumnException("No default value on `{$this->field->getColumn()}`");
103
        }
104
105
        return $this->field->getOptions()->get(self::OPT_DEFAULT);
106
    }
107
108
    /**
109
     * Render column definition.
110
     *
111
     * @param AbstractColumn $column
112
     *
113
     * @throws ColumnException
114
     */
115
    public function render(AbstractColumn $column)
116
    {
117
        $column->nullable($this->isNullable());
118
119
        try {
120
            // bypassing call to AbstractColumn->__call method (or specialized column method)
121
            call_user_func_array([$column, $this->type], $this->typeOptions);
122
        } catch (\Throwable $e) {
123
            throw new ColumnException(
124
                "Invalid column type definition in '{$column->getTable()}'.'{$column->getName()}'",
125
                $e->getCode(),
126
                $e
127
            );
128
        }
129
130
        if ($this->hasDefault() && $this->getDefault() !== null) {
131
            $column->defaultValue($this->getDefault());
132
            return;
133
        }
134
135
        if ($this->hasOption(self::OPT_CAST_DEFAULT)) {
136
            // cast default value
137
            $column->defaultValue($this->castDefault($column));
138
        }
139
    }
140
141
    /**
142
     * @param AbstractColumn $column
143
     * @return bool|float|int|string
144
     */
145
    private function castDefault(AbstractColumn $column)
146
    {
147
        if (in_array($column->getAbstractType(), ['timestamp', 'datetime', 'time', 'date'])) {
148
            return 0;
149
        }
150
151
        if ($column->getAbstractType() == 'enum') {
152
            // we can use first enum value as default
153
            return $column->getEnumValues()[0];
154
        }
155
156
        switch ($column->getType()) {
157
            case AbstractColumn::INT:
158
                return 0;
159
            case AbstractColumn::FLOAT:
160
                return 0.0;
161
            case AbstractColumn::BOOL:
162
                return false;
163
        }
164
165
        return '';
166
    }
167
168
    /**
169
     * @param string $option
170
     * @return bool
171
     */
172
    private function hasOption(string $option): bool
173
    {
174
        return $this->field->getOptions()->has($option);
175
    }
176
177
    /**
178
     * Parse field definition into table definition.
179
     *
180
     * @param Field $field
181
     * @return ColumnSchema
182
     *
183
     * @throws ColumnException
184
     */
185
    public static function parse(Field $field): self
186
    {
187
        $column = new ColumnSchema();
188
        $column->field = $field;
189
190
        if (!preg_match(self::DEFINITION, $field->getType(), $type)) {
191
            throw new ColumnException("Invalid column type definition in `{$field->getType()}`");
192
        }
193
194
        $column->type = $type['type'];
195
        if (!empty($type['options'])) {
196
            $column->typeOptions = array_map('trim', explode(',', $type['options'] ?? ''));
197
        }
198
199
        return $column;
200
    }
201
}