Passed
Pull Request — master (#801)
by Sergei
03:00
created

AbstractPdoConnection   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 197
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 73
c 1
b 0
f 0
dl 0
loc 197
rs 9.68
wmc 34

19 Methods

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

204
        $this->quoter?->/** @scrutinizer ignore-call */ 
205
                        setTablePrefix($value);
Loading history...
205
    }
206
207
    /**
208
     * Initializes the DB connection.
209
     *
210
     * This method is invoked right after the DB connection is established.
211
     *
212
     * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`, if {@see getEmulatePrepare()} is `true`.
213
     */
214
    protected function initConnection(): void
215
    {
216
        if ($this->getEmulatePrepare() !== null) {
217
            $this->driver->attributes([PDO::ATTR_EMULATE_PREPARES => $this->getEmulatePrepare()]);
218
        }
219
220
        $this->pdo = $this->driver->createConnection();
221
    }
222
223
    /*
224
     * Exceptions thrown from rollback will be caught and just logged with {@see logger->log()}.
225
     */
226
    protected function rollbackTransactionOnLevel(TransactionInterface $transaction, int $level): void
227
    {
228
        if ($transaction->isActive() && $transaction->getLevel() === $level) {
229
            /**
230
             * @link https://github.com/yiisoft/yii2/pull/13347
231
             */
232
            try {
233
                $transaction->rollBack();
234
            } catch (Throwable $e) {
235
                $this->logger?->log(LogLevel::ERROR, (string) $e, [__METHOD__, 'type' => LogType::TRANSACTION]);
236
                /** hide this exception to be able to continue throwing original exception outside */
237
            }
238
        }
239
    }
240
}
241