Completed
Pull Request — master (#411)
by Anton
04:55
created

Db::setConnect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Bluz Framework Component
4
 *
5
 * @copyright Bluz PHP Team
6
 * @link https://github.com/bluzphp/framework
7
 */
8
9
declare(strict_types=1);
10
11
namespace Bluz\Db;
12
13
use Bluz\Common\Exception\ConfigurationException;
14
use Bluz\Common\Options;
15
use Bluz\Db\Exception\DbException;
16
use Bluz\Db\Query;
17
use Bluz\Proxy\Logger;
18
19
/**
20
 * PDO wrapper
21
 *
22
 * @package  Bluz\Db
23
 * @author   Anton Shevchuk
24
 * @link     https://github.com/bluzphp/framework/wiki/Db
25
 */
26
class Db
27
{
28
    use Options;
29
30
    /**
31
     * PDO connection settings
32
     * @var  array
33
     * @link http://php.net/manual/en/pdo.construct.php
34
     */
35
    protected $connect = [
36
        "type" => "mysql",
37
        "host" => "localhost",
38
        "name" => "",
39
        "user" => "root",
40
        "pass" => "",
41
        "options" => []
42
    ];
43
44
    /**
45
     * PDO connection flags
46
     * @var  array
47
     * @link http://php.net/manual/en/pdo.setattribute.php
48
     */
49
    protected $attributes = [
50
        \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
51
    ];
52
53
    /**
54
     * @var \PDO PDO instance
55
     */
56
    protected $handler;
57
58
    /**
59
     * Setup connection
60
     *
61
     * Just save connection settings
62
     * <code>
63
     *     $db->setConnect([
64
     *         'type' => 'mysql',
65
     *         'host' => 'localhost',
66
     *         'name' => 'db name',
67
     *         'user' => 'root',
68
     *         'pass' => ''
69
     *     ]);
70
     * </code>
71
     *
72
     * @param  array $connect options
73
     * @return Db
74
     * @throws DbException
75
     */
76 22
    public function setConnect(array $connect)
77
    {
78 22
        $this->connect = array_merge($this->connect, $connect);
79 22
        $this->checkConnect();
80 22
        return $this;
81
    }
82
83
    /**
84
     * Check connection options
85
     *
86
     * @return void
87
     * @throws ConfigurationException
88
     */
89 22
    private function checkConnect()
90
    {
91 22
        if (empty($this->connect['type']) or
92 22
            empty($this->connect['host']) or
93 22
            empty($this->connect['name']) or
94 22
            empty($this->connect['user'])
95
        ) {
96 1
            throw new ConfigurationException(
97
                'Database adapter is not configured.
98 1
                Please check `db` configuration section: required type, host, db name and user'
99
            );
100
        }
101 22
    }
102
103
    /**
104
     * Setup attributes for PDO connect
105
     *
106
     * @param  array $attributes
107
     * @return Db
108
     */
109
    public function setAttributes(array $attributes)
110
    {
111
        $this->attributes = $attributes;
112
        return $this;
113
    }
114
115
    /**
116
     * Connect to Db
117
     *
118
     * @return Db
119
     * @throws DbException
120
     */
121 16
    public function connect()
122
    {
123 16
        if (empty($this->handler)) {
124
            try {
125 16
                $this->checkConnect();
126 16
                $this->log("Connect to " . $this->connect['host']);
127 16
                $this->handler = new \PDO(
128 16
                    $this->connect['type'] . ":host=" . $this->connect['host'] . ";dbname=" . $this->connect['name'],
129 16
                    $this->connect['user'],
130 16
                    $this->connect['pass'],
131 16
                    $this->connect['options']
132
                );
133
134 16
                foreach ($this->attributes as $attribute => $value) {
135 16
                    $this->handler->setAttribute($attribute, $value);
136
                }
137
138 16
                $this->ok();
139
            } catch (\Exception $e) {
140
                throw new DbException("Attempt connection to database is failed: {$e->getMessage()}");
141
            }
142
        }
143 16
        return $this;
144
    }
145
146
    /**
147
     * Return PDO handler
148
     *
149
     * @return \PDO
150
     */
151 36
    public function handler()
152
    {
153 36
        if (empty($this->handler)) {
154 15
            $this->connect();
155
        }
156 36
        return $this->handler;
157
    }
158
159
    /**
160
     * Prepare SQL query and return PDO Statement
161
     *
162
     * @param  string $sql    SQL query with placeholders
163
     * @param  array  $params params for query placeholders
164
     * @return \PDOStatement
165
     */
166 23
    protected function prepare($sql, $params)
167
    {
168 23
        $stmt = $this->handler()->prepare($sql);
169 23
        $stmt->execute($params);
170
171 23
        $this->log($sql, $params);
172
173 23
        return $stmt;
174
    }
175
176
    /**
177
     * Quotes a string for use in a query
178
     *
179
     * Example of usage
180
     * <code>
181
     *     $db->quote($_GET['id'])
182
     * </code>
183
     *
184
     * @param  string $value
185
     * @param  int $type
186
     * @return string
187
     */
188 1
    public function quote($value, $type = \PDO::PARAM_STR)
189
    {
190 1
        return $this->handler()->quote($value, $type);
191
    }
192
193
    /**
194
     * Quote a string so it can be safely used as a table or column name
195
     *
196
     * @param  string $identifier
197
     * @return string
198
     */
199 16
    public function quoteIdentifier($identifier)
200
    {
201
        // switch statement for DB type
202 16
        switch ($this->connect['type']) {
203 16
            case 'mysql':
204 16
                return '`' . str_replace('`', '``', $identifier) . '`';
205
            case 'postgresql':
206
            case 'sqlite':
207
            default:
208
                return '"' . str_replace('"', '\\' . '"', $identifier) . '"';
209
        }
210
    }
211
212
    /**
213
     * Execute SQL query
214
     *
215
     * Example of usage
216
     * <code>
217
     *     $db->query("SET NAMES 'utf8'");
218
     * </code>
219
     *
220
     * @param  string $sql    SQL query with placeholders
221
     *                        "UPDATE users SET name = :name WHERE id = :id"
222
     * @param  array  $params params for query placeholders (optional)
223
     *                        array (':name' => 'John', ':id' => '123')
224
     * @param  array  $types  Types of params (optional)
225
     *                        array (':name' => \PDO::PARAM_STR, ':id' => \PDO::PARAM_INT)
226
     * @return integer the number of rows
227
     */
228 20
    public function query($sql, $params = [], $types = [])
229
    {
230 20
        $stmt = $this->handler()->prepare($sql);
231 20
        foreach ($params as $key => &$param) {
232 11
            $stmt->bindParam(
233 11
                (is_int($key)?$key+1:":".$key),
234
                $param,
235 11
                $types[$key] ?? \PDO::PARAM_STR
236
            );
237
        }
238 20
        $this->log($sql, $params);
239 20
        $stmt->execute($params);
240 20
        $this->ok();
241 20
        return $stmt->rowCount();
242
    }
243
244
    /**
245
     * Create new query select builder
246
     *
247
     * @param array|string ...$select The selection expressions
248
     * @return Query\Select
249
     */
250 1
    public function select(...$select)
251
    {
252 1
        $query = new Query\Select();
253 1
        $query->select(...$select);
254 1
        return $query;
255
    }
256
257
    /**
258
     * Create new query insert builder
259
     *
260
     * @param  string $table
261
     * @return Query\Insert
262
     */
263 1
    public function insert($table)
264
    {
265 1
        $query = new Query\Insert();
266 1
        $query->insert($table);
267 1
        return $query;
268
    }
269
270
    /**
271
     * Create new query update builder
272
     *
273
     * @param  string $table
274
     * @return Query\Update
275
     */
276 1
    public function update($table)
277
    {
278 1
        $query = new Query\Update();
279 1
        $query->update($table);
280 1
        return $query;
281
    }
282
283
    /**
284
     * Create new query update builder
285
     *
286
     * @param  string $table
287
     * @return Query\Delete
288
     */
289 9
    public function delete($table)
290
    {
291 9
        $query = new Query\Delete();
292 9
        $query->delete($table);
293 9
        return $query;
294
    }
295
296
    /**
297
     * Return first field from first element from the result set
298
     *
299
     * Example of usage
300
     * <code>
301
     *     $db->fetchOne("SELECT COUNT(*) FROM users");
302
     * </code>
303
     *
304
     * @param  string $sql     SQL query with placeholders
305
     *                         "SELECT * FROM users WHERE name = :name AND pass = :pass"
306
     * @param  array  $params  params for query placeholders (optional)
307
     *                         array (':name' => 'John', ':pass' => '123456')
308
     * @return string
309
     */
310 4
    public function fetchOne($sql, $params = [])
311
    {
312 4
        $stmt = $this->prepare($sql, $params);
313 4
        $result = $stmt->fetch(\PDO::FETCH_COLUMN);
314
315 4
        $this->ok();
316 4
        return $result;
317
    }
318
319
    /**
320
     * Returns an array containing first row from the result set
321
     *
322
     * Example of usage
323
     * <code>
324
     *     $db->fetchRow("SELECT name, email FROM users WHERE id = ". $db->quote($id));
325
     *     $db->fetchRow("SELECT name, email FROM users WHERE id = ?", [$id]);
326
     *     $db->fetchRow("SELECT name, email FROM users WHERE id = :id", [':id'=>$id]);
327
     * </code>
328
     *
329
     * @param  string $sql     SQL query with placeholders
330
     *                         "SELECT * FROM users WHERE name = :name AND pass = :pass"
331
     * @param  array  $params  params for query placeholders (optional)
332
     *                         array (':name' => 'John', ':pass' => '123456')
333
     * @return array           array ('name' => 'John', 'email' => '[email protected]')
334
     */
335 1
    public function fetchRow($sql, $params = [])
336
    {
337 1
        $stmt = $this->prepare($sql, $params);
338 1
        $result = $stmt->fetch(\PDO::FETCH_ASSOC);
339
340 1
        $this->ok();
341 1
        return $result;
342
    }
343
344
    /**
345
     * Returns an array containing all of the result set rows
346
     *
347
     * Example of usage
348
     * <code>
349
     *     $db->fetchAll("SELECT * FROM users WHERE ip = ?", ['192.168.1.1']);
350
     * </code>
351
     *
352
     * @param  string $sql    SQL query with placeholders
353
     *                        "SELECT * FROM users WHERE ip = :ip"
354
     * @param  array  $params params for query placeholders (optional)
355
     *                        array (':ip' => '127.0.0.1')
356
     * @return array[]
357
     */
358 3
    public function fetchAll($sql, $params = [])
359
    {
360 3
        $stmt = $this->prepare($sql, $params);
361 3
        $result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
362
363 3
        $this->ok();
364 3
        return $result;
365
    }
366
367
    /**
368
     * Returns an array containing one column from the result set rows
369
     *
370
     * @param  string $sql    SQL query with placeholders
371
     *                        "SELECT id FROM users WHERE ip = :ip"
372
     * @param  array  $params params for query placeholders (optional)
373
     *                        array (':ip' => '127.0.0.1')
374
     * @return array
375
     */
376 2
    public function fetchColumn($sql, $params = [])
377
    {
378 2
        $stmt = $this->prepare($sql, $params);
379 2
        $result = $stmt->fetchAll(\PDO::FETCH_COLUMN);
380
381 2
        $this->ok();
382 2
        return $result;
383
    }
384
385
    /**
386
     * Returns an array containing all of the result set rows
387
     *
388
     * Group by first column
389
     *
390
     * <code>
391
     *     $db->fetchGroup("SELECT ip, COUNT(id) FROM users GROUP BY ip", []);
392
     * </code>
393
     *
394
     * @param  string $sql    SQL query with placeholders
395
     *                        "SELECT ip, id FROM users"
396
     * @param  array  $params params for query placeholders (optional)
397
     * @param  mixed  $object
398
     * @return array
399
     */
400 1
    public function fetchGroup($sql, $params = [], $object = null)
401
    {
402 1
        $stmt = $this->prepare($sql, $params);
403
404 1
        if ($object) {
405
            $result = $stmt->fetchAll(\PDO::FETCH_CLASS | \PDO::FETCH_GROUP, $object);
406
        } else {
407 1
            $result = $stmt->fetchAll(\PDO::FETCH_ASSOC | \PDO::FETCH_GROUP);
408
        }
409
410 1
        $this->ok();
411 1
        return $result;
412
    }
413
414
    /**
415
     * Returns an array containing all of the result set rows
416
     *
417
     * Group by first column
418
     *
419
     * @param  string $sql    SQL query with placeholders
420
     *                        "SELECT ip, id FROM users"
421
     * @param  array  $params params for query placeholders (optional)
422
     * @return array
423
     */
424 1
    public function fetchColumnGroup($sql, $params = [])
425
    {
426 1
        $stmt = $this->prepare($sql, $params);
427 1
        $result = $stmt->fetchAll(\PDO::FETCH_COLUMN | \PDO::FETCH_GROUP);
428
429 1
        $this->ok();
430 1
        return $result;
431
    }
432
433
    /**
434
     * Returns a key-value array
435
     *
436
     * @param  string $sql    SQL query with placeholders
437
     *                        "SELECT id, username FROM users WHERE ip = :ip"
438
     * @param  array  $params params for query placeholders (optional)
439
     *                        array (':ip' => '127.0.0.1')
440
     * @return array
441
     */
442 1
    public function fetchPairs($sql, $params = [])
443
    {
444 1
        $stmt = $this->prepare($sql, $params);
445 1
        $result = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR);
446
447 1
        $this->ok();
448 1
        return $result;
449
    }
450
451
    /**
452
     * Returns an object containing first row from the result set
453
     *
454
     * Example of usage
455
     * <code>
456
     *     // Fetch object to stdClass
457
     *     $stdClass = $db->fetchObject('SELECT * FROM some_table WHERE id = ?', [$id]);
458
     *     // Fetch object to new Some object
459
     *     $someClass = $db->fetchObject('SELECT * FROM some_table WHERE id = ?', [$id], 'Some');
460
     *     // Fetch object to exists instance of Some object
461
     *     $someClass = $db->fetchObject('SELECT * FROM some_table WHERE id = ?', [$id], $someClass);
462
     * </code>
463
     *
464
     * @param  string $sql    SQL query with placeholders
465
     *                        "SELECT * FROM users WHERE name = :name AND pass = :pass"
466
     * @param  array  $params params for query placeholders (optional)
467
     *                        array (':name' => 'John', ':pass' => '123456')
468
     * @param  mixed  $object
469
     * @return array
470
     */
471 3
    public function fetchObject($sql, $params = [], $object = 'stdClass')
472
    {
473 3
        $stmt = $this->prepare($sql, $params);
474
475 3
        if (is_string($object)) {
476
            // some class name
477 2
            $result = $stmt->fetchObject($object);
478
        } else {
479
            // some instance
480 1
            $stmt->setFetchMode(\PDO::FETCH_INTO, $object);
481 1
            $result = $stmt->fetch(\PDO::FETCH_INTO);
482
        }
483
484 3
        $stmt->closeCursor();
485 3
        $this->ok();
486 3
        return $result;
487
    }
488
489
    /**
490
     * Returns an array of objects containing the result set
491
     *
492
     * @param  string $sql    SQL query with placeholders
493
     *                        "SELECT * FROM users WHERE name = :name AND pass = :pass"
494
     * @param  array  $params params for query placeholders (optional)
495
     *                        array (':name' => 'John', ':pass' => '123456')
496
     * @param  mixed  $object Class name or instance
497
     * @return array
498
     */
499 10
    public function fetchObjects($sql, $params = [], $object = null)
500
    {
501 10
        $stmt = $this->prepare($sql, $params);
502
503 10
        if (is_string($object)) {
504
            // fetch to some class by name
505 9
            $result = $stmt->fetchAll(\PDO::FETCH_CLASS, $object);
506
        } else {
507
            // fetch to StdClass
508 1
            $result = $stmt->fetchAll(\PDO::FETCH_OBJ);
509
        }
510
511 10
        $stmt->closeCursor();
512 10
        $this->ok();
513 10
        return $result;
514
    }
515
516
    /**
517
     * Returns an array of linked objects containing the result set
518
     *
519
     * @param  string $sql    SQL query with placeholders
520
     *                        "SELECT '__users', u.*, '__users_profile', up.*
521
     *                        FROM users u
522
     *                        LEFT JOIN users_profile up ON up.userId = u.id
523
     *                        WHERE u.name = :name"
524
     * @param  array  $params params for query placeholders (optional)
525
     *                        array (':name' => 'John')
526
     * @return array
527
     */
528
    public function fetchRelations($sql, $params = [])
529
    {
530
        $stmt = $this->prepare($sql, $params);
531
532
        $result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
533
534
        // prepare results
535
        $result = Relations::fetch($result);
536
537
        $stmt->closeCursor();
538
        $this->ok();
539
        return $result;
540
    }
541
542
    /**
543
     * Transaction wrapper
544
     *
545
     * Example of usage
546
     * <code>
547
     *     $db->transaction(function() use ($db) {
548
     *         $db->query("INSERT INTO `table` ...");
549
     *         $db->query("UPDATE `table` ...");
550
     *         $db->query("DELETE FROM `table` ...");
551
     *     })
552
     * </code>
553
     *
554
     * @param  callable $process callable structure - closure function or class with __invoke() method
555
     * @return bool
556
     * @throws DbException
557
     */
558 5
    public function transaction(callable $process)
559
    {
560
        try {
561 5
            $this->handler()->beginTransaction();
562 5
            $process();
563 4
            $this->handler()->commit();
564 4
            return true;
565 1
        } catch (\PDOException $e) {
566 1
            $this->handler()->rollBack();
567 1
            return false;
568
        }
569
    }
570
571
    /**
572
     * Setup timer
573
     *
574
     * @return void
575
     */
576 36
    protected function ok()
577
    {
578 36
        Logger::info("<<<");
579 36
    }
580
581
    /**
582
     * Log queries by Application
583
     *
584
     * @param  string $sql     SQL query for logs
585
     * @param  array  $context
586
     * @return void
587
     */
588 36
    protected function log($sql, array $context = [])
589
    {
590 36
        $sql = str_replace('%', '%%', $sql);
591 36
        $sql = preg_replace('/\?/', '"%s"', $sql, count($context));
592
593
        // replace mask by data
594 36
        $log = vsprintf($sql, $context);
595
596 36
        Logger::info($log);
597 36
    }
598
599
    /**
600
     * Disconnect PDO and clean default adapter
601
     *
602
     * @return void
603
     */
604 21
    public function disconnect()
605
    {
606 21
        if ($this->handler) {
607 15
            $this->handler = null;
608
        }
609 21
    }
610
}
611