Passed
Push — orm-dm ( ff2938...834b21 )
by Nikolaos
04:00
created

AbstractConnection::fetchValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1.0156

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
ccs 3
cts 4
cp 0.75
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1.0156
1
<?php
2
3
/**
4
 * This file is part of the Phalcon Framework.
5
 *
6
 * (c) Phalcon Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 *
11
 * Implementation of this file has been influenced by AtlasPHP
12
 *
13
 * @link    https://github.com/atlasphp/Atlas.Pdo
14
 * @license https://github.com/atlasphp/Atlas.Pdo/blob/1.x/LICENSE.md
15
 */
16
17
declare(strict_types=1);
18
19
namespace Phalcon\DM\Pdo\Connection;
20
21
use BadMethodCallException;
22
use PDO;
23
use PDOStatement;
24
use Phalcon\DM\Pdo\Exception\CannotBindValue;
25
use Phalcon\DM\Pdo\Profiler\ProfilerInterface;
26
27
use function array_merge;
28
use function call_user_func_array;
29
use function func_get_args;
30
use function is_array;
31
32
/**
33
 * Provides array quoting, profiling, a new `perform()` method, new `fetch*()`
34
 * methods
35
 *
36
 * @property PDO               $pdo
37
 * @property ProfilerInterface $profiler
38
 */
39
abstract class AbstractConnection implements ConnectionInterface
40
{
41
    /**
42
     * @var PDO
43
     */
44
    protected $pdo;
45
46
    /**
47
     * @var ProfilerInterface
48
     */
49
    protected $profiler;
50
51
    /**
52
     * Proxies to PDO methods created for specific drivers; in particular,
53
     * `sqlite` and `pgsql`.
54
     *
55
     * @param string $name
56
     * @param array  $arguments
57
     *
58
     * @return mixed
59
     * @throws BadMethodCallException
60
     */
61 4
    public function __call($name, array $arguments)
62
    {
63 4
        $this->connect();
64
65 4
        if (!method_exists($this->pdo, $name)) {
66 2
            $class   = get_class($this);
67 2
            $message = "Class '" . $class
68 2
                . "' does not have a method '" . $name . "'";
69 2
            throw new BadMethodCallException($message);
70
        }
71
72 2
        return call_user_func_array([$this->pdo, $name], $arguments);
73
    }
74
75
    /**
76
     * Begins a transaction. If the profiler is enabled, the operation will
77
     * be recorded.
78
     *
79
     * @return bool
80
     */
81 4
    public function beginTransaction(): bool
82
    {
83 4
        $this->connect();
84 4
        $this->profiler->start(__FUNCTION__);
85 4
        $result = $this->pdo->beginTransaction();
86 4
        $this->profiler->finish();
87
88 4
        return $result;
89
    }
90
91
    /**
92
     * Commits the existing transaction. If the profiler is enabled, the
93
     * operation will be recorded.
94
     *
95
     * @return bool
96
     */
97 2
    public function commit(): bool
98
    {
99 2
        $this->connect();
100 2
        $this->profiler->start(__FUNCTION__);
101 2
        $result = $this->pdo->commit();
102 2
        $this->profiler->finish();
103
104 2
        return $result;
105
    }
106
107
    /**
108
     * Connects to the database.
109
     */
110
    abstract public function connect(): void;
111
112
    /**
113
     * Disconnects from the database.
114
     */
115
    abstract public function disconnect(): void;
116
117
    /**
118
     * Gets the most recent error code.
119
     *
120
     * @return string|null
121
     */
122 2
    public function errorCode(): ?string
123
    {
124 2
        $this->connect();
125
126 2
        return $this->pdo->errorCode();
127
    }
128
129
    /**
130
     * Gets the most recent error info.
131
     *
132
     * @return array
133
     */
134 2
    public function errorInfo(): array
135
    {
136 2
        $this->connect();
137
138 2
        return $this->pdo->errorInfo();
139
    }
140
141
    /**
142
     * Executes an SQL statement and returns the number of affected rows. If
143
     * the profiler is enabled, the operation will be recorded.
144
     *
145
     * @param string $statement
146
     *
147
     * @return int
148
     */
149 43
    public function exec(string $statement): int
150
    {
151 43
        $this->connect();
152 43
        $this->profiler->start(__FUNCTION__);
153 43
        $affectedRows = $this->pdo->exec($statement);
154 43
        $this->profiler->finish($statement);
155
156 43
        return $affectedRows;
157
    }
158
159
    /**
160
     * Performs a statement and returns the number of affected rows.
161
     *
162
     * @param string $statement
163
     * @param array  $values
164
     *
165
     * @return int
166
     * @throws CannotBindValue
167
     */
168 2
    public function fetchAffected(string $statement, array $values = []): int
169
    {
170 2
        $sth = $this->perform($statement, $values);
171
172 2
        return $sth->rowCount();
173
    }
174
175
    /**
176
     * Fetches a sequential array of rows from the database; the rows are
177
     * returned as associative arrays.
178
     *
179
     * @param string $statement
180
     * @param array  $values
181
     *
182
     * @return array
183
     * @throws CannotBindValue
184
     */
185 4
    public function fetchAll(string $statement, array $values = []): array
186
    {
187 4
        return $this->fetchData(
188 4
            "fetchAll",
189 4
            [PDO::FETCH_ASSOC],
190
            $statement,
191
            $values
192
        );
193
    }
194
195
    /**
196
     * Fetches an associative array of rows from the database; the rows are
197
     * returned as associative arrays, and the array of rows is keyed on the
198
     * first column of each row.
199
     *
200
     * If multiple rows have the same first column value, the last row with
201
     * that value will overwrite earlier rows. This method is more resource
202
     * intensive and should be avoided if possible.
203
     *
204
     * @param string $statement
205
     * @param array  $values
206
     *
207
     * @return array
208
     * @throws CannotBindValue
209
     */
210 2
    public function fetchAssoc(string $statement, array $values = []): array
211
    {
212 2
        $sth  = $this->perform($statement, $values);
213 2
        $data = [];
214 2
        while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
215 2
            $data[current($row)] = $row;
216
        }
217
218 2
        return $data;
219
    }
220
221
    /**
222
     * Fetches a column of rows as a sequential array (default first one).
223
     *
224
     * @param string $statement
225
     * @param array  $values
226
     * @param int    $column
227
     *
228
     * @return array
229
     * @throws CannotBindValue
230
     */
231 2
    public function fetchColumn(
232
        string $statement,
233
        array $values = [],
234
        int $column = 0
235
    ): array {
236 2
        return $this->fetchData(
237 2
            "fetchAll",
238 2
            [PDO::FETCH_COLUMN, $column],
239
            $statement,
240
            $values
241
        );
242
    }
243
244
    /**
245
     * Fetches multiple from the database as an associative array. The first
246
     * column will be the index key. The default flags are
247
     * PDO::FETCH_ASSOC | PDO::FETCH_GROUP
248
     *
249
     * @param string $statement
250
     * @param array  $values
251
     * @param int    $flags
252
     *
253
     * @return array
254
     * @throws CannotBindValue
255
     */
256 2
    public function fetchGroup(
257
        string $statement,
258
        array $values = [],
259
        int $flags = PDO::FETCH_ASSOC
260
    ): array {
261 2
        return $this->fetchData(
262 2
            "fetchAll",
263 2
            [PDO::FETCH_GROUP | $flags],
264
            $statement,
265
            $values
266
        );
267
    }
268
269
    /**
270
     * Fetches one row from the database as an object where the column values
271
     * are mapped to object properties.
272
     *
273
     * Since PDO injects property values before invoking the constructor, any
274
     * initializations for defaults that you potentially have in your object's
275
     * constructor, will override the values that have been injected by
276
     * `fetchObject`. The default object returned is `\stdClass`
277
     *
278
     * @param string $statement
279
     * @param array  $values
280
     * @param string $class
281
     * @param array  $arguments
282
     *
283
     * @return object
284
     * @throws CannotBindValue
285
     */
286 4
    public function fetchObject(
287
        string $statement,
288
        array $values = [],
289
        string $class = 'stdClass',
290
        array $arguments = []
291
    ): object {
292 4
        $sth = $this->perform($statement, $values);
293
294 4
        return $sth->fetchObject($class, $arguments);
295
    }
296
297
    /**
298
     * Fetches a sequential array of rows from the database; the rows are
299
     * returned as objects where the column values are mapped to object
300
     * properties.
301
     *
302
     * Since PDO injects property values before invoking the constructor, any
303
     * initializations for defaults that you potentially have in your object's
304
     * constructor, will override the values that have been injected by
305
     * `fetchObject`. The default object returned is `\stdClass`
306
     *
307
     * @param string $statement
308
     * @param array  $values
309
     * @param string $class
310
     * @param array  $arguments
311
     *
312
     * @return array
313
     * @throws CannotBindValue
314
     */
315 4
    public function fetchObjects(
316
        string $statement,
317
        array $values = [],
318
        string $class = 'stdClass',
319
        array $arguments = []
320
    ): array {
321 4
        $sth = $this->perform($statement, $values);
322
323 4
        return $sth->fetchAll(PDO::FETCH_CLASS, $class, $arguments);
324
    }
325
326
    /**
327
     * Fetches one row from the database as an associative array.
328
     *
329
     * @param string $statement
330
     * @param array  $values
331
     *
332
     * @return array
333
     * @throws CannotBindValue
334
     */
335 13
    public function fetchOne(string $statement, array $values = []): array
336
    {
337 13
        return $this->fetchData(
338 13
            "fetch",
339 13
            [PDO::FETCH_ASSOC],
340
            $statement,
341
            $values
342
        );
343
    }
344
345
    /**
346
     * Fetches an associative array of rows as key-value pairs (first column is
347
     * the key, second column is the value).
348
     *
349
     * @param string $statement
350
     * @param array  $values
351
     *
352
     * @return array
353
     * @throws CannotBindValue
354
     */
355 2
    public function fetchPairs(string $statement, array $values = []): array
356
    {
357 2
        return $this->fetchData(
358 2
            "fetchAll",
359 2
            [PDO::FETCH_KEY_PAIR],
360
            $statement,
361
            $values
362
        );
363
    }
364
365
    /**
366
     * Fetches the very first value (i.e., first column of the first row).
367
     *
368
     * @param string $statement
369
     * @param array  $values
370
     *
371
     * @return mixed
372
     * @throws CannotBindValue
373
     */
374 2
    public function fetchValue(string $statement, array $values = [])
375
    {
376 2
        $sth = $this->perform($statement, $values);
377
378 2
        return $sth->fetchColumn(0);
379
    }
380
381
    /**
382
     * Return the inner PDO (if any)
383
     *
384
     * @return PDO
385
     */
386 4
    public function getAdapter(): PDO
387
    {
388 4
        $this->connect();
389
390 4
        return $this->pdo;
391
    }
392
393
    /**
394
     * Retrieve a database connection attribute
395
     *
396
     * @param int $attribute
397
     *
398
     * @return mixed
399
     */
400 2
    public function getAttribute($attribute)
401
    {
402 2
        $this->connect();
403
404 2
        return $this->pdo->getAttribute($attribute);
405
    }
406
407
    /**
408
     * Return an array of available PDO drivers (empty array if none available)
409
     *
410
     * @return array
411
     */
412 2
    public static function getAvailableDrivers(): array
413
    {
414 2
        return PDO::getAvailableDrivers();
415
    }
416
417
    /**
418
     * Return the driver name
419
     *
420
     * @return string
421
     */
422 70
    public function getDriverName(): string
423
    {
424 70
        $this->connect();
425
426 70
        return $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
427
    }
428
429
    /**
430
     * Returns the Profiler instance.
431
     *
432
     * @return ProfilerInterface
433
     */
434 4
    public function getProfiler(): ProfilerInterface
435
    {
436 4
        return $this->profiler;
437
    }
438
439
    /**
440
     * Gets the quote parameters based on the driver
441
     *
442
     * @param string $driver
443
     *
444
     * @return array
445
     */
446 181
    public function getQuoteNames($driver = ""): array
447
    {
448 181
        $driver = "" === $driver ? $this->getDriverName() : $driver;
449 181
        switch ($driver) {
450 181
            case 'mysql':
451
                return [
452 92
                    "prefix"  => '`',
453
                    "suffix"  => '`',
454
                    "find"    => '`',
455
                    "replace" => '``',
456
                ];
457
458 91
            case 'sqlsrv':
459
                return [
460 2
                    "prefix"  => '[',
461
                    "suffix"  => ']',
462
                    "find"    => ']',
463
                    "replace" => '][',
464
                ];
465
466
            default:
467
                return [
468 91
                    "prefix"  => '"',
469
                    "suffix"  => '"',
470
                    "find"    => '"',
471
                    "replace" => '""',
472
                ];
473
        }
474
    }
475
476
    /**
477
     * Is a transaction currently active? If the profiler is enabled, the
478
     * operation will be recorded. If the profiler is enabled, the operation
479
     * will be recorded.
480
     *
481
     * @return bool
482
     */
483 4
    public function inTransaction(): bool
484
    {
485 4
        $this->connect();
486 4
        $this->profiler->start(__FUNCTION__);
487 4
        $result = $this->pdo->inTransaction();
488 4
        $this->profiler->finish();
489 4
        return $result;
490
    }
491
492
    /**
493
     * Is the PDO connection active?
494
     *
495
     * @return bool
496
     */
497 7
    public function isConnected(): bool
498
    {
499 7
        return (bool) $this->pdo;
500
    }
501
502
    /**
503
     * Returns the last inserted autoincrement sequence value. If the profiler
504
     * is enabled, the operation will be recorded.
505
     *
506
     * @param string $name
507
     *
508
     * @return string
509
     */
510 4
    public function lastInsertId(string $name = null): string
511
    {
512 4
        $this->connect();
513
514 4
        $this->profiler->start(__FUNCTION__);
515 4
        $result = $this->pdo->lastInsertId($name);
516 4
        $this->profiler->finish();
517
518 4
        return $result;
519
    }
520
521
    /**
522
     * Performs a query with bound values and returns the resulting
523
     * PDOStatement; array values will be passed through `quote()` and their
524
     * respective placeholders will be replaced in the query string. If the
525
     * profiler is enabled, the operation will be recorded.
526
     *
527
     * @param string $statement
528
     * @param array  $values
529
     *
530
     * @return PDOStatement
531
     */
532 37
    public function perform(string $statement, array $values = []): PDOStatement
533
    {
534 37
        $this->connect();
535
536 37
        $this->profiler->start(__FUNCTION__);
537
538 37
        $sth = $this->prepare($statement);
539 37
        foreach ($values as $name => $value) {
540 18
            $this->performBind($sth, $name, $value);
541
        }
542 37
        $sth->execute();
543
544 37
        $this->profiler->finish($statement, $values);
545
546 37
        return $sth;
547
    }
548
549
    /**
550
     * Prepares an SQL statement for execution.
551
     *
552
     * @param string $statement
553
     * @param array  $options
554
     *
555
     * @return PDOStatement|false
556
     */
557 37
    public function prepare(string $statement, array $options = [])
558
    {
559 37
        $this->connect();
560
561 37
        $this->profiler->start(__FUNCTION__);
562 37
        $sth = $this->pdo->prepare($statement, $options);
563 37
        $this->profiler->finish($sth->queryString);
564
565 37
        return $sth;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sth also could return the type boolean which is incompatible with the documented return type PDOStatement|false.
Loading history...
566
    }
567
568
    /**
569
     * Queries the database and returns a PDOStatement. If the profiler is
570
     * enabled, the operation will be recorded.
571
     *
572
     * @param string $statement
573
     * @param mixed  ...$fetch
574
     *
575
     * @return PDOStatement|false
576
     */
577 2
    public function query(string $statement)
578
    {
579 2
        $this->connect();
580
581 2
        $this->profiler->start(__FUNCTION__);
582
        $sth = call_user_func_array(
583
            [
584 2
                $this->pdo,
585 2
                "query",
586
            ],
587 2
            func_get_args()
588
        );
589 2
        $this->profiler->finish($sth->queryString);
590
591 2
        return $sth;
592
    }
593
594
    /**
595
     * Quotes a value for use in an SQL statement. This differs from
596
     * `PDO::quote()` in that it will convert an array into a string of
597
     * comma-separated quoted values. The default type is `PDO::PARAM_STR`
598
     *
599
     * @param mixed $value
600
     * @param int   $type
601
     *
602
     * @return string The quoted value.
603
     */
604 10
    public function quote($value, int $type = PDO::PARAM_STR): string
605
    {
606 10
        $this->connect();
607
608 10
        $quotes = $this->getQuoteNames();
609 10
        if (!is_array($value)) {
610 10
            $value = (string) $value;
611
612 10
            return $quotes["prefix"] . $value . $quotes["suffix"];
613
        }
614
615
        // quote array values, not keys, then combine with commas
616
        foreach ($value as $key => $element) {
617
            $element     = (string) $value;
618
            $value[$key] = $quotes["prefix"] . $element . $quotes["suffix"];
619
        }
620
621
        return implode(', ', $value);
622
    }
623
624
    /**
625
     * Rolls back the current transaction, and restores autocommit mode. If the
626
     * profiler is enabled, the operation will be recorded.
627
     *
628
     * @return bool
629
     */
630 2
    public function rollBack(): bool
631
    {
632 2
        $this->connect();
633
634 2
        $this->profiler->start(__FUNCTION__);
635 2
        $result = $this->pdo->rollBack();
636 2
        $this->profiler->finish();
637
638 2
        return $result;
639
    }
640
641
    /**
642
     * Set a database connection attribute
643
     *
644
     * @param int   $attribute
645
     * @param mixed $value
646
     *
647
     * @return bool
648
     */
649 2
    public function setAttribute(int $attribute, $value): bool
650
    {
651 2
        $this->connect();
652
653 2
        return $this->pdo->setAttribute($attribute, $value);
654
    }
655
656
    /**
657
     * Sets the Profiler instance.
658
     *
659
     * @param ProfilerInterface $profiler
660
     */
661 181
    public function setProfiler(ProfilerInterface $profiler)
662
    {
663 181
        $this->profiler = $profiler;
664 181
    }
665
666
    /**
667
     * Bind a value using the proper PDO::PARAM_* type.
668
     *
669
     * @param PDOStatement $statement
670
     * @param mixed        $name
671
     * @param mixed        $arguments
672
     */
673 18
    protected function performBind(PDOStatement $statement, $name, $arguments): void
674
    {
675 18
        if (is_int($name)) {
676 16
            $name++;
677
        }
678
679 18
        if (is_array($arguments)) {
680 4
            $type = $arguments[1] ?? PDO::PARAM_STR;
681 4
            if ($type === PDO::PARAM_BOOL && is_bool($arguments[0])) {
682 2
                $arguments[0] = $arguments[0] ? '1' : '0';
683
            }
684
685 4
            $parameters = array_merge([$name], $arguments);
686
        } else {
687 16
            $parameters = [$name, $arguments];
688
        }
689
690
        call_user_func_array(
691
            [
692 18
                $statement,
693 18
                "bindValue",
694
            ],
695
            $parameters
696
        );
697 18
    }
698
699
    /**
700
     * Helper method to get data from PDO based on the method passed
701
     *
702
     * @param string $method
703
     * @param array  $arguments
704
     * @param string $statement
705
     * @param array  $values
706
     *
707
     * @return array
708
     */
709 23
    protected function fetchData(
710
        string $method,
711
        array $arguments,
712
        string $statement,
713
        array $values = []
714
    ) {
715 23
        $sth    = $this->perform($statement, $values);
716 23
        $result = call_user_func_array([$sth, $method], $arguments);
717
718
        /**
719
         * If this returns boolean or anything other than an array, return
720
         * an empty array back
721
         */
722 23
        if (!is_array($result)) {
723 4
            $result = [];
724
        }
725
726 23
        return $result;
727
    }
728
}
729