Completed
Push — master ( fd5a8f...ebf9fc )
by Oleg
04:44
created

MysqlDriver::listFields()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 34
rs 8.439
cc 5
eloc 22
nc 5
nop 1
1
<?php /** MysqlDriverMicro */
2
3
namespace Micro\Db\Drivers;
4
5
use Micro\Base\Exception;
6
7
/**
8
 * MySQL Driver class file.
9
 *
10
 * @author Oleg Lunegov <[email protected]>
11
 * @link https://github.com/linpax/microphp-framework
12
 * @copyright Copyright (c) 2013 Oleg Lunegov
13
 * @license https://github.com/linpax/microphp-framework/blob/master/LICENSE
14
 * @package Micro
15
 * @subpackage Db\Drivers
16
 * @version 1.0
17
 * @since 1.0
18
 */
19
class MysqlDriver extends Driver
20
{
21
    /** @var \PDO|null $conn Connection to DB */
22
    protected $conn;
23
    /** @var string $tableSchema Table schema for postgres */
24
    protected $tableSchema = 'public';
25
26
27
    /**
28
     * Construct for this class
29
     *
30
     * @access public
31
     *
32
     * @param array $config
33
     * @param array $options
34
     *
35
     * @result void
36
     * @throws Exception
37
     */
38
    public function __construct(array $config = [], array $options = [])
39
    {
40
        parent::__construct();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Micro\Db\Drivers\Driver as the method __construct() does only exist in the following sub-classes of Micro\Db\Drivers\Driver: Micro\Db\Drivers\MongodbDriver, Micro\Db\Drivers\MysqlDriver. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
41
42
        if (!empty($config['schema'])) {
43
            $this->tableSchema = $config['schema'];
44
        }
45
46
        try {
47
            $this->conn = new \PDO($config['dsn'], $config['username'], $config['password'], $options);
48
            $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
49
50
        } catch (\PDOException $e) {
51
            if (!array_key_exists('ignoreFail', $config) || !$config['ignoreFail']) {
52
                throw new Exception('Connect to DB failed: '.$e->getMessage());
53
            }
54
        }
55
    }
56
57
    /**
58
     * Destructor for this class
59
     *
60
     * @access public
61
     * @return void
62
     */
63
    public function __destruct()
64
    {
65
        $this->conn = null;
66
    }
67
68
    /**
69
     * Send RAW query to DB
70
     *
71
     * @access public
72
     *
73
     * @param string $query raw query to db
74
     * @param array $params params for query
75
     * @param int $fetchType fetching type
76
     * @param string $fetchClass fetching class
77
     *
78
     * @return \PDOStatement|array
79
     * @throws Exception
80
     */
81
    public function rawQuery($query = '', array $params = [], $fetchType = \PDO::FETCH_ASSOC, $fetchClass = 'Model')
82
    {
83
        $sth = $this->conn->prepare($query);
84
85
        if ($fetchType === \PDO::FETCH_CLASS) {
86
            /** @noinspection PhpMethodParametersCountMismatchInspection */
87
            $sth->setFetchMode($fetchType, $fetchClass, ['new' => false]);
88
        } else {
89
            $sth->setFetchMode($fetchType);
90
        }
91
92
        foreach ($params AS $name => $value) {
93
            $sth->bindValue($name, $value);
94
        }
95
96
        $sth->execute();
97
98
        return $sth->fetchAll();
99
    }
100
101
    /**
102
     * List database names on this connection
103
     *
104
     * @access public
105
     * @return array|boolean
106
     */
107
    public function listDatabases()
108
    {
109
        $sql = 'SHOW DATABASES;';
110
111
        if ($this->getDriverType() === 'pgsql') {
112
            $sql = 'SELECT datname FROM pg_database;';
113
        }
114
115
        $sth = $this->conn->query($sql);
116
        $result = [];
117
118
        foreach ($sth->fetchAll() AS $row) {
119
            $result[] = $row[0];
120
        }
121
122
        return $result;
123
    }
124
125
    public function getDriverType()
126
    {
127
        return $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME);
128
    }
129
130
    /**
131
     * Info of database
132
     *
133
     * @access public
134
     * @param string $dbName Database name
135
     * @return array
136
     */
137
    public function infoDatabase($dbName)
138
    {
139
        $sth = $this->conn->query("SHOW TABLE STATUS FROM {$dbName};");
140
141
        $result = [];
142
        foreach ($sth->fetchAll() AS $row) {
143
            $result[] = [
144
                'name' => $row['Name'],
145
                'engine' => $row['Engine'],
146
                'rows' => $row['Rows'],
147
                'length' => $row['Avg_row_length'],
148
                'increment' => $row['Auto_increment'],
149
                'collation' => $row['Collation']
150
            ];
151
        }
152
153
        return $result;
154
    }
155
156
    /**
157
     * @inheritdoc
158
     */
159
    public function tableExists($table)
160
    {
161
        return in_array($table, $this->listTables(), false);
162
    }
163
164
    /**
165
     * @inheritdoc
166
     */
167
    public function listTables()
168
    {
169
        $sql = 'SHOW TABLES;';
170
171
        if ($this->getDriverType() == 'pgsql') {
172
            $sql = 'SELECT table_name FROM information_schema.tables WHERE table_schema = \'' . $this->tableSchema . '\'';
173
        }
174
175
        return $this->conn->query($sql)->fetchAll(\PDO::FETCH_COLUMN, 0);
176
    }
177
178
    /**
179
     * @inheritdoc
180
     */
181
    public function createTable($name, array $elements = [], $params = '')
182
    {
183
        return $this->conn->exec(
184
            sprintf('SELECT TABLE IF NOT EXISTS `%s` (%s) %s;', $name, implode(',', $elements), $params)
185
        );
186
    }
187
188
    /**
189
     * @inheritdoc
190
     */
191
    public function clearTable($name)
192
    {
193
        return $this->conn->exec("TRUNCATE {$name};");
194
    }
195
196
    /**
197
     * @inheritdoc
198
     */
199
    public function removeTable($name)
200
    {
201
        return $this->conn->exec("DROP TABLE {$name};");
202
    }
203
204
    /**
205
     * @inheritdoc
206
     */
207
    public function fieldExists($field, $table)
208
    {
209
        foreach ($this->listFields($table) AS $tbl) {
210
            if ($tbl['field'] === $field) {
211
                return true;
212
            }
213
        }
214
215
        return false;
216
    }
217
218
    /**
219
     * @inheritdoc
220
     */
221
    public function listFields($table)
222
    {
223
        if ($this->getDriverType() === 'pgsql') {
224
            $sth = $this->conn->query('SELECT * FROM information_schema.columns WHERE table_name =\'categories\'');
225
226
            $result = [];
227
            foreach ($sth->fetchAll(\PDO::FETCH_ASSOC) as $row) {
228
                $result[] = [
229
                    'field' => $row['column_name'],
230
                    'type' => $row['data_type'] . (($max = $row['character_maximum_length']) ? '(' . $max . ')' : ''),
231
                    'null' => $row['is_nullable'],
232
                    'default' => $row['column_default']
233
                ];
234
            }
235
236
            return $result;
237
        }
238
239
        $sth = $this->conn->query("SHOW COLUMNS FROM {$table};");
240
241
        $result = [];
242
        foreach ($sth->fetchAll(\PDO::FETCH_ASSOC) as $row) {
243
            $result[] = [
244
                'field' => $row['Field'],
245
                'type' => $row['Type'],
246
                'null' => $row['Null'],
247
                'key' => $row['Key'],
248
                'default' => $row['Default'],
249
                'extra' => $row['Extra']
250
            ];
251
        }
252
253
        return $result;
254
    }
255
256
    /**
257
     * @inheritdoc
258
     */
259
    public function fieldInfo($field, $table)
260
    {
261
        return $this->conn->query("SELECT {$field} FROM {$table} LIMIT 1;")->getColumnMeta(0);
262
    }
263
264
    /**
265
     * @inheritdoc
266
     */
267
    public function switchDatabase($dbName)
268
    {
269
        if ($this->conn->exec("USE {$dbName};") !== false) {
270
            return true;
271
        } else {
272
            return false;
273
        }
274
    }
275
276
    /**
277
     * @inheritdoc
278
     */
279
    public function insert($table, array $line = [], $multi = false)
280
    {
281
        $fields = '`' . implode('`, `', array_keys($multi ? $line[0] : $line)) . '`';
282
283
        if ($this->getDriverType() === 'pgsql') {
284
            $fields = '"' . implode('", "', array_keys($multi ? $line[0] : $line)) . '"';
285
        }
286
287
        $values = ':'.implode(', :', array_keys($multi ? $line[0] : $line));
288
        $rows = $multi ? $line : [$line];
289
        $id = null;
290
291
        if ($rows) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rows 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...
292
            $this->conn->beginTransaction();
293
294
            $dbh = null;
295
            foreach ($rows AS $row) {
296
                $dbh = $this->conn->prepare("INSERT INTO {$table} ({$fields}) VALUES ({$values});")->execute($row);
297
            }
298
299
            $id = $dbh ? $this->conn->lastInsertId() : false;
300
            $this->conn->commit();
301
        }
302
303
        return $id ?: false;
304
    }
305
306
    /**
307
     * @inheritdoc
308
     */
309
    public function update($table, array $elements = [], $conditions = '')
310
    {
311
        $keys = array_keys($elements);
312
        if (0 === count($keys)) {
313
            return false;
314
        }
315
316
        $valStr = [];
317
        foreach ($keys as $key) {
318
            $valStr[] = '`'.$key.'` = :'.$key;
319
        }
320
        $valStr = implode(',', $valStr);
321
322
        if ($conditions) {
323
            $conditions = 'WHERE '.$conditions;
324
        }
325
326
        return $this->conn->prepare("UPDATE {$table} SET {$valStr} {$conditions};")->execute($elements);
327
    }
328
329
    /**
330
     * @inheritdoc
331
     */
332
    public function delete($table, $conditions, array $ph = [])
333
    {
334
        return $this->conn->prepare("DELETE FROM {$table} WHERE {$conditions};")->execute($ph);
335
    }
336
337
    /**
338
     * @inheritdoc
339
     */
340
    public function exists($table, array $params = [])
341
    {
342
        $keys = [];
343
        foreach ($params AS $key => $val) {
344
            $keys[] = $table . '.' . $key . '=\'' . $val . '\'';
345
        }
346
347
        $sth = $this->conn->prepare('SELECT * FROM ' . $table . ' WHERE ' . implode(' AND ', $keys) . ' LIMIT 1;');
348
        /** @noinspection PdoApiUsageInspection */
349
        $sth->execute();
350
351
        return (bool)$sth->rowCount();
352
    }
353
354
    /**
355
     * @inheritdoc
356
     */
357
    public function count($subQuery = '', $table = '')
358
    {
359
        if ($subQuery) {
360
            $sth = $this->conn->prepare("SELECT COUNT(*) FROM ({$subQuery}) AS m;");
361
        } elseif ($table) {
362
            $sth = $this->conn->prepare("SELECT COUNT(*) FROM {$table} AS m;");
363
        } else {
364
            return false;
365
        }
366
        if ($sth->execute()) {
367
            return $sth->fetchColumn();
368
        }
369
370
        return false;
371
    }
372
}
373