Passed
Pull Request — master (#684)
by Def
02:13
created

AbstractPdoConnection   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 190
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 70
dl 0
loc 190
rs 9.76
c 0
b 0
f 0
wmc 33

18 Methods

Rating   Name   Duplication   Size   Complexity  
A isActive() 0 3 1
A rollbackTransactionOnLevel() 0 10 4
A getServerVersion() 0 9 3
A getDriverName() 0 3 1
A getLastInsertID() 0 7 2
A getActivePDO() 0 10 2
A open() 0 23 4
A close() 0 10 2
A getDriver() 0 3 1
A getPDO() 0 3 1
A beginTransaction() 0 8 3
A setEmulatePrepare() 0 3 1
A quoteValue() 0 7 2
A __clone() 0 4 1
A __sleep() 0 11 1
A __construct() 0 2 1
A getEmulatePrepare() 0 3 1
A initConnection() 0 7 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\LoggerAwareInterface;
10
use Psr\Log\LoggerAwareTrait;
11
use Psr\Log\LogLevel;
12
use Yiisoft\Db\Cache\SchemaCache;
13
use Yiisoft\Db\Connection\AbstractConnection;
14
use Yiisoft\Db\Exception\Exception;
15
use Yiisoft\Db\Exception\InvalidCallException;
16
use Yiisoft\Db\Exception\InvalidConfigException;
17
use Yiisoft\Db\Profiler\Context\ConnectionContext;
18
use Yiisoft\Db\Profiler\ProfilerAwareInterface;
19
use Yiisoft\Db\Profiler\ProfilerAwareTrait;
20
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
21
use Yiisoft\Db\Schema\QuoterInterface;
22
use Yiisoft\Db\Schema\SchemaInterface;
23
use Yiisoft\Db\Transaction\TransactionInterface;
24
25
use function array_keys;
26
use function is_string;
27
28
/**
29
 * Represents a connection to a database using the PDO (PHP Data Objects) extension.
30
 *
31
 * It provides a set of methods for interacting with a database using PDO, such as executing SQL statements, preparing
32
 * and executing statements, and managing transactions.
33
 *
34
 * The ConnectionPDO classes extend from this class, which is a base class for representing a connection to a database.
35
 *
36
 * It implements the ConnectionInterface, which defines the interface for interacting with a database connection.
37
 */
38
abstract class AbstractPdoConnection extends AbstractConnection implements PdoConnectionInterface, LoggerAwareInterface, ProfilerAwareInterface
39
{
40
    use LoggerAwareTrait;
41
    use ProfilerAwareTrait;
42
43
    protected PDO|null $pdo = null;
44
    protected string $serverVersion = '';
45
    protected bool|null $emulatePrepare = null;
46
    protected QueryBuilderInterface|null $queryBuilder = null;
47
    protected QuoterInterface|null $quoter = null;
48
    protected SchemaInterface|null $schema = null;
49
50
    public function __construct(protected PdoDriverInterface $driver, protected SchemaCache $schemaCache)
51
    {
52
    }
53
54
    /**
55
     * Reset the connection after cloning.
56
     */
57
    public function __clone()
58
    {
59
        $this->transaction = null;
60
        $this->pdo = null;
61
    }
62
63
    /**
64
     * Close the connection before serializing.
65
     */
66
    public function __sleep(): array
67
    {
68
        $fields = (array) $this;
69
70
        unset(
71
            $fields["\000*\000" . 'pdo'],
72
            $fields["\000*\000" . 'transaction'],
73
            $fields["\000*\000" . 'schema']
74
        );
75
76
        return array_keys($fields);
77
    }
78
79
    public function beginTransaction(string $isolationLevel = null): TransactionInterface
80
    {
81
        $transaction = parent::beginTransaction($isolationLevel);
82
        if ($this->logger !== null && $transaction instanceof LoggerAwareInterface) {
83
            $transaction->setLogger($this->logger);
84
        }
85
86
        return $transaction;
87
    }
88
89
    public function open(): void
90
    {
91
        if ($this->pdo instanceof PDO) {
92
            return;
93
        }
94
95
        if ($this->driver->getDsn() === '') {
96
            throw new InvalidConfigException('Connection::dsn cannot be empty.');
97
        }
98
99
        $token = 'Opening DB connection: ' . $this->driver->getDsn();
100
        $connectionContext = new ConnectionContext(__METHOD__);
101
102
        try {
103
            $this->logger?->log(LogLevel::INFO, $token);
104
            $this->profiler?->begin($token, $connectionContext);
105
            $this->initConnection();
106
            $this->profiler?->end($token, $connectionContext);
107
        } catch (PDOException $e) {
108
            $this->profiler?->end($token, $connectionContext->setException($e));
109
            $this->logger?->log(LogLevel::ERROR, $token);
110
111
            throw new Exception($e->getMessage(), (array) $e->errorInfo, $e);
112
        }
113
    }
114
115
    public function close(): void
116
    {
117
        if ($this->pdo !== null) {
118
            $this->logger?->log(
119
                LogLevel::DEBUG,
120
                'Closing DB connection: ' . $this->driver->getDsn() . ' ' . __METHOD__,
121
            );
122
123
            $this->pdo = null;
124
            $this->transaction = null;
125
        }
126
    }
127
128
    public function getDriver(): PdoDriverInterface
129
    {
130
        return $this->driver;
131
    }
132
133
    public function getEmulatePrepare(): bool|null
134
    {
135
        return $this->emulatePrepare;
136
    }
137
138
    public function getActivePDO(string|null $sql = '', bool|null $forRead = null): PDO
139
    {
140
        $this->open();
141
        $pdo = $this->getPDO();
142
143
        if ($pdo === null) {
144
            throw new Exception('PDO cannot be initialized.');
145
        }
146
147
        return $pdo;
148
    }
149
150
    public function getPDO(): PDO|null
151
    {
152
        return $this->pdo;
153
    }
154
155
    public function getLastInsertID(string $sequenceName = null): string
156
    {
157
        if ($this->pdo !== null) {
158
            return $this->pdo->lastInsertID($sequenceName ?? null);
159
        }
160
161
        throw new InvalidCallException('DB Connection is not active.');
162
    }
163
164
    public function getDriverName(): string
165
    {
166
        return $this->driver->getDriverName();
167
    }
168
169
    public function getServerVersion(): string
170
    {
171
        if ($this->serverVersion === '') {
172
            /** @psalm-var mixed $version */
173
            $version = $this->getActivePDO()->getAttribute(PDO::ATTR_SERVER_VERSION);
174
            $this->serverVersion = is_string($version) ? $version : 'Version could not be determined.';
175
        }
176
177
        return $this->serverVersion;
178
    }
179
180
    public function isActive(): bool
181
    {
182
        return $this->pdo !== null;
183
    }
184
185
    public function quoteValue(mixed $value): mixed
186
    {
187
        if (is_string($value) === false) {
188
            return $value;
189
        }
190
191
        return $this->getActivePDO()->quote($value);
192
    }
193
194
    public function setEmulatePrepare(bool $value): void
195
    {
196
        $this->emulatePrepare = $value;
197
    }
198
199
    /**
200
     * Initializes the DB connection.
201
     *
202
     * This method is invoked right after the DB connection is established.
203
     *
204
     * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`, if {@see getEmulatePrepare()} is `true`.
205
     */
206
    protected function initConnection(): void
207
    {
208
        if ($this->getEmulatePrepare() !== null) {
209
            $this->driver->attributes([PDO::ATTR_EMULATE_PREPARES => $this->getEmulatePrepare()]);
210
        }
211
212
        $this->pdo = $this->driver->createConnection();
213
    }
214
215
    /*
216
     * Exceptions thrown from rollback will be caught and just logged with {@see logger->log()}.
217
     */
218
    protected function rollbackTransactionOnLevel(TransactionInterface $transaction, int $level): void
219
    {
220
        if ($transaction->isActive() && $transaction->getLevel() === $level) {
221
            /**
222
             * @link https://github.com/yiisoft/yii2/pull/13347
223
             */
224
            try {
225
                $transaction->rollBack();
226
            } catch (Exception $e) {
227
                $this->logger?->log(LogLevel::ERROR, (string) $e, [__METHOD__]);
228
                /** hide this exception to be able to continue throwing original exception outside */
229
            }
230
        }
231
    }
232
}
233