ColumnRenderer::declareColumn()   C
last analyzed

Complexity

Conditions 12
Paths 22

Size

Total Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 68
rs 6.2714
c 0
b 0
f 0
cc 12
nc 22
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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