Completed
Push — 7.4 ( abf332 )
by Nikolaos
16:13
created

AbstractConnection::commit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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 8
    public function __call($name, array $arguments)
68
    {
69 8
        $this->connect();
70
71 8
        if (!method_exists($this->pdo, $name)) {
72 4
            $class   = get_class($this);
73 4
            $message = "Class '" . $class
74 4
                . "' does not have a method '" . $name . "'";
75 4
            throw new BadMethodCallException($message);
76
        }
77
78 4
        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 8
    public function beginTransaction(): bool
88
    {
89 8
        $this->connect();
90 8
        $this->profiler->start(__FUNCTION__);
91 8
        $result = $this->pdo->beginTransaction();
92 8
        $this->profiler->finish();
93
94 8
        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 4
    public function commit(): bool
104
    {
105 4
        $this->connect();
106 4
        $this->profiler->start(__FUNCTION__);
107 4
        $result = $this->pdo->commit();
108 4
        $this->profiler->finish();
109
110 4
        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 4
    public function errorCode(): ?string
129
    {
130 4
        $this->connect();
131
132 4
        return $this->pdo->errorCode();
133
    }
134
135
    /**
136
     * Gets the most recent error info.
137
     *
138
     * @return array
139
     */
140 4
    public function errorInfo(): array
141
    {
142 4
        $this->connect();
143
144 4
        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 18
    public function exec(string $statement): int
156
    {
157 18
        $this->connect();
158 18
        $this->profiler->start(__FUNCTION__);
159 18
        $affectedRows = $this->pdo->exec($statement);
160 18
        $this->profiler->finish($statement);
161
162 18
        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 4
    public function fetchAffected(string $statement, array $values = []): int
175
    {
176 4
        $sth = $this->perform($statement, $values);
177
178 4
        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 8
    public function fetchAll(string $statement, array $values = []): array
192
    {
193 8
        return $this->fetchData(
194 8
            "fetchAll",
195 8
            [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 4
    public function fetchAssoc(string $statement, array $values = []): array
217
    {
218 4
        $sth  = $this->perform($statement, $values);
219 4
        $data = [];
220 4
        while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
221 4
            $data[current($row)] = $row;
222
        }
223
224 4
        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 4
    public function fetchColumn(
238
        string $statement,
239
        array $values = [],
240
        int $column = 0
241
    ): array {
242 4
        return $this->fetchData(
243 4
            "fetchAll",
244 4
            [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 4
    public function fetchGroup(
263
        string $statement,
264
        array $values = [],
265
        int $flags = PDO::FETCH_ASSOC
266
    ): array {
267 4
        return $this->fetchData(
268 4
            "fetchAll",
269 4
            [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 8
    public function fetchObject(
293
        string $statement,
294
        array $values = [],
295
        string $class = 'stdClass',
296
        array $arguments = []
297
    ): object {
298 8
        $sth = $this->perform($statement, $values);
299
300 8
        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 8
    public function fetchObjects(
322
        string $statement,
323
        array $values = [],
324
        string $class = 'stdClass',
325
        array $arguments = []
326
    ): array {
327 8
        $sth = $this->perform($statement, $values);
328
329 8
        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 26
    public function fetchOne(string $statement, array $values = []): array
342
    {
343 26
        return $this->fetchData(
344 26
            "fetch",
345 26
            [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 4
    public function fetchPairs(string $statement, array $values = []): array
362
    {
363 4
        return $this->fetchData(
364 4
            "fetchAll",
365 4
            [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 4
    public function fetchValue(string $statement, array $values = [])
381
    {
382 4
        $sth = $this->perform($statement, $values);
383
384 4
        return $sth->fetchColumn(0);
385
    }
386
387
    /**
388
     * Return the inner PDO (if any)
389
     *
390
     * @return PDO
391
     */
392 92
    public function getAdapter(): PDO
393
    {
394 92
        $this->connect();
395
396 92
        return $this->pdo;
397
    }
398
399
    /**
400
     * Retrieve a database connection attribute
401
     *
402
     * @param int $attribute
403
     *
404
     * @return mixed
405
     */
406 4
    public function getAttribute($attribute)
407
    {
408 4
        $this->connect();
409
410 4
        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 4
    public static function getAvailableDrivers(): array
419
    {
420 4
        return PDO::getAvailableDrivers();
421
    }
422
423
    /**
424
     * Return the driver name
425
     *
426
     * @return string
427
     */
428 140
    public function getDriverName(): string
429
    {
430 140
        $this->connect();
431
432 140
        return $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
433
    }
434
435
    /**
436
     * Returns the Profiler instance.
437
     *
438
     * @return ProfilerInterface
439
     */
440 8
    public function getProfiler(): ProfilerInterface
441
    {
442 8
        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 354
    public function getQuoteNames($driver = ""): array
453
    {
454 354
        $driver = "" === $driver ? $this->getDriverName() : $driver;
455 354
        switch ($driver) {
456 354
            case 'mysql':
457
                return [
458 180
                    "prefix"  => '`',
459
                    "suffix"  => '`',
460
                    "find"    => '`',
461
                    "replace" => '``',
462
                ];
463
464 178
            case 'sqlsrv':
465
                return [
466 4
                    "prefix"  => '[',
467
                    "suffix"  => ']',
468
                    "find"    => ']',
469
                    "replace" => '][',
470
                ];
471
472
            default:
473
                return [
474 178
                    "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 8
    public function inTransaction(): bool
490
    {
491 8
        $this->connect();
492 8
        $this->profiler->start(__FUNCTION__);
493 8
        $result = $this->pdo->inTransaction();
494 8
        $this->profiler->finish();
495 8
        return $result;
496
    }
497
498
    /**
499
     * Is the PDO connection active?
500
     *
501
     * @return bool
502
     */
503 14
    public function isConnected(): bool
504
    {
505 14
        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 8
    public function lastInsertId(string $name = null): string
517
    {
518 8
        $this->connect();
519
520 8
        $this->profiler->start(__FUNCTION__);
521 8
        $result = $this->pdo->lastInsertId($name);
522 8
        $this->profiler->finish();
523
524 8
        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 74
    public function perform(string $statement, array $values = []): PDOStatement
539
    {
540 74
        $this->connect();
541
542 74
        $this->profiler->start(__FUNCTION__);
543
544 74
        $sth = $this->prepare($statement);
545 74
        foreach ($values as $name => $value) {
546 36
            $this->performBind($sth, $name, $value);
0 ignored issues
show
Security Bug introduced by
It seems like $sth defined by $this->prepare($statement) on line 544 can also be of type false; however, Phalcon\DataMapper\Pdo\C...nnection::performBind() does only seem to accept object<PDOStatement>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
547
        }
548 74
        $sth->execute();
549
550 74
        $this->profiler->finish($statement, $values);
551
552 74
        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 74
    public function prepare(string $statement, array $options = [])
564
    {
565 74
        $this->connect();
566
567 74
        $this->profiler->start(__FUNCTION__);
568 74
        $sth = $this->pdo->prepare($statement, $options);
569 74
        $this->profiler->finish($sth->queryString);
570
571 74
        return $sth;
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 4
    public function query(string $statement)
584
    {
585 4
        $this->connect();
586
587 4
        $this->profiler->start(__FUNCTION__);
588
        $sth = call_user_func_array(
589
            [
590 4
                $this->pdo,
591 4
                "query",
592
            ],
593 4
            func_get_args()
594
        );
595 4
        $this->profiler->finish($sth->queryString);
596
597 4
        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 20
    public function quote($value, int $type = PDO::PARAM_STR): string
611
    {
612 20
        $this->connect();
613
614 20
        $quotes = $this->getQuoteNames();
615 20
        if (!is_array($value)) {
616 20
            $value = (string) $value;
617
618 20
            return $quotes["prefix"] . $value . $quotes["suffix"];
619
        }
620
621
        // quote array values, not keys, then combine with commas
622 4
        foreach ($value as $key => $element) {
623 4
            $element     = (string) $element;
624 4
            $value[$key] = $quotes["prefix"] . $element . $quotes["suffix"];
625
        }
626
627 4
        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 4
    public function rollBack(): bool
637
    {
638 4
        $this->connect();
639
640 4
        $this->profiler->start(__FUNCTION__);
641 4
        $result = $this->pdo->rollBack();
642 4
        $this->profiler->finish();
643
644 4
        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 4
    public function setAttribute(int $attribute, $value): bool
656
    {
657 4
        $this->connect();
658
659 4
        return $this->pdo->setAttribute($attribute, $value);
660
    }
661
662
    /**
663
     * Sets the Profiler instance.
664
     *
665
     * @param ProfilerInterface $profiler
666
     */
667 362
    public function setProfiler(ProfilerInterface $profiler)
668
    {
669 362
        $this->profiler = $profiler;
670 362
    }
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 36
    protected function performBind(PDOStatement $statement, $name, $arguments): void
680
    {
681 36
        if (is_int($name)) {
682 32
            $name++;
683
        }
684
685 36
        if (is_array($arguments)) {
686 8
            $type = $arguments[1] ?? PDO::PARAM_STR;
687 8
            if ($type === PDO::PARAM_BOOL && is_bool($arguments[0])) {
688 4
                $arguments[0] = $arguments[0] ? '1' : '0';
689
            }
690
691 8
            $parameters = array_merge([$name], $arguments);
692
        } else {
693 32
            $parameters = [$name, $arguments];
694
        }
695
696
        call_user_func_array(
697
            [
698 36
                $statement,
699 36
                "bindValue",
700
            ],
701
            $parameters
702
        );
703 36
    }
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 46
    protected function fetchData(
716
        string $method,
717
        array $arguments,
718
        string $statement,
719
        array $values = []
720
    ) {
721 46
        $sth    = $this->perform($statement, $values);
722 46
        $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 46
        if (!is_array($result)) {
729 8
            $result = [];
730
        }
731
732 46
        return $result;
733
    }
734
}
735