Completed
Push — master ( e91368...26d5da )
by Anton
9s
created

Db::log()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 2
dl 0
loc 10
ccs 6
cts 6
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
/**
10
 * @namespace
11
 */
12
namespace Bluz\Db;
13
14
use Bluz\Common\Exception\ConfigurationException;
15
use Bluz\Common\Options;
16
use Bluz\Db\Exception\DbException;
17
use Bluz\Db\Query;
18
use Bluz\Proxy\Logger;
19
20
/**
21
 * PDO wrapper
22
 *
23
 * @package  Bluz\Db
24
 * @author   Anton Shevchuk
25
 * @link     https://github.com/bluzphp/framework/wiki/Db
26
 */
27
class Db
28
{
29
    use Options;
30
31
    /**
32
     * PDO connection settings
33
     * @var  array
34
     * @link http://php.net/manual/en/pdo.construct.php
35
     */
36
    protected $connect = [
37
        "type" => "mysql",
38
        "host" => "localhost",
39
        "name" => "",
40
        "user" => "root",
41
        "pass" => "",
42
        "options" => []
43
    ];
44
45
    /**
46
     * PDO connection flags
47
     * @var  array
48
     * @link http://php.net/manual/en/pdo.setattribute.php
49
     */
50
    protected $attributes = [
51
        \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
52
    ];
53
54
    /**
55
     * @var \PDO PDO instance
56
     */
57
    protected $handler;
58
59
    /**
60
     * Setup connection
61
     *
62
     * Just save connection settings
63
     * <code>
64
     *     $db->setConnect([
65
     *         'type' => 'mysql',
66
     *         'host' => 'localhost',
67
     *         'name' => 'db name',
68
     *         'user' => 'root',
69
     *         'pass' => ''
70
     *     ]);
71
     * </code>
72
     *
73
     * @param  array $connect options
74
     * @return Db
75
     * @throws DbException
76
     */
77 22
    public function setConnect(array $connect)
78
    {
79 22
        $this->connect = array_merge($this->connect, $connect);
80 22
        $this->checkConnect();
81 22
        return $this;
82
    }
83
84
    /**
85
     * Check connection options
86
     *
87
     * @return void
88
     * @throws ConfigurationException
89
     */
90 22
    private function checkConnect()
91
    {
92 22
        if (empty($this->connect['type']) or
93 22
            empty($this->connect['host']) or
94 22
            empty($this->connect['name']) or
95 22
            empty($this->connect['user'])
96
        ) {
97 1
            throw new ConfigurationException(
98
                "Database adapter is not configured.
99 1
                Please check 'db' configuration section: required type, host, db name and user"
100
            );
101
        }
102 22
    }
103
104
    /**
105
     * Setup attributes for PDO connect
106
     *
107
     * @param  array $attributes
108
     * @return Db
109
     */
110
    public function setAttributes(array $attributes)
111
    {
112
        $this->attributes = $attributes;
113
        return $this;
114
    }
115
116
    /**
117
     * Connect to Db
118
     *
119
     * @return Db
120
     * @throws DbException
121
     */
122 16
    public function connect()
123
    {
124 16
        if (empty($this->handler)) {
125
            try {
126 16
                $this->checkConnect();
127 16
                $this->log("Connect to " . $this->connect['host']);
128 16
                $this->handler = new \PDO(
129 16
                    $this->connect['type'] . ":host=" . $this->connect['host'] . ";dbname=" . $this->connect['name'],
130 16
                    $this->connect['user'],
131 16
                    $this->connect['pass'],
132 16
                    $this->connect['options']
133
                );
134
135 16
                foreach ($this->attributes as $attribute => $value) {
136 16
                    $this->handler->setAttribute($attribute, $value);
137
                }
138
139 16
                $this->ok();
140
            } catch (\Exception $e) {
141
                throw new DbException('Attempt connection to database is failed: '. $e->getMessage());
142
            }
143
        }
144 16
        return $this;
145
    }
146
147
    /**
148
     * Return PDO handler
149
     *
150
     * @return \PDO
151
     */
152 26
    public function handler()
153
    {
154 26
        if (empty($this->handler)) {
155 15
            $this->connect();
156
        }
157 26
        return $this->handler;
158
    }
159
160
    /**
161
     * Prepare SQL query and return PDO Statement
162
     *
163
     * @param  string $sql    SQL query with placeholders
164
     * @param  array  $params params for query placeholders
165
     * @return \PDOStatement
166
     */
167 15
    protected function prepare($sql, $params)
168
    {
169 15
        $stmt = $this->handler()->prepare($sql);
170 15
        $stmt->execute($params);
171
172 15
        $this->log($sql, $params);
173
174 15
        return $stmt;
175
    }
176
177
    /**
178
     * Quotes a string for use in a query
179
     *
180
     * Example of usage
181
     * <code>
182
     *     $db->quote($_GET['id'])
183
     * </code>
184
     *
185
     * @param  string $value
186
     * @return string
187
     */
188 1
    public function quote($value)
189
    {
190 1
        return $this->handler()->quote($value);
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 13
    public function quoteIdentifier($identifier)
200
    {
201
        // switch statement for DB type
202 13
        switch ($this->connect['type']) {
203 13
            case 'mysql':
204 13
                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 10
    public function query($sql, $params = [], $types = [])
229
    {
230 10
        $stmt = $this->handler()->prepare($sql);
231 10
        foreach ($params as $key => &$param) {
232 8
            $stmt->bindParam(
233 8
                (is_int($key)?$key+1:":".$key),
234
                $param,
235 8
                $types[$key] ?? \PDO::PARAM_STR
236
            );
237
        }
238 10
        $this->log($sql, $params);
239 10
        $stmt->execute($params);
240 10
        $this->ok();
241 10
        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 3
    public function fetchOne($sql, $params = [])
311
    {
312 3
        $stmt = $this->prepare($sql, $params);
313 3
        $result = $stmt->fetch(\PDO::FETCH_COLUMN);
314
315 3
        $this->ok();
316 3
        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 1
    public function fetchColumn($sql, $params = [])
377
    {
378 1
        $stmt = $this->prepare($sql, $params);
379 1
        $result = $stmt->fetchAll(\PDO::FETCH_COLUMN);
380
381 1
        $this->ok();
382 1
        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 3
    public function fetchObjects($sql, $params = [], $object = null)
500
    {
501 3
        $stmt = $this->prepare($sql, $params);
502
503 3
        if (is_string($object)) {
504
            // fetch to some class by name
505 2
            $result = $stmt->fetchAll(\PDO::FETCH_CLASS, $object);
506
        } else {
507
            // fetch to StdClass
508 1
            $result = $stmt->fetchAll(\PDO::FETCH_OBJ);
509
        }
510
511 3
        $stmt->closeCursor();
512 3
        $this->ok();
513 3
        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($process)
559
    {
560 5
        if (!is_callable($process)) {
561 1
            throw new DbException('First argument of transaction method should be callable');
562
        }
563
        try {
564 4
            $this->handler()->beginTransaction();
565 4
            call_user_func($process);
566 3
            $this->handler()->commit();
567 3
            return true;
568 1
        } catch (\PDOException $e) {
569 1
            $this->handler()->rollBack();
570 1
            return false;
571
        }
572
    }
573
574
    /**
575
     * Setup timer
576
     *
577
     * @return void
578
     */
579 26
    protected function ok()
580
    {
581 26
        Logger::info("<<<");
582 26
    }
583
584
    /**
585
     * Log queries by Application
586
     *
587
     * @param  string $sql     SQL query for logs
588
     * @param  array  $context
589
     * @return void
590
     */
591 26
    protected function log($sql, array $context = [])
592
    {
593 26
        $sql = str_replace('%', '%%', $sql);
594 26
        $sql = preg_replace('/\?/', '"%s"', $sql, sizeof($context));
595
596
        // replace mask by data
597 26
        $log = vsprintf($sql, $context);
598
599 26
        Logger::info($log);
600 26
    }
601
602
    /**
603
     * Disconnect PDO and clean default adapter
604
     *
605
     * @return void
606
     */
607 21
    public function disconnect()
608
    {
609 21
        if ($this->handler) {
610 15
            $this->handler = null;
611
        }
612 21
    }
613
}
614