Passed
Pull Request — master (#449)
by Wilmer
31:32 queued 29:20
created

AbstractConnectionPDO::getActivePDO()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 10
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Driver\PDO;
6
7
use PDO;
8
use PDOException;
9
use Psr\Log\LogLevel;
10
use Yiisoft\Db\Cache\QueryCache;
11
use Yiisoft\Db\Cache\SchemaCache;
12
use Yiisoft\Db\Connection\AbstractConnection;
13
use Yiisoft\Db\Exception\Exception;
14
use Yiisoft\Db\Exception\InvalidCallException;
15
use Yiisoft\Db\Exception\InvalidConfigException;
16
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
17
use Yiisoft\Db\Schema\QuoterInterface;
18
use Yiisoft\Db\Schema\SchemaInterface;
19
20
use function array_keys;
21
use function is_string;
22
23
/**
24
 * The AbstractConnectionPDO class represents a connection to a database using the PDO (PHP Data Objects) extension. It
25
 * provides a set of methods for interacting with a database using PDO, such as executing SQL statements, preparing and
26
 * executing statements, and managing transactions.
27
 *
28
 * The ConnectionPDO class extends from the AbstractConnection class, which is a base class for representing a
29
 * connection to a database. It implements the ConnectionInterface, which defines the interface for interacting with a
30
 * database connection.
31
 */
32
abstract class AbstractConnectionPDO extends AbstractConnection implements ConnectionPDOInterface
33
{
34
    protected PDO|null $pdo = null;
35
    protected string $serverVersion = '';
36
    protected bool|null $emulatePrepare = null;
37
    protected QueryBuilderInterface|null $queryBuilder = null;
38
    protected QuoterInterface|null $quoter = null;
39
    protected SchemaInterface|null $schema = null;
40
41
    public function __construct(
42
        protected PDODriverInterface $driver,
43
        protected QueryCache $queryCache,
44
        protected SchemaCache $schemaCache
45
    ) {
46
        parent::__construct($queryCache);
47
    }
48
49
    /**
50
     * Reset the connection after cloning.
51
     */
52
    public function __clone()
53
    {
54
        $this->transaction = null;
55
        $this->pdo = null;
56
    }
57
58
    /**
59
     * Close the connection before serializing.
60
     */
61
    public function __sleep(): array
62
    {
63
        $fields = (array) $this;
64
65
        unset(
66
            $fields["\000*\000" . 'pdo'],
67
            $fields["\000*\000" . 'transaction'],
68
            $fields["\000*\000" . 'schema']
69
        );
70
71
        return array_keys($fields);
72
    }
73
74
    public function open(): void
75
    {
76
        if ($this->pdo instanceof PDO) {
77
            return;
78
        }
79
80
        if ($this->driver->getDsn() === '') {
81
            throw new InvalidConfigException('Connection::dsn cannot be empty.');
82
        }
83
84
        $token = 'Opening DB connection: ' . $this->driver->getDsn();
85
86
        try {
87
            $this->logger?->log(LogLevel::INFO, $token);
88
            $this->profiler?->begin($token, [__METHOD__]);
89
            $this->initConnection();
90
            $this->profiler?->end($token, [__METHOD__]);
91
        } catch (PDOException $e) {
92
            $this->profiler?->end($token, [__METHOD__]);
93
            $this->logger?->log(LogLevel::ERROR, $token);
94
95
            throw new Exception($e->getMessage(), (array) $e->errorInfo, $e);
96
        }
97
    }
98
99
    public function close(): void
100
    {
101
        if ($this->pdo !== null) {
102
            $this->logger?->log(
103
                LogLevel::DEBUG,
104
                'Closing DB connection: ' . $this->driver->getDsn() . ' ' . __METHOD__,
105
            );
106
107
            $this->pdo = null;
108
            $this->transaction = null;
109
        }
110
    }
111
112
    public function getCacheKey(): array
113
    {
114
        return [$this->driver->getDsn(), $this->driver->getUsername()];
115
    }
116
117
    public function getDriver(): PDODriverInterface
118
    {
119
        return $this->driver;
120
    }
121
122
    public function getEmulatePrepare(): bool|null
123
    {
124
        return $this->emulatePrepare;
125
    }
126
127
    public function getPDO(): PDO|null
128
    {
129
        return $this->pdo;
130
    }
131
132
    public function getActivePDO(string|null $sql = '', bool|null $forRead = null): PDO
133
    {
134
        $this->open();
135
        $pdo = $this->getPDO();
136
137
        if ($pdo === null) {
138
            throw new Exception('PDO cannot be initialized.');
139
        }
140
141
        return $pdo;
142
    }
143
144
    public function getLastInsertID(string $sequenceName = null): string
145
    {
146
        if ($this->isActive() && $this->pdo) {
147
            return $this->pdo->lastInsertID($sequenceName ?? null);
148
        }
149
150
        throw new InvalidCallException('DB Connection is not active.');
151
    }
152
153
    public function getName(): string
154
    {
155
        return $this->driver->getDriverName();
156
    }
157
158
    /**
159
     * @throws Exception
160
     * @throws InvalidConfigException
161
     */
162
    public function getServerVersion(): string
163
    {
164
        if ($this->serverVersion === '') {
165
            /** @psalm-var mixed $version */
166
            $version = $this->getActivePDO()->getAttribute(PDO::ATTR_SERVER_VERSION);
167
            $this->serverVersion = is_string($version) ? $version : 'Version could not be determined.';
168
        }
169
170
        return $this->serverVersion;
171
    }
172
173
    public function isActive(): bool
174
    {
175
        return $this->pdo !== null;
176
    }
177
178
    /**
179
     * @throws Exception
180
     * @throws InvalidConfigException
181
     */
182
    public function quoteValue(mixed $value): mixed
183
    {
184
        if (is_string($value) === false) {
185
            return $value;
186
        }
187
188
        return $this->getActivePDO()->quote($value);
189
    }
190
191
    public function setEmulatePrepare(bool $value): void
192
    {
193
        $this->emulatePrepare = $value;
194
    }
195
196
    /**
197
     * Initializes the DB connection.
198
     *
199
     * This method is invoked right after the DB connection is established.
200
     *
201
     * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`.
202
     *
203
     * if {@see emulatePrepare} is true, and sets the database {@see charset} if it is not empty.
204
     */
205
    protected function initConnection(): void
206
    {
207
        if ($this->getEmulatePrepare() !== null) {
208
            $this->driver->attributes([PDO::ATTR_EMULATE_PREPARES => $this->getEmulatePrepare()]);
209
        }
210
211
        $this->pdo = $this->driver->createConnection();
212
    }
213
}
214