Completed
Pull Request — master (#21)
by Michal
02:30
created

MySqlDataManager::deleteDatabase()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 0
cts 3
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 2
1
<?php
2
3
namespace UniMan\Drivers\MySql;
4
5
use UniMan\Core\DataManager\AbstractDataManager;
6
use UniMan\Core\Exception\OperatorNotSupportedException;
7
use UniMan\Core\Utils\Filter;
8
use UniMan\Core\Utils\Multisort;
9
use InvalidArgumentException;
10
use Nette\Utils\Strings;
11
use PDO;
12
13
class MySqlDataManager extends AbstractDataManager
14
{
15
    private $columns = null;
16
17
    private $connection;
18
19
    private $database;
20
21
    /**
22
     * cache
23
     * @var array|null
24
     */
25
    private $databases = null;
26
27 6
    public function __construct(PDO $connection)
28
    {
29 6
        $this->connection = $connection;
30 6
    }
31
32
    public function databases(array $sorting = [])
33
    {
34
        if ($this->databases !== null) {
35
            return $this->databases;
36
        }
37
38
        $query = 'SELECT information_schema.SCHEMATA.*, count(information_schema.TABLES.TABLE_NAME) AS tables_count, SUM(information_schema.TABLES.DATA_LENGTH) AS size
39
FROM information_schema.SCHEMATA
40
LEFT JOIN information_schema.TABLES ON information_schema.SCHEMATA.SCHEMA_NAME = information_schema.TABLES.TABLE_SCHEMA
41
GROUP BY information_schema.SCHEMATA.SCHEMA_NAME
42
ORDER BY information_schema.SCHEMATA.SCHEMA_NAME';
43
44
        $databases = [];
45
        foreach ($this->connection->query($query)->fetchAll(PDO::FETCH_ASSOC) as $database) {
46
            $databases[$database['SCHEMA_NAME']] = [
47
                'database' => $database['SCHEMA_NAME'],
48
                'charset' => $database['DEFAULT_CHARACTER_SET_NAME'],
49
                'collation' => $database['DEFAULT_COLLATION_NAME'],
50
                'tables_count' => $database['tables_count'],
51
                'size' => $database['size'] ?: 0,
52
            ];
53
        }
54
        $this->databases = $databases;
55
        return Multisort::sort($databases, $sorting);
56
    }
57
58
    public function tables(array $sorting = [])
59
    {
60
        $tables = [
61
            MySqlDriver::TYPE_TABLE => [],
62
            MySqlDriver::TYPE_VIEW => [],
63
        ];
64
        $type = 'BASE TABLE';
65
        if ($this->database == 'information_schema') {
66
            $type = 'SYSTEM VIEW';
67
        }
68
69
        foreach ($this->connection->query("SELECT * FROM information_schema.TABLES WHERE TABLE_TYPE = '$type' AND TABLE_SCHEMA = '{$this->database}' ORDER BY TABLE_NAME")->fetchAll(PDO::FETCH_ASSOC) as $table) {
70
            $tables[MySqlDriver::TYPE_TABLE][$table['TABLE_NAME']] = [
71
                'table' => $table['TABLE_NAME'],
72
                'engine' => $table['ENGINE'],
73
                'collation' => $table['TABLE_COLLATION'],
74
                'data_length' => $table['DATA_LENGTH'],
75
                'index_length' => $table['INDEX_LENGTH'],
76
                'data_free' => $table['DATA_FREE'],
77
                'autoincrement' => $table['AUTO_INCREMENT'],
78
                'rows' => $table['TABLE_ROWS'],
79
            ];
80
        }
81
82
        foreach ($this->connection->query("SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = '{$this->database}' ORDER BY TABLE_NAME")->fetchAll(PDO::FETCH_ASSOC) as $view) {
83
            $tables[MySqlDriver::TYPE_VIEW][$view['TABLE_NAME']] = [
84
                'view' => $view['TABLE_NAME'],
85
                'check_option' => $view['CHECK_OPTION'],
86
                'is_updatable' => $view['IS_UPDATABLE'],
87
                'definer' => $view['DEFINER'],
88
                'security_type' => $view['SECURITY_TYPE'],
89
                'character_set' => $view['CHARACTER_SET_CLIENT'],
90
                'collation' => $view['COLLATION_CONNECTION'],
91
            ];
92
        }
93
        return [
94
            MySqlDriver::TYPE_TABLE => Multisort::sort($tables[MySqlDriver::TYPE_TABLE], $sorting),
95
            MySqlDriver::TYPE_VIEW => Multisort::sort($tables[MySqlDriver::TYPE_VIEW], $sorting),
96
        ];
97
    }
98
99
    public function itemsCount($type, $table, array $filter = [])
100
    {
101
        $query = sprintf('SELECT count(*) FROM `%s`', $table) . $this->createWhere($filter);
102
        return $this->connection->query($query)->fetch(PDO::FETCH_COLUMN);
103
    }
104
105
    public function items($type, $table, $page, $onPage, array $filter = [], array $sorting = [])
106
    {
107
        $primaryColumns = $this->getPrimaryColumns($type, $table);
108
        $query = sprintf('SELECT * FROM `%s`', $table);
109
        $query .= $this->createWhere($filter);
110
        $query .= $this->createOrderBy($sorting);
111
        $query .= ' LIMIT ' . (($page - 1) * $onPage) . ', ' . $onPage;
112
        $items = [];
113
        foreach ($this->connection->query($query)->fetchAll(PDO::FETCH_ASSOC) as $item) {
114
            $pk = [];
115
            foreach ($primaryColumns as $primaryColumn) {
116
                $pk[] = $item[$primaryColumn];
117
            }
118
            $items[md5(implode('|', $pk))] = $item;
119
        }
120
        return $items;
121
    }
122
123
    private function createWhere(array $filter)
124
    {
125
        if (empty($filter)) {
126
            return '';
127
        }
128
129
        $operatorsMap = [
130
            Filter::OPERATOR_EQUAL => '= %s',
131
            Filter::OPERATOR_GREATER_THAN => '> %s',
132
            Filter::OPERATOR_GREATER_THAN_OR_EQUAL => '>= %s',
133
            Filter::OPERATOR_LESS_THAN => '< %s',
134
            Filter::OPERATOR_LESS_THAN_OR_EQUAL => '<= %s',
135
            Filter::OPERATOR_NOT_EQUAL => '!= %s',
136
            Filter::OPERATOR_CONTAINS => 'LIKE "%%%s%%"',
137
            Filter::OPERATOR_NOT_CONTAINS => 'NOT LIKE "%%%s%%"',
138
            Filter::OPERATOR_STARTS_WITH => 'LIKE "%s%%"',
139
            Filter::OPERATOR_ENDS_WITH => 'LIKE "%%%s"',
140
            Filter::OPERATOR_IS_NULL => 'IS NULL',
141
            Filter::OPERATOR_IS_NOT_NULL => 'IS NOT NULL',
142
            Filter::OPERATOR_IS_IN => 'IN (%s)',
143
            Filter::OPERATOR_IS_NOT_IN => 'NOT IN (%s)',
144
        ];
145
146
        $where = ' WHERE ';
147
        $whereParts = [];
148
        foreach ($filter as $filterPart) {
149
            foreach ($filterPart as $key => $filterSettings) {
150
                foreach ($filterSettings as $operator => $value) {
151
                    if (!isset($operatorsMap[$operator])) {
152
                        throw new OperatorNotSupportedException('Operator "' . $operator . '" is not supported.');
153
                    }
154
                    $whereParts[] = "`$key` " . sprintf($operatorsMap[$operator], $value);
155
                }
156
            }
157
        }
158
        $where .= implode(' AND ', $whereParts);
159
        return $where;
160
    }
161
162
    private function createOrderBy(array $sorting)
163
    {
164
        if (empty($sorting)) {
165
            return '';
166
        }
167
        $orderBy = ' ORDER BY ';
168
        $order = [];
169
        foreach ($sorting as $sort) {
170
            foreach ($sort as $key => $direction) {
171
                $direction = strtolower($direction) == 'asc' ? 'ASC' : 'DESC';
172
                $order[] = "`$key` $direction";
173
            }
174
        }
175
        $orderBy .= implode(', ', $order);
176
        return $orderBy;
177
    }
178
179
    public function loadItem($type, $table, $item)
180
    {
181
        $primaryColumns = $this->getPrimaryColumns($type, $table);
182
        $query = sprintf('SELECT * FROM `%s` WHERE md5(concat(%s)) = "%s"', $table, implode(', "|", ', $primaryColumns), $item);
183
        return $this->connection->query($query)->fetch(PDO::FETCH_ASSOC);
184
    }
185
186
    public function deleteItem($type, $table, $item)
187
    {
188
        $primaryColumns = $this->getPrimaryColumns($type, $table);
189
        $query = sprintf('DELETE FROM `%s` WHERE md5(concat(%s)) = "%s"', $table, implode(', "|", ', $primaryColumns), $item);
190
        return $this->connection->query($query);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->connection->query($query); (PDOStatement) is incompatible with the return type declared by the interface UniMan\Core\DataManager\...erInterface::deleteItem of type boolean|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
191
    }
192
193
    public function deleteTable($type, $table)
194
    {
195
        if ($type === MySqlDriver::TYPE_TABLE) {
196
            $query = sprintf('DROP TABLE `%s`', $table);
197
        } elseif ($type === MySqlDriver::TYPE_VIEW) {
198
            $query = sprintf('DROP VIEW `%s`', $table);
199
        } else {
200
            throw new InvalidArgumentException('Type "' . $type . '" is not supported');
201
        }
202
        return $this->connection->query($query);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->connection->query($query); (PDOStatement) is incompatible with the return type declared by the interface UniMan\Core\DataManager\...rInterface::deleteTable of type boolean|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
203
    }
204
205
    public function deleteDatabase($database)
206
    {
207
        $query = sprintf('DROP DATABASE `%s`', $database);
208
        return $this->connection->query($query);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->connection->query($query); (PDOStatement) is incompatible with the return type declared by the interface UniMan\Core\DataManager\...terface::deleteDatabase of type boolean|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
209
    }
210
211
    public function selectDatabase($database)
212
    {
213
        $this->database = $database;
214
        $query = sprintf('USE `%s`', $database);
215
        $this->connection->query($query);
216
    }
217
218
    public function getPrimaryColumns($type, $table)
219
    {
220
        $primaryColumns = [];
221
        $columns = [];
222
        foreach ($this->getColumns($type, $table) as $column) {
223
            $columns[] = $column['Field'];
224
            if ($column['Key'] == 'PRI') {
225
                $primaryColumns[] = $column['Field'];
226
            }
227
        }
228
        if (empty($primaryColumns)) {
229
            $primaryColumns = $columns;
230
        }
231
        return $primaryColumns;
232
    }
233
234
    public function getColumns($type, $table)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
235
    {
236
        if ($this->columns === null) {
237
            $columns = $this->connection->query(sprintf('SHOW FULL COLUMNS FROM `%s`', $table))->fetchAll(PDO::FETCH_ASSOC);
238
            $keys = [];
239
            foreach ($this->connection->query(sprintf('SELECT * FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = "%s" AND TABLE_NAME = "%s"', $this->database, $table))->fetchAll(PDO::FETCH_ASSOC) as $key) {
240
                $keys[$key['COLUMN_NAME']] = $key;
241
            }
242
            $this->columns = [];
243
            foreach ($columns as $column) {
244
                $column['key_info'] = isset($keys[$column['Field']]) ? $keys[$column['Field']] : [];
245
                $this->columns[$column['Field']] = $column;
246
            }
247
        }
248
        return $this->columns;
249
    }
250
251
    public function execute($commands)
252
    {
253
        $queries = array_filter(array_map('trim', explode(';', $commands)), function ($query) {
254
            return $query;
255
        });
256
        $results = [];
257
        foreach ($queries as $query) {
258
            $results[$query]['headers'] = [];
259
            $statement = $this->connection->query($query);
260
            if (Strings::startsWith(strtolower($query), 'select ') || Strings::startsWith(strtolower($query), 'show ')) {
261
                $res = $statement->fetchAll(PDO::FETCH_ASSOC);
262
                $results[$query]['headers'] = array_keys(current($res));
263
                $results[$query]['items'] = $res;
264
                continue;
265
            }
266
            $results[$query] = (bool) $statement;
267
        }
268
        return $results;
269
    }
270
}
271