SQL::getSQLType()   D
last analyzed

Complexity

Conditions 36
Paths 73

Size

Total Lines 56
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 42.0036

Importance

Changes 0
Metric Value
cc 36
eloc 48
c 0
b 0
f 0
nc 73
nop 1
dl 0
loc 56
ccs 40
cts 48
cp 0.8333
crap 42.0036
rs 4.1666

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
 * This file is part of the Divergence package.
4
 *
5
 * (c) Henry Paradiz <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Divergence\IO\Database;
12
13
use Exception;
14
15
/**
16
 * SQL.
17
 * @package Divergence
18
 * @author  Henry Paradiz <[email protected]>
19
 * @author  Chris Alfano <[email protected]>
20
 *
21
 */
22
class SQL
23
{
24
    protected static $aggregateFieldConfigs;
25
26
    /**
27
     * This is how MySQL escapes it's string under the hood.
28
     * Keep it. We don't need a database connection to escape strings.
29
     *
30
     * @param string $str String to escape.
31
     * @return string Escaped string.
32
     */
33 5
    public static function escape($str)
34
    {
35 5
        return str_replace(
36 5
            ["\\",  "\x00", "\n",  "\r",  "'",  '"', "\x1a"],
37 5
            ["\\\\","\\0","\\n", "\\r", "\'", '\"', "\\Z"],
38 5
            $str
39 5
        );
40
    }
41
42 4
    public static function compileFields($recordClass, $historyVariant = false)
43
    {
44 4
        $queryString = [];
45 4
        $fields = static::getAggregateFieldOptions($recordClass);
46
47 4
        foreach ($fields as $fieldId => $field) {
48 4
            if ($field['columnName'] == 'RevisionID') {
49 3
                continue;
50
            }
51
52 4
            $queryString[] = static::getFieldDefinition($recordClass, $fieldId, $historyVariant);
53
54 4
            if (!empty($field['primary'])) {
55 4
                if ($historyVariant) {
56 2
                    $queryString[] = 'KEY `'.$field['columnName'].'` (`'.$field['columnName'].'`)';
57
                } else {
58 3
                    $queryString[] = 'PRIMARY KEY (`'.$field['columnName'].'`)';
59
                }
60
            }
61
62 4
            if (!empty($field['unique']) && !$historyVariant) {
63
                $queryString[] = 'UNIQUE KEY `'.$field['columnName'].'` (`'.$field['columnName'].'`)';
64
            }
65
66 4
            if (!empty($field['index']) && !$historyVariant) {
67
                $queryString[] = 'KEY `'.$field['columnName'].'` (`'.$field['columnName'].'`)';
68
            }
69
        }
70
71 4
        return $queryString;
72
    }
73
74 3
    public static function getFullTextColumns($recordClass)
75
    {
76 3
        $fulltextColumns = [];
77 3
        $fields = static::getAggregateFieldOptions($recordClass);
78
79 3
        foreach ($fields as $fieldId => $field) {
80 3
            if ($field['columnName'] == 'RevisionID') {
81 2
                continue;
82
            }
83
84 3
            if (!empty($field['fulltext'])) {
85
                $fulltextColumns[] = $field['columnName'];
86
            }
87
        }
88
89 3
        return $fulltextColumns;
90
    }
91
92 3
    public static function getContextIndex($recordClass)
93
    {
94 3
        return 'KEY `CONTEXT` (`'.$recordClass::getColumnName('ContextClass').'`,`'.$recordClass::getColumnName('ContextID').'`)';
95
    }
96
97
    /**
98
     * Generates a MySQL create table query from a Divergence\Models\ActiveRecord class.
99
     *
100
     * @param string $recordClass Class name
101
     * @param boolean $historyVariant
102
     * @return string
103
     */
104 4
    public static function getCreateTable($recordClass, $historyVariant = false)
105
    {
106 4
        $indexes = $historyVariant ? [] : $recordClass::$indexes;
0 ignored issues
show
Bug introduced by
The property indexes does not exist on string.
Loading history...
107 4
        $fulltextColumns = [];
108 4
        $queryString = [];
109
110
111
        // history table revisionID field
112 4
        if ($historyVariant) {
113 2
            $queryString[] = '`RevisionID` int(10) unsigned NOT NULL auto_increment';
114 2
            $queryString[] = 'PRIMARY KEY (`RevisionID`)';
115
        }
116
117 4
        $queryString = array_merge($queryString, static::compileFields($recordClass, $historyVariant));
118
119 4
        if (!$historyVariant) {
120
            // If ContextClass && ContextID are members of this model let's index them
121 3
            if ($recordClass::fieldExists('ContextClass') && $recordClass::fieldExists('ContextID')) {
122 3
                $queryString[] = static::getContextIndex($recordClass);
123
            }
124
125 3
            $fulltextColumns = static::getFullTextColumns($recordClass);
126
        }
127
128
        // compile indexes
129 4
        foreach ($indexes as $indexName => $index) {
130
131
            // translate field names
132 2
            foreach ($index['fields'] as &$indexField) {
133 2
                $indexField = $recordClass::getColumnName($indexField);
134
            }
135
136 2
            if (!empty($index['fulltext'])) {
137
                $fulltextColumns = array_unique(array_merge($fulltextColumns, $index['fields']));
138
                continue;
139
            }
140
141 2
            $queryString[] = sprintf(
142 2
                '%s KEY `%s` (`%s`)',
143 2
                !empty($index['unique']) ? 'UNIQUE' : '',
144 2
                $indexName,
145 2
                join('`,`', $index['fields'])
146 2
            );
147
        }
148
149 4
        if (!empty($fulltextColumns)) {
150
            $queryString[] = 'FULLTEXT KEY `FULLTEXT` (`'.join('`,`', $fulltextColumns).'`)';
151
        }
152
153
154 4
        $createSQL = sprintf(
155 4
            "CREATE TABLE IF NOT EXISTS `%s` (\n\t%s\n) ENGINE=MyISAM DEFAULT CHARSET=utf8;",
156 4
            $historyVariant ? $recordClass::getHistoryTable() : $recordClass::$tableName,
0 ignored issues
show
Bug introduced by
The property tableName does not exist on string.
Loading history...
157 4
            join("\n\t,", $queryString)
158 4
        );
159
160
        // append history table SQL
161 4
        if (!$historyVariant && is_subclass_of($recordClass, 'VersionedRecord')) {
162
            $createSQL .= PHP_EOL.PHP_EOL.PHP_EOL.static::getCreateTable($recordClass, true);
163
        }
164 4
        return $createSQL;
165
    }
166
167 4
    public static function getSQLType($field)
168
    {
169 4
        switch ($field['type']) {
170 4
            case 'boolean':
171 3
                return 'boolean';
172 4
            case 'tinyint':
173 4
            case 'smallint':
174 4
            case 'mediumint':
175 4
            case 'bigint':
176
                return $field['type'].($field['unsigned'] ? ' unsigned' : '').($field['zerofill'] ? ' zerofill' : '');
177 4
            case 'uint':
178 3
                $field['unsigned'] = true;
179
                // no break
180 4
            case 'int':
181 4
            case 'integer':
182 4
                return 'int'.($field['unsigned'] ? ' unsigned' : '').(!empty($field['zerofill']) ? ' zerofill' : '');
183 4
            case 'decimal':
184 4
                return sprintf('decimal(%s,%s)', $field['precision'], $field['scale']);
185 4
            case 'float':
186 3
                return 'float';
187 4
            case 'double':
188
                return 'double';
189
190 4
            case 'password':
191 4
            case 'string':
192 4
            case 'varchar':
193 4
            case 'list':
194 4
                return sprintf(!$field['length'] || $field['type'] == 'varchar' ? 'varchar(%u)' : 'char(%u)', $field['length'] ? $field['length'] : 255);
195 4
            case 'clob':
196 4
            case 'serialized':
197 4
            case 'json':
198 3
                return 'text';
199 4
            case 'blob':
200
                return 'blob';
201 4
            case 'binary':
202
                return sprintf('binary(%s)', isset($field['length']) ? $field['length'] : 1);
203
204 4
            case 'timestamp':
205 4
                return 'timestamp';
206 4
            case 'datetime':
207
                return 'datetime';
208 4
            case 'time':
209
                return 'time';
210 4
            case 'date':
211 3
                return 'date';
212 4
            case 'year':
213
                return 'year';
214
215 4
            case 'enum':
216 4
                return sprintf('enum("%s")', join('","', array_map([static::class,'escape'], $field['values'])));
217
218 3
            case 'set':
219 3
                return sprintf('set("%s")', join('","', array_map([static::class,'escape'], $field['values'])));
220
221
            default:
222
                throw new Exception("getSQLType: unhandled type $field[type]");
223
        }
224
    }
225
226 4
    public static function getFieldDefinition($recordClass, $fieldName, $historyVariant = false)
227
    {
228 4
        $field = static::getAggregateFieldOptions($recordClass, $fieldName);
229 4
        $rootClass = $recordClass::$rootClass;
230
231
        // force notnull=false on non-rootclass fields
232 4
        if ($rootClass && !$rootClass::fieldExists($fieldName)) {
233
            $field['notnull'] = false;
234
        }
235
236
        // auto-prepend class type
237 4
        if ($field['columnName'] == 'Class' && $field['type'] == 'enum' && !in_array($rootClass, $field['values']) && !count($rootClass::getStaticSubClasses())) {
238
            array_unshift($field['values'], $rootClass);
239
        }
240
241 4
        $fieldDef = '`'.$field['columnName'].'`';
242 4
        $fieldDef .= ' '.static::getSQLType($field);
243
244 4
        if (!empty($field['charset'])) {
245
            $fieldDef .= " CHARACTER SET $field[charset]";
246
        }
247
248 4
        if (!empty($field['collate'])) {
249
            $fieldDef .= " COLLATE $field[collate]";
250
        }
251
252 4
        $fieldDef .= ' '.($field['notnull'] ? 'NOT NULL' : 'NULL');
253
254 4
        if ($field['autoincrement'] && !$historyVariant) {
255 3
            $fieldDef .= ' auto_increment';
256 4
        } elseif (($field['type'] == 'timestamp') && ($field['default'] == 'CURRENT_TIMESTAMP')) {
257 4
            $fieldDef .= ' default CURRENT_TIMESTAMP';
258 4
        } elseif (empty($field['notnull']) && ($field['default'] == null)) {
259 4
            $fieldDef .= ' default NULL';
260 4
        } elseif (isset($field['default'])) {
261 3
            if ($field['type'] == 'boolean') {
262 3
                $fieldDef .= ' default '.($field['default'] ? 1 : 0);
263
            } else {
264 3
                $fieldDef .= ' default "'.static::escape($field['default']).'"';
265
            }
266
        }
267
268 4
        return $fieldDef;
269
    }
270
271 4
    protected static function getAggregateFieldOptions($recordClass, $field = null)
272
    {
273 4
        if (!isset(static::$aggregateFieldConfigs[$recordClass])) {
274 2
            static::$aggregateFieldConfigs[$recordClass] = $recordClass::getClassFields();
275
        }
276
277 4
        if ($field) {
278 4
            return static::$aggregateFieldConfigs[$recordClass][$field];
279
        } else {
280 4
            return static::$aggregateFieldConfigs[$recordClass];
281
        }
282
    }
283
}
284