Completed
Push — master ( b71ad6...2211ef )
by Ivan
08:51
created

Driver::table()   D

Complexity

Conditions 22
Paths 4

Size

Total Lines 160
Code Lines 108

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 160
rs 4.6625
cc 22
eloc 108
nc 4
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
class Driver extends DriverAbstract implements DriverInterface
14
{
15
    protected $connection;
16
    protected $lnk = null;
17
18
    public function __construct(array $connection)
19
    {
20
        $this->connection = $connection;
21
        if (!isset($this->connection['port'])) {
22
            $this->connection['port'] = 3306;
23
        }
24
        if (!isset($this->connection['opts'])) {
25
            $this->connection['opts'] = [];
26
        }
27
        if (!isset($this->connection['opts']['charset'])) {
28
            $this->connection['opts']['charset'] = 'UTF8';
29
        }
30
    }
31
    public function __destruct()
32
    {
33
        $this->disconnect();
34
    }
35
    protected function connect()
36
    {
37
        if ($this->lnk === null) {
38
            $this->lnk = new \mysqli(
39
                (isset($this->connection['opts']['persist']) && $this->connection['opts']['persist'] ? 'p:' : '') .
40
                    $this->connection['host'],
41
                $this->connection['user'],
42
                $this->connection['pass'],
43
                $this->connection['name'],
44
                $this->connection['port']
45
            );
46
            if ($this->lnk->connect_errno) {
47
                throw new DBException('Connect error: '.$this->lnk->connect_errno);
48
            }
49
            if (!$this->lnk->set_charset($this->connection['opts']['charset'])) {
50
                throw new DBException('Charset error: '.$this->lnk->connect_errno);
51
            }
52
            if (isset($this->connection['opts']['timezone'])) {
53
                @$this->lnk->query("SET time_zone = '".addslashes($this->connection['opts']['timezone'])."'");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
54
            }
55
        }
56
    }
57
    protected function disconnect()
58
    {
59
        if ($this->lnk !== null) {
60
            @$this->lnk->close();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
61
        }
62
    }
63
    public function prepare(string $sql) : StatementInterface
64
    {
65
        $this->connect();
66
        $temp = $this->lnk->prepare($sql);
67
        if (!$temp) {
68
            throw new DBException('Could not prepare : '.$this->lnk->error.' <'.$sql.'>');
69
        }
70
        return new Statement($temp);
71
    }
72
73
    public function begin() : bool
74
    {
75
        $this->connect();
76
        return $this->lnk->begin_transaction();
77
    }
78
    public function commit() : bool
79
    {
80
        $this->connect();
81
        return $this->lnk->commit();
82
    }
83
    public function rollback() : bool
84
    {
85
        $this->connect();
86
        return $this->lnk->rollback();
87
    }
88
89
    public function table(string $table, bool $detectRelations = true) : Table
90
    {
91
        static $tables = [];
92
        if (isset($tables[$table])) {
93
            return $tables[$table];
94
        }
95
        
96
        $columns = Collection::from($this->query("SHOW FULL COLUMNS FROM {$table}"));
97
        if (!count($columns)) {
98
            throw new DBException('Table not found by name');
99
        }
100
        $tables[$table] = $definition = (new Table($table))
101
            ->addColumns(
102
                $columns
103
                    ->clone()
104
                    ->mapKey(function ($v) { return $v['Field']; })
105
                    ->toArray()
106
            )
107
            ->setPrimaryKey(
108
                $columns
109
                    ->clone()
110
                    ->filter(function ($v) { return $v['Key'] === 'PRI'; })
111
                    ->pluck('Field')
112
                    ->toArray()
113
            )
114
            ->setComment(
115
                (string)Collection::from($this
116
                    ->query(
117
                        "SELECT table_comment FROM information_schema.tables WHERE table_schema = ? AND table_name = ?",
118
                        [ $this->connection['name'], $table ]
119
                    ))
120
                    ->pluck('table_comment')
121
                    ->value()
122
            );
123
124
        if ($detectRelations) {
125
            // relations where the current table is referenced
126
            // assuming current table is on the "one" end having "many" records in the referencing table
127
            // resulting in a "hasMany" or "manyToMany" relationship (if a pivot table is detected)
128
            $relations = [];
129
            foreach (
130
                $this
131
                    ->query(
132
                        "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_COLUMN_NAME
133
                         FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
134
                         WHERE TABLE_SCHEMA = ? AND REFERENCED_TABLE_SCHEMA = ? AND REFERENCED_TABLE_NAME = ?",
135
                        [ $this->connection['name'], $this->connection['name'], $table ]
136
                    ) as $relation
137
            ) {
138
                $relations[$relation['CONSTRAINT_NAME']]['table'] = $relation['TABLE_NAME'];
139
                $relations[$relation['CONSTRAINT_NAME']]['keymap'][$relation['REFERENCED_COLUMN_NAME']] = $relation['COLUMN_NAME'];
140
            }
141
            foreach ($relations as $data) {
142
                $rtable = $this->table($data['table'], true);
143
                $columns = [];
144
                foreach ($rtable->getColumns() as $column) {
145
                    if (!in_array($column, $data['keymap'])) {
146
                        $columns[] = $column;
147
                    }
148
                }
149
                $foreign = [];
150
                $usedcol = [];
151
                if (count($columns)) {
152
                    foreach (Collection::from($this
153
                        ->query(
154
                            "SELECT
155
                                 TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME,
156
                                 REFERENCED_COLUMN_NAME, REFERENCED_TABLE_NAME
157
                             FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
158
                             WHERE
159
                                 TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME IN (??) AND
160
                                 REFERENCED_TABLE_NAME IS NOT NULL",
161
                            [ $this->connection['name'], $data['table'], $columns ]
162
                        ))
163
                        ->map(function ($v) {
164
                            $new = [];
165
                            foreach ($v as $kk => $vv) {
166
                                $new[strtoupper($kk)] = $vv;
167
                            }
168
                            return $new;
169
                        }) as $relation
170
                    ) {
171
                        $foreign[$relation['CONSTRAINT_NAME']]['table'] = $relation['REFERENCED_TABLE_NAME'];
172
                        $foreign[$relation['CONSTRAINT_NAME']]['keymap'][$relation['COLUMN_NAME']] = $relation['REFERENCED_COLUMN_NAME'];
173
                        $usedcol[] = $relation['COLUMN_NAME'];
174
                    }
175
                }
176
                if (count($foreign) === 1 && !count(array_diff($columns, $usedcol))) {
177
                    $foreign = current($foreign);
178
                    $relname = $foreign['table'];
179
                    $cntr = 1;
180
                    while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
181
                        $relname = $foreign['table'] . '_' . (++ $cntr);
182
                    }
183
                    $definition->addRelation(
184
                        new TableRelation(
185
                            $relname,
186
                            $this->table($foreign['table'], true),
187
                            $data['keymap'],
188
                            true,
189
                            $rtable,
190
                            $foreign['keymap']
191
                        )
192
                    );
193
                } else {
194
                    $relname = $data['table'];
195
                    $cntr = 1;
196
                    while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
197
                        $relname = $data['table'] . '_' . (++ $cntr);
198
                    }
199
                    $definition->addRelation(
200
                        new TableRelation(
201
                            $relname,
202
                            $this->table($data['table'], true),
203
                            $data['keymap'],
204
                            true
205
                        )
206
                    );
207
                }
208
            }
209
            // relations where the current table references another table
210
            // assuming current table is linked to "one" record in the referenced table
211
            // resulting in a "belongsTo" relationship
212
            $relations = [];
213
            foreach (Collection::from($this
214
                ->query(
215
                    "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
216
                     FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
217
                     WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND REFERENCED_TABLE_NAME IS NOT NULL",
218
                    [ $this->connection['name'], $table ]
219
                ))
220
                ->map(function ($v) {
221
                    $new = [];
222
                    foreach ($v as $kk => $vv) {
223
                        $new[strtoupper($kk)] = $vv;
224
                    }
225
                    return $new;
226
                }) as $relation
227
            ) {
228
                $relations[$relation['CONSTRAINT_NAME']]['table'] = $relation['REFERENCED_TABLE_NAME'];
229
                $relations[$relation['CONSTRAINT_NAME']]['keymap'][$relation['COLUMN_NAME']] = $relation['REFERENCED_COLUMN_NAME'];
230
            }
231
            foreach ($relations as $name => $data) {
232
                $relname = $data['table'];
233
                $cntr = 1;
234
                while ($definition->hasRelation($relname) || $definition->getName() == $relname) {
235
                    $relname = $data['table'] . '_' . (++ $cntr);
236
                }
237
                $definition->addRelation(
238
                    new TableRelation(
239
                        $relname,
240
                        $this->table($data['table'], true),
241
                        $data['keymap'],
242
                        false
243
                    )
244
                );
245
            }
246
        }
247
        return $definition->toLowerCase();
248
    }
249
    public function tables() : array
250
    {
251
        return Collection::from($this
252
            ->query(
253
                "SELECT table_name FROM information_schema.tables where table_schema = ?",
254
                [$this->connection['name']]
255
            ))
256
            ->map(function ($v) {
257
                $new = [];
258
                foreach ($v as $kk => $vv) {
259
                    $new[strtoupper($kk)] = $vv;
260
                }
261
                return $new;
262
            })
263
            ->pluck('TABLE_NAME')
264
            ->map(function ($v) {
265
                return $this->table($v);
266
            })
267
            ->toArray();
268
    }
269
}