Passed
Push — master ( d62e11...3859d4 )
by Anton
01:41
created

Column::getDefault()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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