Passed
Pull Request — master (#411)
by Def
07:49 queued 04:41
created

ConnectionPDO   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 61
dl 0
loc 185
rs 10
c 0
b 0
f 0
wmc 29

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getServerVersion() 0 9 3
A isActive() 0 3 1
A setEmulatePrepare() 0 3 1
A getPDO() 0 3 1
A getCacheKey() 0 3 1
A __clone() 0 4 1
A getEmulatePrepare() 0 3 1
A close() 0 10 2
A __construct() 0 6 1
A getDriver() 0 3 1
A __sleep() 0 11 1
A open() 0 22 4
A getActivePDO() 0 10 2
A getName() 0 3 1
A quoteValue() 0 7 2
A getLastInsertID() 0 9 4
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\LogLevel;
10
use Yiisoft\Db\Cache\QueryCache;
11
use Yiisoft\Db\Cache\SchemaCache;
12
use Yiisoft\Db\Connection\Connection;
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
abstract class ConnectionPDO extends Connection implements ConnectionPDOInterface
24
{
25
    protected PDO|null $pdo = null;
26
    protected string $serverVersion = '';
27
    protected bool|null $emulatePrepare = null;
28
    protected QueryBuilderInterface|null $queryBuilder = null;
29
    protected QuoterInterface|null $quoter = null;
30
    protected SchemaInterface|null $schema = null;
31
32
    public function __construct(
33
        protected PDODriverInterface $driver,
34
        protected QueryCache $queryCache,
35
        protected SchemaCache $schemaCache
36
    ) {
37
        parent::__construct($queryCache);
38
    }
39
40
    /**
41
     * Reset the connection after cloning.
42
     */
43
    public function __clone()
44
    {
45
        $this->transaction = null;
46
        $this->pdo = null;
47
    }
48
49
    /**
50
     * Close the connection before serializing.
51
     *
52
     * @return array
53
     */
54
    public function __sleep(): array
55
    {
56
        $fields = (array) $this;
57
58
        unset(
59
            $fields["\000*\000" . 'pdo'],
60
            $fields["\000*\000" . 'transaction'],
61
            $fields["\000*\000" . 'schema']
62
        );
63
64
        return array_keys($fields);
65
    }
66
67
    public function open(): void
68
    {
69
        if (!empty($this->pdo)) {
70
            return;
71
        }
72
73
        if (empty($this->driver->getDsn())) {
74
            throw new InvalidConfigException('Connection::dsn cannot be empty.');
75
        }
76
77
        $token = 'Opening DB connection: ' . $this->driver->getDsn();
78
79
        try {
80
            $this->logger?->log(LogLevel::INFO, $token);
81
            $this->profiler?->begin($token, [__METHOD__]);
82
            $this->initConnection();
83
            $this->profiler?->end($token, [__METHOD__]);
84
        } catch (PDOException $e) {
85
            $this->profiler?->end($token, [__METHOD__]);
86
            $this->logger?->log(LogLevel::ERROR, $token);
87
88
            throw new Exception($e->getMessage(), (array) $e->errorInfo, $e);
89
        }
90
    }
91
92
    public function close(): void
93
    {
94
        if ($this->pdo !== null) {
95
            $this->logger?->log(
96
                LogLevel::DEBUG,
97
                'Closing DB connection: ' . $this->driver->getDsn() . ' ' . __METHOD__,
98
            );
99
100
            $this->pdo = null;
101
            $this->transaction = null;
102
        }
103
    }
104
105
    public function getCacheKey(): array
106
    {
107
        return [$this->driver->getDsn(), $this->driver->getUsername()];
108
    }
109
110
    public function getDriver(): PDODriverInterface
111
    {
112
        return $this->driver;
113
    }
114
115
    public function getEmulatePrepare(): bool|null
116
    {
117
        return $this->emulatePrepare;
118
    }
119
120
    public function getPDO(): PDO|null
121
    {
122
        return $this->pdo;
123
    }
124
125
    /**
126
     * Input variables $sql and $forRead needs for future implementation of Connection + Pool
127
     *
128
     * @throws Exception
129
     * @throws InvalidConfigException
130
     */
131
    public function getActivePDO(string|null $sql = '', bool|null $forRead = null): PDO
132
    {
133
        $this->open();
134
        $pdo = $this->getPDO();
135
136
        if ($pdo === null) {
137
            throw new Exception('PDO cannot be initialized.');
138
        }
139
140
        return $pdo;
141
    }
142
143
    public function getLastInsertID(string $sequenceName = null): string
144
    {
145
        if ($this->isActive() && $this->pdo) {
146
            return $this->pdo->lastInsertID(
147
                $sequenceName === null ? null : $this->getQuoter()->quoteTableName($sequenceName)
148
            );
149
        }
150
151
        throw new InvalidCallException('DB Connection is not active.');
152
    }
153
154
    public function getName(): string
155
    {
156
        return $this->driver->getDriverName();
157
    }
158
159
    /**
160
     * @throws Exception
161
     */
162
    public function getServerVersion(): string
163
    {
164
        if ($this->serverVersion === '') {
165
            /** @var mixed */
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
    public function quoteValue(mixed $value): mixed
179
    {
180
        if (!is_string($value)) {
181
            return $value;
182
        }
183
184
        return $this->getActivePDO()->quote($value);
185
    }
186
187
    public function setEmulatePrepare(bool $value): void
188
    {
189
        $this->emulatePrepare = $value;
190
    }
191
192
    /**
193
     * Initializes the DB connection.
194
     *
195
     * This method is invoked right after the DB connection is established.
196
     *
197
     * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`.
198
     *
199
     * if {@see emulatePrepare} is true, and sets the database {@see charset} if it is not empty.
200
     */
201
    protected function initConnection(): void
202
    {
203
        if ($this->getEmulatePrepare() !== null) {
204
            $this->driver->attributes([PDO::ATTR_EMULATE_PREPARES => $this->getEmulatePrepare()]);
205
        }
206
207
        $this->pdo = $this->driver->createConnection();
208
    }
209
}
210