Completed
Branch feature/pre-split (60f5c0)
by Anton
03:19
created

ColumnRenderer::castDefault()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 16
nc 6
nop 1
dl 0
loc 25
rs 8.439
c 0
b 0
f 0
1
<?php
2
/**
3
 * components
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\ORM\Helpers;
8
9
use Spiral\Database\Schemas\Prototypes\AbstractColumn;
10
use Spiral\Database\Schemas\Prototypes\AbstractTable;
11
use Spiral\ORM\Exceptions\ColumnRenderException;
12
use Spiral\ORM\Exceptions\DefinitionException;
13
14
/**
15
 * Implements ability to define column in AbstractSchema based on string representation and default
16
 * value (if defined).
17
 */
18
class ColumnRenderer
19
{
20
    /**
21
     * Render columns in table based on string definition.
22
     *
23
     * Example:
24
     * renderColumns([
25
     *    'id'     => 'primary',
26
     *    'time'   => 'datetime, nullable',
27
     *    'status' => 'enum(active, disabled)'
28
     * ], [
29
     *    'status' => 'active',
30
     *    'time'   => null, //idential as if define column as null
31
     * ], $table);
32
     *
33
     * Attention, new table instance will be returned!
34
     *
35
     * @param array         $fields
36
     * @param array         $defaults
37
     * @param AbstractTable $table
38
     *
39
     * @return AbstractTable
40
     *
41
     * @throws ColumnRenderException
42
     */
43
    public function renderColumns(
44
        array $fields,
45
        array $defaults,
46
        AbstractTable $table
47
    ): AbstractTable {
48
        foreach ($fields as $name => $definition) {
49
            $column = $table->column($name);
50
51
            //Declaring our column
52
            $this->declareColumn(
53
                $column,
54
                $definition,
55
                array_key_exists($name, $defaults),
56
                $defaults[$name] ?? null
57
            );
58
        }
59
60
        return clone $table;
61
    }
62
63
    /**
64
     * Cast (specify) column schema based on provided column definition and default value.
65
     * Spiral will force default values (internally) for every NOT NULL column except primary keys!
66
     *
67
     * Column definition are compatible with database Migrations and AbstractColumn types.
68
     *
69
     * Column definition examples (by default all columns has flag NOT NULL):
70
     * protected $schema = [
71
     *      'id'           => 'primary',
72
     *      'name'         => 'string',                          //Default length is 255 characters.
73
     *      'email'        => 'string(255), nullable',           //Can be NULL
74
     *      'status'       => 'enum(active, pending, disabled)', //Enum values, trimmed
75
     *      'balance'      => 'decimal(10, 2)',
76
     *      'message'      => 'text, null',                      //Alias for nullable
77
     *      'time_expired' => 'timestamp'
78
     * ];
79
     *
80
     * Attention, column state will be affected!
81
     *
82
     * @see AbstractColumn
83
     *
84
     * @param AbstractColumn $column
85
     * @param string         $definition
86
     * @param bool           $hasDefault Must be set to true if default value was set by user.
87
     * @param mixed          $default    Default value declared by record schema.
88
     *
89
     * @return mixed
90
     *
91
     * @throws DefinitionException
92
     * @throws \Spiral\Database\Exceptions\SchemaException
93
     */
94
    public function declareColumn(
95
        AbstractColumn $column,
96
        string $definition,
97
        bool $hasDefault,
98
        $default = null
99
    ) {
100
        //Expression used to declare column type, easy to read
101
        $pattern = '/(?P<type>[a-z]+)(?: *\((?P<options>[^\)]+)\))?(?: *, *(?P<nullable>null(?:able)?))?/i';
102
103
        if (!preg_match($pattern, $definition, $type)) {
104
            throw new DefinitionException(
105
                "Invalid column type definition in '{$this}'.'{$column->getName()}'"
106
            );
107
        }
108
109
        if (!empty($type['options'])) {
110
            //Exporting and trimming
111
            $type['options'] = array_map('trim', explode(',', $type['options']));
112
        }
113
114
        //ORM force EVERY column to NOT NULL state unless different is said
115
        $column->nullable(false);
116
117
        if (!empty($type['nullable'])) {
118
            //Indication that column is nullable
119
            $column->nullable(true);
120
        }
121
122
        //Bypassing call to AbstractColumn->__call method (or specialized column method)
123
        call_user_func_array(
124
            [$column, $type['type']],
125
            !empty($type['options']) ? $type['options'] : []
126
        );
127
128
        if (in_array($column->abstractType(), ['primary', 'bigPrimary'])) {
129
            //No default value can be set of primary keys
130
            return $column;
131
        }
132
        if (!$hasDefault && !$column->isNullable()) {
133
            //We have to come up with some default value
134
            return $column->defaultValue($this->castDefault($column));
135
        }
136
137
        if (is_null($default)) {
138
            //Default value is stated and NULL, clear what to do
139
            $column->nullable(true);
140
        }
141
142
        return $column->defaultValue($default);
143
    }
144
145
    /**
146
     * Cast default value based on column type. Required to prevent conflicts when not nullable
147
     * column added to existed table with data in.
148
     *
149
     * @param AbstractColumn $column
150
     *
151
     * @return mixed
152
     */
153
    public function castDefault(AbstractColumn $column)
154
    {
155
        if (in_array($column->abstractType(), ['timestamp', 'datetime', 'time', 'date'])) {
156
            return 0;
157
        }
158
159
        if ($column->abstractType() == 'enum') {
160
            //We can use first enum value as default
161
            return $column->getEnumValues()[0];
162
        }
163
164
        switch ($column->phpType()) {
165
            case 'int':
1 ignored issue
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
166
                return 0;
167
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
168
            case 'float':
1 ignored issue
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
169
                return 0.0;
170
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
171
            case 'bool':
172
                return false;
173
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
174
        }
175
176
        return '';
177
    }
178
}