Completed
Push — master ( 407844...9daecf )
by Ivan
02:25
created

Schema::table()   F

Complexity

Conditions 34
Paths 13

Size

Total Lines 200

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 200
rs 3.3333
c 0
b 0
f 0
cc 34
nc 13
nop 2

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
namespace vakata\database\driver\mysql;
4
5
use \vakata\database\DBException;
6
use \vakata\database\DriverInterface;
7
use \vakata\database\DriverAbstract;
8
use \vakata\database\StatementInterface;
9
use \vakata\database\schema\Table;
10
use \vakata\database\schema\TableRelation;
11
use \vakata\collection\Collection;
12
13
trait Schema
14
{
15
    public function table(string $table, bool $detectRelations = true) : Table
16
    {
17
        static $tables = [];
18
        if (isset($tables[$table])) {
19
            return $tables[$table];
20
        }
21
22
        static $comments = null;
23
        if (!isset($comments)) {
24
            $comments = Collection::from(
25
                $this->query(
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
26
                    "SELECT TABLE_NAME, TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?",
27
                    [ $this->connection['opts']['schema'] ?? $this->connection['name'] ]
0 ignored issues
show
Bug introduced by
The property connection does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
28
                )
29
            )
30
            ->mapKey(function ($v) {
31
                return $v['TABLE_NAME'];
32
            })
33
            ->pluck('TABLE_COMMENT')
34
            ->toArray();
35
        }
36
37
        static $relationsT = null;
38
        static $relationsR = null;
39
        if (!isset($relationsT) || !isset($relationsR)) {
40
            $relationsT = [];
41
            $relationsR = [];
42
            $col = Collection::from(
43
                $this->query(
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
44
                    "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
45
                     FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
46
                     WHERE
47
                        TABLE_SCHEMA = ? AND TABLE_NAME IS NOT NULL AND
48
                        REFERENCED_TABLE_SCHEMA = ? AND REFERENCED_TABLE_NAME IS NOT NULL",
49
                    [
50
                        $this->connection['opts']['schema'] ?? $this->connection['name'],
51
                        $this->connection['opts']['schema'] ?? $this->connection['name']
52
                    ]
53
                )
54
            )->toArray();
55
            foreach ($col as $row) {
56
                $relationsT[$row['TABLE_NAME']][] = $row;
57
                $relationsR[$row['REFERENCED_TABLE_NAME']][] = $row;
58
            }
59
        }
60
61
        
62
        $columns = Collection::from($this->query("SHOW FULL COLUMNS FROM {$table}"));
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
63
        if (!count($columns)) {
64
            throw new DBException('Table not found by name');
65
        }
66
        $tables[$table] = $definition = (new Table($table))
67
            ->addColumns(
68
                $columns
69
                    ->clone()
70
                    ->mapKey(function ($v) { return $v['Field']; })
71
                    ->map(function ($v) {
72
                        $v['length'] = null;
73
                        if (!isset($v['Type'])) {
74
                            return $v;
75
                        }
76
                        $type = strtolower($v['Type']);
77
                        switch ($type) {
78
                            case 'tinytext':
79
                                $v['length'] = 255;
80
                                break;
81
                            case 'text':
82
                                $v['length'] = 65535;
83
                                break;
84
                            case 'mediumtext':
85
                                $v['length'] = 16777215;
86
                                break;
87
                            case 'longtext':
88
                                // treat this as no limit
89
                                break;
90
                            default:
91
                                if (strpos($type, 'char') !== false && strpos($type, '(') !== false) {
92
                                    // extract length from varchar
93
                                    $v['length'] = (int)explode(')', explode('(', $type)[1])[0];
94
                                    $v['length'] = $v['length'] > 0 ? $v['length'] : null;
95
                                }
96
                                break;
97
                        }
98
                        return $v;
99
                    })
100
                    ->toArray()
101
            )
102
            ->setPrimaryKey(
103
                $columns
104
                    ->clone()
105
                    ->filter(function ($v) { return $v['Key'] === 'PRI'; })
106
                    ->pluck('Field')
107
                    ->toArray()
108
            )
109
            ->setComment($comments[$table] ?? '');
110
111
        if ($detectRelations) {
112
            // relations where the current table is referenced
113
            // assuming current table is on the "one" end having "many" records in the referencing table
114
            // resulting in a "hasMany" or "manyToMany" relationship (if a pivot table is detected)
115
            $relations = [];
116
            foreach ($relationsR[$table] ?? [] as $relation) {
117
                $relations[$relation['CONSTRAINT_NAME']]['table'] = $relation['TABLE_NAME'];
118
                $relations[$relation['CONSTRAINT_NAME']]['keymap'][$relation['REFERENCED_COLUMN_NAME']] = $relation['COLUMN_NAME'];
119
            }
120
            foreach ($relations as $data) {
121
                $rtable = $this->table($data['table'], true);
122
                $columns = [];
123
                foreach ($rtable->getColumns() as $column) {
124
                    if (!in_array($column, $data['keymap'])) {
125
                        $columns[] = $column;
126
                    }
127
                }
128
                $foreign = [];
129
                $usedcol = [];
130
                if (count($columns)) {
131
                    foreach (Collection::from($relationsT[$data['table']] ?? [])
132
                        ->filter(function ($v) use ($columns) {
133
                            return in_array($v['COLUMN_NAME'], $columns);
134
                        })
135
                        ->map(function ($v) {
136
                            $new = [];
137
                            foreach ($v as $kk => $vv) {
138
                                $new[strtoupper($kk)] = $vv;
139
                            }
140
                            return $new;
141
                        }) as $relation
142
                    ) {
143
                        $foreign[$relation['CONSTRAINT_NAME']]['table'] = $relation['REFERENCED_TABLE_NAME'];
144
                        $foreign[$relation['CONSTRAINT_NAME']]['keymap'][$relation['COLUMN_NAME']] = $relation['REFERENCED_COLUMN_NAME'];
145
                        $usedcol[] = $relation['COLUMN_NAME'];
146
                    }
147
                }
148
                if (count($foreign) === 1 && !count(array_diff($columns, $usedcol))) {
149
                    $foreign = current($foreign);
150
                    $relname = $foreign['table'];
151
                    $cntr = 1;
152
                    while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
153
                        $relname = $foreign['table'] . '_' . (++ $cntr);
154
                    }
155
                    $definition->addRelation(
156
                        new TableRelation(
157
                            $relname,
158
                            $this->table($foreign['table'], true),
159
                            $data['keymap'],
160
                            true,
161
                            $rtable,
162
                            $foreign['keymap']
163
                        )
164
                    );
165
                } else {
166
                    $relname = $data['table'];
167
                    $cntr = 1;
168
                    while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
169
                        $relname = $data['table'] . '_' . (++ $cntr);
170
                    }
171
                    $definition->addRelation(
172
                        new TableRelation(
173
                            $relname,
174
                            $this->table($data['table'], true),
175
                            $data['keymap'],
176
                            true
177
                        )
178
                    );
179
                }
180
            }
181
            // relations where the current table references another table
182
            // assuming current table is linked to "one" record in the referenced table
183
            // resulting in a "belongsTo" relationship
184
            $relations = [];
185
            foreach (Collection::from($relationsT[$table] ?? [])
186
                ->map(function ($v) {
187
                    $new = [];
188
                    foreach ($v as $kk => $vv) {
189
                        $new[strtoupper($kk)] = $vv;
190
                    }
191
                    return $new;
192
                }) as $relation
193
            ) {
194
                $relations[$relation['CONSTRAINT_NAME']]['table'] = $relation['REFERENCED_TABLE_NAME'];
195
                $relations[$relation['CONSTRAINT_NAME']]['keymap'][$relation['COLUMN_NAME']] = $relation['REFERENCED_COLUMN_NAME'];
196
            }
197
            foreach ($relations as $name => $data) {
198
                $relname = $data['table'];
199
                $cntr = 1;
200
                while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
201
                    $relname = $data['table'] . '_' . (++ $cntr);
202
                }
203
                $definition->addRelation(
204
                    new TableRelation(
205
                        $relname,
206
                        $this->table($data['table'], true),
207
                        $data['keymap'],
208
                        false
209
                    )
210
                );
211
            }
212
        }
213
        return $definition->toLowerCase();
214
    }
215
    public function tables() : array
216
    {
217
        return Collection::from($this
0 ignored issues
show
Bug introduced by
It seems like query() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
218
            ->query(
219
                "SELECT table_name FROM information_schema.tables where table_schema = ?",
220
                [$this->connection['opts']['schema'] ?? $this->connection['name']]
221
            ))
222
            ->map(function ($v) {
223
                $new = [];
224
                foreach ($v as $kk => $vv) {
225
                    $new[strtoupper($kk)] = $vv;
226
                }
227
                return $new;
228
            })
229
            ->pluck('TABLE_NAME')
230
            ->map(function ($v) {
231
                return $this->table($v);
232
            })
233
            ->toArray();
234
    }
235
}