Completed
Push — master ( 369b2c...ffb215 )
by Anton
8s
created

Db::ok()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

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