Completed
Push — master ( a6d599...c90248 )
by Ivan
04:03
created

Table   C

Complexity

Total Complexity 67

Size/Duplication

Total Lines 456
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 29.95%

Importance

Changes 0
Metric Value
wmc 67
c 0
b 0
f 0
lcom 1
cbo 3
dl 0
loc 456
ccs 62
cts 207
cp 0.2995
rs 5.7097

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A getComment() 0 4 1
A setComment() 0 5 1
A addColumn() 0 5 1
A addColumns() 0 11 4
A setPrimaryKey() 0 8 2
A getName() 0 4 1
A getColumn() 0 4 1
A getColumns() 0 4 1
A getFullColumns() 0 4 1
A getPrimaryKey() 0 4 1
A addRelation() 0 7 1
A hasRelations() 0 4 1
A getRelations() 0 4 1
A hasRelation() 0 4 1
A getRelation() 0 4 1
A renameRelation() 0 14 3
B toLowerCase() 0 28 6
C hasOne() 0 46 8
C hasMany() 0 46 8
C belongsTo() 0 46 8
F manyToMany() 0 66 14

How to fix   Complexity   

Complex Class

Complex classes like Table often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Table, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace vakata\database\schema;
4
5
use \vakata\database\DBException;
6
7
/**
8
 * A table definition
9
 */
10
class Table
11
{
12
    protected $data = [];
13
    /**
14
     * @var TableRelation[]
15
     */
16
    protected $relations = [];
17
18
    /**
19
     * Create a new instance
20
     * @param  string      $name the table name
21
     */
22 1
    public function __construct(string $name)
23
    {
24 1
        $this->data = [
25 1
            'name'    => $name,
26
            'columns' => [],
27
            'primary' => [],
28 1
            'comment' => ''
29
        ];
30 1
    }
31
    /**
32
     * Get the table comment
33
     * @return string  the table comment
34
     */
35
    public function getComment()
36
    {
37
        return $this->data['comment'];
38
    }
39
    /**
40
     * Set the table comment
41
     * @param  string    $comment     the table comment
42
     * @return $this
43
     */
44 1
    public function setComment(string $comment)
45
    {
46 1
        $this->data['comment'] = $comment;
47 1
        return $this;
48
    }
49
    /**
50
     * Add a column to the definition
51
     * @param  string    $column     the column name
52
     * @param  array     $definition optional array of data associated with the column
53
     * @return  self
54
     */
55 1
    public function addColumn(string $column, array $definition = []) : Table
56
    {
57 1
        $this->data['columns'][$column] = TableColumn::fromArray($column, $definition);
58 1
        return $this;
59
    }
60
    /**
61
     * Add columns to the definition
62
     * @param  array      $columns key - value pairs, where each key is a column name and each value - array of info
63
     * @return  self
64
     */
65 1
    public function addColumns(array $columns) : Table
66
    {
67 1
        foreach ($columns as $column => $definition) {
68 1
            if (is_numeric($column) && is_string($definition)) {
69
                $this->addColumn($definition, []);
70
            } else {
71 1
                $this->addColumn($column, $definition);
72
            }
73
        }
74 1
        return $this;
75
    }
76
    /**
77
     * Set the primary key
78
     * @param  array|string        $column either a single column name or an array of column names
79
     * @return  self
80
     */
81 1
    public function setPrimaryKey($column) : Table
82
    {
83 1
        if (!is_array($column)) {
84
            $column = [ $column ];
85
        }
86 1
        $this->data['primary'] = $column;
87 1
        return $this;
88
    }
89
    /**
90
     * Get the table name
91
     * @return string  the table name
92
     */
93 12
    public function getName()
94
    {
95 12
        return $this->data['name'];
96
    }
97
    /**
98
     * Get a column definition
99
     * @param  string    $column the column name to search for
100
     * @return array|null the column details or `null` if the column does not exist
101
     */
102 12
    public function getColumn($column)
103
    {
104 12
        return $this->data['columns'][$column] ?? null;
105
    }
106
    /**
107
     * Get all column names
108
     * @return array     array of strings, where each element is a column name
109
     */
110 12
    public function getColumns()
111
    {
112 12
        return array_keys($this->data['columns']);
113
    }
114
    /**
115
     * Get all column definitions
116
     * @return array         key - value pairs, where each key is a column name and each value - the column data
117
     */
118 2
    public function getFullColumns()
119
    {
120 2
        return $this->data['columns'];
121
    }
122
    /**
123
     * Get the primary key columns
124
     * @return array        array of column names
125
     */
126 12
    public function getPrimaryKey()
127
    {
128 12
        return $this->data['primary'];
129
    }
130
    /**
131
     * Create a relation where each record has zero or one related rows in another table
132
     * @param  Table             $toTable       the related table definition
133
     * @param  string|null       $name          the name of the relation (defaults to the related table name)
134
     * @param  string|array|null $toTableColumn the remote columns pointing to the PK in the current table
135
     * @param  string|null       $sql           additional where clauses to use, default to null
136
     * @param  array|null        $par           parameters for the above statement, defaults to null
137
     * @return $this
138
     */
139
    public function hasOne(
140
        Table $toTable,
141
        string $name = null,
142
        $toTableColumn = null,
143
        string $sql = null,
144
        array $par = []
145
    ) : Table {
146
        $columns = $toTable->getColumns();
147
148
        $keymap = [];
149
        if (!isset($toTableColumn)) {
150
            $toTableColumn = [];
151
        }
152
        if (!is_array($toTableColumn)) {
153
            $toTableColumn = [$toTableColumn];
154
        }
155
        foreach ($this->getPrimaryKey() as $k => $pkField) {
156
            $key = null;
0 ignored issues
show
Unused Code introduced by
$key is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
157
            if (isset($toTableColumn[$pkField])) {
158
                $key = $toTableColumn[$pkField];
159
            } elseif (isset($toTableColumn[$k])) {
160
                $key = $toTableColumn[$k];
161
            } else {
162
                $key = $this->getName().'_'.$pkField;
163
            }
164
            if (!in_array($key, $columns)) {
165
                throw new DBException('Missing foreign key mapping');
166
            }
167
            $keymap[$pkField] = $key;
168
        }
169
170
        if (!isset($name)) {
171
            $name = $toTable->getName() . '_' . implode('_', array_keys($keymap));
172
        }
173
        $this->addRelation(new TableRelation(
174
            $name,
175
            $toTable,
176
            $keymap,
177
            false,
178
            null,
179
            null,
180
            $sql,
181
            $par
182
        ));
183
        return $this;
184
    }
185
    /**
186
     * Create a relation where each record has zero, one or more related rows in another table
187
     * @param  Table   $toTable       the related table definition
188
     * @param  string|null       $name          the name of the relation (defaults to the related table name)
189
     * @param  string|array|null $toTableColumn the remote columns pointing to the PK in the current table
190
     * @param  string|null       $sql           additional where clauses to use, default to null
191
     * @param  array|null        $par           parameters for the above statement, defaults to null
192
     * @return $this
193
     */
194
    public function hasMany(
195
        Table $toTable,
196
        string $name = null,
197
        $toTableColumn = null,
198
        $sql = null,
199
        array $par = []
200
    ) : Table {
201
        $columns = $toTable->getColumns();
202
203
        $keymap = [];
204
        if (!isset($toTableColumn)) {
205
            $toTableColumn = [];
206
        }
207
        if (!is_array($toTableColumn)) {
208
            $toTableColumn = [$toTableColumn];
209
        }
210
        foreach ($this->getPrimaryKey() as $k => $pkField) {
211
            $key = null;
0 ignored issues
show
Unused Code introduced by
$key is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
212
            if (isset($toTableColumn[$pkField])) {
213
                $key = $toTableColumn[$pkField];
214
            } elseif (isset($toTableColumn[$k])) {
215
                $key = $toTableColumn[$k];
216
            } else {
217
                $key = $this->getName().'_'.$pkField;
218
            }
219
            if (!in_array($key, $columns)) {
220
                throw new DBException('Missing foreign key mapping');
221
            }
222
            $keymap[$pkField] = $key;
223
        }
224
225
        if (!isset($name)) {
226
            $name = $toTable->getName().'_'.implode('_', array_keys($keymap));
227
        }
228
        $this->addRelation(new TableRelation(
229
            $name,
230
            $toTable,
231
            $keymap,
232
            true,
233
            null,
234
            null,
235
            $sql,
236
            $par
237
        ));
238
        return $this;
239
    }
240
    /**
241
     * Create a relation where each record belongs to another row in another table
242
     * @param  Table   $toTable       the related table definition
243
     * @param  string|null       $name          the name of the relation (defaults to the related table name)
244
     * @param  string|array|null $localColumn   the local columns pointing to the PK of the related table
245
     * @param  string|null       $sql           additional where clauses to use, default to null
246
     * @param  array|null        $par           parameters for the above statement, defaults to null
247
     * @return $this
248
     */
249
    public function belongsTo(
250
        Table $toTable,
251
        string $name = null,
252
        $localColumn = null,
253
        $sql = null,
254
        array $par = []
255
    ) : Table {
256
        $columns = $this->getColumns();
257
258
        $keymap = [];
259
        if (!isset($localColumn)) {
260
            $localColumn = [];
261
        }
262
        if (!is_array($localColumn)) {
263
            $localColumn = [$localColumn];
264
        }
265
        foreach ($toTable->getPrimaryKey() as $k => $pkField) {
266
            $key = null;
0 ignored issues
show
Unused Code introduced by
$key is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
267
            if (isset($localColumn[$pkField])) {
268
                $key = $localColumn[$pkField];
269
            } elseif (isset($localColumn[$k])) {
270
                $key = $localColumn[$k];
271
            } else {
272
                $key = $toTable->getName().'_'.$pkField;
273
            }
274
            if (!in_array($key, $columns)) {
275
                throw new DBException('Missing foreign key mapping');
276
            }
277
            $keymap[$key] = $pkField;
278
        }
279
280
        if (!isset($name)) {
281
            $name = $toTable->getName().'_'.implode('_', array_keys($keymap));
282
        }
283
        $this->addRelation(new TableRelation(
284
            $name,
285
            $toTable,
286
            $keymap,
287
            false,
288
            null,
289
            null,
290
            $sql,
291
            $par
292
        ));
293
        return $this;
294
    }
295
    /**
296
     * Create a relation where each record has many linked records in another table but using a liking table
297
     * @param  Table   $toTable       the related table definition
298
     * @param  Table   $pivot         the pivot table definition
299
     * @param  string|null       $name          the name of the relation (defaults to the related table name)
300
     * @param  string|array|null $toTableColumn the local columns pointing to the pivot table
301
     * @param  string|array|null $localColumn   the pivot columns pointing to the related table PK
302
     * @return $this
303
     */
304
    public function manyToMany(
305
        Table $toTable,
306
        Table $pivot,
307
        $name = null,
308
        $toTableColumn = null,
309
        $localColumn = null
310
    ) : Table {
311
        $pivotColumns = $pivot->getColumns();
312
313
        $keymap = [];
314
        if (!isset($toTableColumn)) {
315
            $toTableColumn = [];
316
        }
317
        if (!is_array($toTableColumn)) {
318
            $toTableColumn = [$toTableColumn];
319
        }
320
        foreach ($this->getPrimaryKey() as $k => $pkField) {
321
            $key = null;
0 ignored issues
show
Unused Code introduced by
$key is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
322
            if (isset($toTableColumn[$pkField])) {
323
                $key = $toTableColumn[$pkField];
324
            } elseif (isset($toTableColumn[$k])) {
325
                $key = $toTableColumn[$k];
326
            } else {
327
                $key = $this->getName().'_'.$pkField;
328
            }
329
            if (!in_array($key, $pivotColumns)) {
330
                throw new DBException('Missing foreign key mapping');
331
            }
332
            $keymap[$pkField] = $key;
333
        }
334
335
        $pivotKeymap = [];
336
        if (!isset($localColumn)) {
337
            $localColumn = [];
338
        }
339
        if (!is_array($localColumn)) {
340
            $localColumn = [$localColumn];
341
        }
342
        foreach ($toTable->getPrimaryKey() as $k => $pkField) {
343
            $key = null;
0 ignored issues
show
Unused Code introduced by
$key is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
344
            if (isset($localColumn[$pkField])) {
345
                $key = $localColumn[$pkField];
346
            } elseif (isset($localColumn[$k])) {
347
                $key = $localColumn[$k];
348
            } else {
349
                $key = $toTable->getName().'_'.$pkField;
350
            }
351
            if (!in_array($key, $pivotColumns)) {
352
                throw new DBException('Missing foreign key mapping');
353
            }
354
            $pivotKeymap[$key] = $pkField;
355
        }
356
357
        if (!isset($name)) {
358
            $name = $toTable->getName().'_'.implode('_', array_keys($keymap));
359
        }
360
        $this->addRelation(new TableRelation(
361
            $name,
362
            $toTable,
363
            $keymap,
364
            true,
365
            $pivot,
366
            $pivotKeymap
367
        ));
368
        return $this;
369
    }
370
    /**
371
     * Create an advanced relation using the internal array format
372
     * @param  TableRelation     $relation      the relation definition
373
     * @param  string|null       $name          optional name of the relation (defaults to the related table name)
374
     * @return $this
375
     */
376 1
    public function addRelation(TableRelation $relation, string $name = null)
377
    {
378 1
        $name = $name ?? $relation->name;
379 1
        $relation->name = $name;
380 1
        $this->relations[$name] = $relation;
381 1
        return $this;
382
    }
383
    /**
384
     * Does the definition have related tables
385
     * @return boolean
386
     */
387
    public function hasRelations() : bool
388
    {
389
        return count($this->relations) > 0;
390
    }
391
    /**
392
     * Get all relation definitions
393
     * @return TableRelation[]       the relation definitions
394
     */
395 12
    public function getRelations() : array
396
    {
397 12
        return $this->relations;
398
    }
399
    /**
400
     * Check if a named relation exists
401
     * @param  string      $name the name to search for
402
     * @return boolean           does the relation exist
403
     */
404 6
    public function hasRelation(string $name) : bool
405
    {
406 6
        return isset($this->relations[$name]);
407
    }
408
    /**
409
     * Get a relation by name
410
     * @param  string      $name      the name to search for
411
     * @return TableRelation|null     the relation definition
412
     */
413 4
    public function getRelation(string $name)
414
    {
415 4
        return $this->relations[$name] ?? null;
416
    }
417
    /**
418
     * Rename a relation
419
     * @param  string      $name the name to search for
420
     * @param  string      $new  the new name for the relation
421
     * @return TableRelation     the relation definition
422
     */
423
    public function renameRelation(string $name, string $new) : array
424
    {
425
        if (!isset($this->relations[$name])) {
426
            throw new DBException("Relation not found");
427
        }
428
        if (isset($this->relations[$new])) {
429
            throw new DBException("A relation with that name already exists");
430
        }
431
        $temp = $this->relations[$name];
432
        $temp->name = $new;
433
        $this->relations[$new] = $temp;
434
        unset($this->relations[$name]);
435
        return $this->relations[$new] ?? null;
436
    }
437 1
    public function toLowerCase()
438
    {
439 1
        $temp = [];
440 1
        foreach ($this->data['columns'] as $k => $v) {
441 1
            $temp[strtolower($k)] = $v;
442 1
            $v->setName(strtolower($k));
443
        }
444 1
        $this->data['columns'] = $temp;
445 1
        $this->data['primary'] = array_map("strtolower", $this->data['primary']);
446 1
        $temp = [];
447 1
        foreach ($this->relations as $k => $v) {
448 1
            $t = [];
449 1
            foreach ($v->keymap as $kk => $vv) {
450 1
                $t[strtolower($kk)] = strtolower($vv);
451
            }
452 1
            $v->keymap = $t;
453 1
            if ($v->pivot_keymap) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $v->pivot_keymap of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
454 1
                $t = [];
455 1
                foreach ($v->pivot_keymap as $kk => $vv) {
456 1
                    $t[strtolower($kk)] = strtolower($vv);
457
                }
458 1
                $v->pivot_keymap = $t;
459
            }
460 1
            $temp[strtolower($k)] = $v;
461
        }
462 1
        $this->relations = $temp;
463 1
        return $this;
464
    }
465
}
466