Completed
Pull Request — master (#367)
by Anton
09:09
created

Db::fetchGroup()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0116

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 8
c 1
b 0
f 1
nc 2
nop 3
dl 0
loc 13
ccs 6
cts 7
cp 0.8571
crap 2.0116
rs 9.4285
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 = 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 26
    public function handler()
158
    {
159 26
        if (empty($this->handler)) {
160 15
            $this->connect();
161
        }
162 26
        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 15
    protected function prepare($sql, $params)
173
    {
174 15
        $stmt = $this->handler()->prepare($sql);
175 15
        $stmt->execute($params);
176
177 15
        $this->log($sql, $params);
178
179 15
        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 13
    public function quoteIdentifier($identifier)
205
    {
206
        // switch statement for DB type
207 13
        switch ($this->connect['type']) {
208 13
            case 'mysql':
209 13
                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 10
    public function query($sql, $params = array(), $types = array())
234
    {
235 10
        $stmt = $this->handler()->prepare($sql);
236 10
        foreach ($params as $key => &$param) {
237 8
            $stmt->bindParam(
238 8
                (is_int($key)?$key+1:":".$key),
239
                $param,
240 8
                isset($types[$key])?$types[$key]:\PDO::PARAM_STR
241
            );
242
        }
243 10
        $this->log($sql, $params);
244 10
        $stmt->execute($params);
245 10
        $this->ok();
246 10
        return $stmt->rowCount();
247
    }
248
249
    /**
250
     * Create new query select builder
251
     *
252
     * @param array|string ...$select The selection expressions
253
     * @return Query\Select
254
     */
255 1
    public function select(...$select)
256
    {
257 1
        $query = new Query\Select();
258 1
        $query->select(...$select);
259 1
        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 3
    public function fetchOne($sql, $params = array())
316
    {
317 3
        $stmt = $this->prepare($sql, $params);
318 3
        $result = $stmt->fetch(\PDO::FETCH_COLUMN);
319
320 3
        $this->ok();
321 3
        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 1
    public function fetchColumn($sql, $params = array())
382
    {
383 1
        $stmt = $this->prepare($sql, $params);
384 1
        $result = $stmt->fetchAll(\PDO::FETCH_COLUMN);
385
386 1
        $this->ok();
387 1
        return $result;
388
    }
389
390
    /**
391
     * Returns an array containing all of the result set rows
392
     *
393
     * Group by first column
394
     *
395
     * <code>
396
     *     $db->fetchGroup("SELECT ip, COUNT(id) FROM users GROUP BY ip", array());
397
     * </code>
398
     *
399
     * @param  string $sql    SQL query with placeholders
400
     *                        "SELECT ip, id FROM users"
401
     * @param  array  $params params for query placeholders (optional)
402
     * @param  mixed  $object
403
     * @return array
404
     */
405 1
    public function fetchGroup($sql, $params = array(), $object = null)
406
    {
407 1
        $stmt = $this->prepare($sql, $params);
408
409 1
        if ($object) {
410
            $result = $stmt->fetchAll(\PDO::FETCH_CLASS | \PDO::FETCH_GROUP, $object);
411
        } else {
412 1
            $result = $stmt->fetchAll(\PDO::FETCH_ASSOC | \PDO::FETCH_GROUP);
413
        }
414
415 1
        $this->ok();
416 1
        return $result;
417
    }
418
419
    /**
420
     * Returns an array containing all of the result set rows
421
     *
422
     * Group by first column
423
     *
424
     * @param  string $sql    SQL query with placeholders
425
     *                        "SELECT ip, id FROM users"
426
     * @param  array  $params params for query placeholders (optional)
427
     * @return array
428
     */
429 1
    public function fetchColumnGroup($sql, $params = array())
430
    {
431 1
        $stmt = $this->prepare($sql, $params);
432 1
        $result = $stmt->fetchAll(\PDO::FETCH_COLUMN | \PDO::FETCH_GROUP);
433
434 1
        $this->ok();
435 1
        return $result;
436
    }
437
438
    /**
439
     * Returns a key-value array
440
     *
441
     * @param  string $sql    SQL query with placeholders
442
     *                        "SELECT id, username FROM users WHERE ip = :ip"
443
     * @param  array  $params params for query placeholders (optional)
444
     *                        array (':ip' => '127.0.0.1')
445
     * @return array
446
     */
447 1
    public function fetchPairs($sql, $params = array())
448
    {
449 1
        $stmt = $this->prepare($sql, $params);
450 1
        $result = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR);
451
452 1
        $this->ok();
453 1
        return $result;
454
    }
455
456
    /**
457
     * Returns an object containing first row from the result set
458
     *
459
     * Example of usage
460
     * <code>
461
     *     // Fetch object to stdClass
462
     *     $stdClass = $db->fetchObject('SELECT * FROM some_table WHERE id = ?', array($id));
463
     *     // Fetch object to new Some object
464
     *     $someClass = $db->fetchObject('SELECT * FROM some_table WHERE id = ?', array($id), 'Some');
465
     *     // Fetch object to exists instance of Some object
466
     *     $someClass = $db->fetchObject('SELECT * FROM some_table WHERE id = ?', array($id), $someClass);
467
     * </code>
468
     *
469
     * @param  string $sql    SQL query with placeholders
470
     *                        "SELECT * FROM users WHERE name = :name AND pass = :pass"
471
     * @param  array  $params params for query placeholders (optional)
472
     *                        array (':name' => 'John', ':pass' => '123456')
473
     * @param  mixed  $object
474
     * @return array
475
     */
476 3
    public function fetchObject($sql, $params = array(), $object = null)
477
    {
478 3
        $stmt = $this->prepare($sql, $params);
479
480 3
        if (is_string($object)) {
481
            // some class name
482 1
            $result = $stmt->fetchObject($object);
483
        } else {
484
            // some instance
485 2
            $stmt->setFetchMode(\PDO::FETCH_INTO, $object);
486 1
            $result = $stmt->fetch(\PDO::FETCH_INTO);
487
        }
488
489 2
        $stmt->closeCursor();
490 2
        $this->ok();
491 2
        return $result;
492
    }
493
494
    /**
495
     * Returns an array of objects containing the result set
496
     *
497
     * @param  string $sql    SQL query with placeholders
498
     *                        "SELECT * FROM users WHERE name = :name AND pass = :pass"
499
     * @param  array  $params params for query placeholders (optional)
500
     *                        array (':name' => 'John', ':pass' => '123456')
501
     * @param  mixed  $object Class name or instance
502
     * @return array
503
     */
504 3
    public function fetchObjects($sql, $params = array(), $object = null)
505
    {
506 3
        $stmt = $this->prepare($sql, $params);
507
508 3
        if (is_string($object)) {
509
            // fetch to some class by name
510 2
            $result = $stmt->fetchAll(\PDO::FETCH_CLASS, $object);
511
        } else {
512
            // fetch to StdClass
513 1
            $result = $stmt->fetchAll(\PDO::FETCH_OBJ);
514
        }
515
516 3
        $stmt->closeCursor();
517 3
        $this->ok();
518 3
        return $result;
519
    }
520
521
    /**
522
     * Returns an array of linked objects containing the result set
523
     *
524
     * @param  string $sql    SQL query with placeholders
525
     *                        "SELECT '__users', u.*, '__users_profile', up.*
526
     *                        FROM users u
527
     *                        LEFT JOIN users_profile up ON up.userId = u.id
528
     *                        WHERE u.name = :name"
529
     * @param  array  $params params for query placeholders (optional)
530
     *                        array (':name' => 'John')
531
     * @return array
532
     */
533
    public function fetchRelations($sql, $params = array())
534
    {
535
        $stmt = $this->prepare($sql, $params);
536
537
        $result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
538
539
        // prepare results
540
        $result = Relations::fetch($result);
541
542
        $stmt->closeCursor();
543
        $this->ok();
544
        return $result;
545
    }
546
547
    /**
548
     * Transaction wrapper
549
     *
550
     * Example of usage
551
     * <code>
552
     *     $db->transaction(function() use ($db) {
553
     *         $db->query("INSERT INTO `table` ...");
554
     *         $db->query("UPDATE `table` ...");
555
     *         $db->query("DELETE FROM `table` ...");
556
     *     })
557
     * </code>
558
     *
559
     * @param  callable $process callable structure - closure function or class with __invoke() method
560
     * @return bool
561
     * @throws DbException
562
     */
563 5
    public function transaction($process)
564
    {
565 5
        if (!is_callable($process)) {
566 1
            throw new DbException('First argument of transaction method should be callable');
567
        }
568
        try {
569 4
            $this->handler()->beginTransaction();
570 4
            call_user_func($process);
571 3
            $this->handler()->commit();
572 3
            return true;
573 1
        } catch (\PDOException $e) {
574 1
            $this->handler()->rollBack();
575 1
            return false;
576
        }
577
    }
578
579
    /**
580
     * Setup timer
581
     *
582
     * @return void
583
     */
584 26
    protected function ok()
585
    {
586 26
        $log = sprintf("--: %f", microtime(true) - $this->timer);
587 26
        Logger::info($log);
588 26
    }
589
590
    /**
591
     * Log queries by Application
592
     *
593
     * @param  string $sql     SQL query for logs
594
     * @param  array  $context
595
     * @return void
596
     */
597 26
    protected function log($sql, array $context = [])
598
    {
599 26
        $this->timer = microtime(true);
600
601 26
        $sql = str_replace('%', '%%', $sql);
602 26
        $sql = preg_replace('/\?/', '"%s"', $sql, sizeof($context));
603
604
        // replace mask by data
605 26
        $log = vsprintf("db: ". $sql, $context);
606
607 26
        Logger::info($log);
608 26
    }
609
610
    /**
611
     * Disconnect PDO and clean default adapter
612
     *
613
     * @return void
614
     */
615 21
    public function disconnect()
616
    {
617 21
        if ($this->handler) {
618 15
            $this->handler = null;
619
        }
620 21
    }
621
}
622