AbstractConnection::fetchAffected()   A
last analyzed

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