1 | <?php |
||||
2 | |||||
3 | namespace Bdf\Prime\Connection; |
||||
4 | |||||
5 | use Bdf\Prime\Connection\Event\ConnectionClosedListenerInterface; |
||||
6 | use Bdf\Prime\Connection\Extensions\LostConnection; |
||||
7 | use Bdf\Prime\Connection\Extensions\SchemaChanged; |
||||
8 | use Bdf\Prime\Connection\Result\DoctrineResultSet; |
||||
9 | use Bdf\Prime\Connection\Result\ResultSetInterface; |
||||
10 | use Bdf\Prime\Connection\Result\UpdateResultSet; |
||||
11 | use Bdf\Prime\Exception\DBALException; |
||||
12 | use Bdf\Prime\Exception\PrimeException; |
||||
13 | use Bdf\Prime\Exception\QueryExecutionException; |
||||
14 | use Bdf\Prime\Platform\PlatformInterface; |
||||
15 | use Bdf\Prime\Platform\Sql\SqlPlatform; |
||||
16 | use Bdf\Prime\Query\CommandInterface; |
||||
17 | use Bdf\Prime\Query\Compiler\Preprocessor\PreprocessorInterface; |
||||
18 | use Bdf\Prime\Query\Compiler\SqlCompiler; |
||||
19 | use Bdf\Prime\Query\Contract\Compilable; |
||||
20 | use Bdf\Prime\Query\Contract\Query\InsertQueryInterface; |
||||
21 | use Bdf\Prime\Query\Contract\Query\KeyValueQueryInterface; |
||||
22 | use Bdf\Prime\Query\Custom\BulkInsert\BulkInsertQuery; |
||||
23 | use Bdf\Prime\Query\Custom\BulkInsert\BulkInsertSqlCompiler; |
||||
24 | use Bdf\Prime\Query\Custom\KeyValue\KeyValueQuery; |
||||
25 | use Bdf\Prime\Query\Custom\KeyValue\KeyValueSqlCompiler; |
||||
26 | use Bdf\Prime\Query\Factory\DefaultQueryFactory; |
||||
27 | use Bdf\Prime\Query\Factory\QueryFactoryInterface; |
||||
28 | use Bdf\Prime\Query\Query; |
||||
29 | use Bdf\Prime\Query\ReadCommandInterface; |
||||
30 | use Bdf\Prime\Schema\SchemaManager; |
||||
31 | use Closure; |
||||
32 | use Doctrine\Common\EventManager; |
||||
33 | use Doctrine\DBAL\Cache\QueryCacheProfile; |
||||
34 | use Doctrine\DBAL\Configuration; |
||||
35 | use Doctrine\DBAL\Connection as BaseConnection; |
||||
36 | use Doctrine\DBAL\Driver; |
||||
37 | use Doctrine\DBAL\Exception as DoctrineDBALException; |
||||
38 | use Doctrine\DBAL\Exception\DriverException; |
||||
39 | use Doctrine\DBAL\Result; |
||||
40 | use Doctrine\DBAL\Statement; |
||||
41 | |||||
42 | /** |
||||
43 | * Connection |
||||
44 | * |
||||
45 | * @method \Bdf\Prime\Configuration getConfiguration() |
||||
46 | */ |
||||
47 | class SimpleConnection extends BaseConnection implements ConnectionInterface, TransactionManagerInterface |
||||
48 | { |
||||
49 | use LostConnection; |
||||
50 | use SchemaChanged; |
||||
51 | |||||
52 | /** |
||||
53 | * The connection name. |
||||
54 | * |
||||
55 | * @var string |
||||
56 | */ |
||||
57 | protected $name; |
||||
58 | |||||
59 | /** |
||||
60 | * The schema manager. |
||||
61 | * |
||||
62 | * @var SchemaManager |
||||
63 | */ |
||||
64 | private $schema; |
||||
65 | |||||
66 | /** |
||||
67 | * @var SqlPlatform |
||||
68 | */ |
||||
69 | private $platform; |
||||
70 | |||||
71 | /** |
||||
72 | * @var QueryFactoryInterface |
||||
73 | */ |
||||
74 | private $factory; |
||||
75 | |||||
76 | /** |
||||
77 | * SimpleConnection constructor. |
||||
78 | * |
||||
79 | * @param array $params |
||||
80 | * @param Driver $driver |
||||
81 | * @param Configuration|null $config |
||||
82 | * @param EventManager|null $eventManager |
||||
83 | * @throws DoctrineDBALException |
||||
84 | */ |
||||
85 | 402 | public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $eventManager = null) |
|||
86 | { |
||||
87 | /** @psalm-suppress InternalMethod */ |
||||
88 | 402 | parent::__construct($params, $driver, $config, $eventManager); |
|||
89 | |||||
90 | /** @psalm-suppress InvalidArgument */ |
||||
91 | 402 | $this->factory = new DefaultQueryFactory( |
|||
92 | 402 | $this, |
|||
93 | 402 | new SqlCompiler($this), |
|||
94 | 402 | [ |
|||
95 | 402 | KeyValueQuery::class => KeyValueSqlCompiler::class, |
|||
96 | 402 | BulkInsertQuery::class => BulkInsertSqlCompiler::class, |
|||
97 | 402 | ], |
|||
98 | 402 | [ |
|||
99 | 402 | KeyValueQueryInterface::class => KeyValueQuery::class, |
|||
100 | 402 | InsertQueryInterface::class => BulkInsertQuery::class, |
|||
101 | 402 | ] |
|||
102 | 402 | ); |
|||
103 | } |
||||
104 | |||||
105 | /** |
||||
106 | * {@inheritdoc} |
||||
107 | */ |
||||
108 | 403 | public function setName(string $name) |
|||
109 | { |
||||
110 | 403 | $this->name = $name; |
|||
111 | |||||
112 | 403 | return $this; |
|||
113 | } |
||||
114 | |||||
115 | /** |
||||
116 | * {@inheritdoc} |
||||
117 | */ |
||||
118 | 406 | public function getName(): string |
|||
119 | { |
||||
120 | 406 | return $this->name; |
|||
121 | } |
||||
122 | |||||
123 | /** |
||||
124 | * {@inheritdoc} |
||||
125 | */ |
||||
126 | 1015 | public function getDatabase(): ?string |
|||
127 | { |
||||
128 | 1015 | return parent::getDatabase(); |
|||
129 | } |
||||
130 | |||||
131 | /** |
||||
132 | * {@inheritdoc} |
||||
133 | */ |
||||
134 | 1 | public function isConnected() |
|||
135 | { |
||||
136 | 1 | return $this->_conn !== null; |
|||
137 | } |
||||
138 | |||||
139 | /** |
||||
140 | * {@inheritdoc} |
||||
141 | */ |
||||
142 | 1087 | public function schema(): SchemaManager |
|||
143 | { |
||||
144 | 1087 | if ($this->schema === null) { |
|||
145 | 368 | $this->schema = new SchemaManager($this); |
|||
146 | } |
||||
147 | |||||
148 | 1087 | return $this->schema; |
|||
149 | } |
||||
150 | |||||
151 | /** |
||||
152 | * {@inheritdoc} |
||||
153 | */ |
||||
154 | 1324 | public function platform(): PlatformInterface |
|||
155 | { |
||||
156 | 1324 | if ($this->platform === null) { |
|||
157 | try { |
||||
158 | 385 | $this->platform = new SqlPlatform($this->getDatabasePlatform(), $this->getConfiguration()->getTypes()); |
|||
159 | } catch (DoctrineDBALException $e) { |
||||
160 | /** @psalm-suppress InvalidScalarArgument */ |
||||
161 | throw new DBALException($e->getMessage(), $e->getCode(), $e); |
||||
162 | } |
||||
163 | } |
||||
164 | |||||
165 | 1324 | return $this->platform; |
|||
166 | } |
||||
167 | |||||
168 | /** |
||||
169 | * {@inheritdoc} |
||||
170 | */ |
||||
171 | 110 | public function fromDatabase($value, $type, array $fieldOptions = []) |
|||
172 | { |
||||
173 | 110 | return $this->platform()->types()->fromDatabase($value, $type, $fieldOptions); |
|||
174 | } |
||||
175 | |||||
176 | /** |
||||
177 | * {@inheritdoc} |
||||
178 | */ |
||||
179 | 4 | public function toDatabase($value, $type = null) |
|||
180 | { |
||||
181 | 4 | return $this->platform()->types()->toDatabase($value, $type); |
|||
182 | } |
||||
183 | |||||
184 | /** |
||||
185 | * {@inheritdoc} |
||||
186 | */ |
||||
187 | 912 | public function builder(PreprocessorInterface $preprocessor = null): Query |
|||
188 | { |
||||
189 | 912 | return $this->factory->make(Query::class, $preprocessor); |
|||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||
190 | } |
||||
191 | |||||
192 | /** |
||||
193 | * {@inheritdoc} |
||||
194 | */ |
||||
195 | 439 | public function make(string $query, PreprocessorInterface $preprocessor = null): CommandInterface |
|||
196 | { |
||||
197 | 439 | return $this->factory->make($query, $preprocessor); |
|||
198 | } |
||||
199 | |||||
200 | /** |
||||
201 | * {@inheritdoc} |
||||
202 | */ |
||||
203 | 1081 | public function factory(): QueryFactoryInterface |
|||
204 | { |
||||
205 | 1081 | return $this->factory; |
|||
206 | } |
||||
207 | |||||
208 | /** |
||||
209 | * {@inheritdoc} |
||||
210 | */ |
||||
211 | 729 | public function from($table, ?string $alias = null): Query |
|||
212 | { |
||||
213 | 729 | return $this->builder()->from($table, $alias); |
|||
214 | } |
||||
215 | |||||
216 | /** |
||||
217 | * {@inheritdoc} |
||||
218 | */ |
||||
219 | 13 | public function delete($table, array $criteria, array $types = []) |
|||
220 | { |
||||
221 | 13 | return $this->from($table)->where($criteria)->delete(); |
|||
222 | } |
||||
223 | |||||
224 | /** |
||||
225 | * {@inheritdoc} |
||||
226 | */ |
||||
227 | 1 | public function update($table, array $data, array $criteria, array $types = []) |
|||
228 | { |
||||
229 | 1 | return $this->from($table)->where($criteria)->update($data, $types); |
|||
230 | } |
||||
231 | |||||
232 | /** |
||||
233 | * {@inheritdoc} |
||||
234 | */ |
||||
235 | 619 | public function insert($table, array $data, array $types = []) |
|||
236 | { |
||||
237 | 619 | return $this->from($table)->insert($data); |
|||
238 | } |
||||
239 | |||||
240 | /** |
||||
241 | * {@inheritdoc} |
||||
242 | */ |
||||
243 | 2 | public function select($query, array $bindings = []): ResultSetInterface |
|||
244 | { |
||||
245 | 2 | return (new DoctrineResultSet($this->executeQuery($query, $bindings)))->asObject(); |
|||
246 | } |
||||
247 | |||||
248 | /** |
||||
249 | * {@inheritdoc} |
||||
250 | */ |
||||
251 | 1114 | public function executeQuery(string $sql, array $params = [], $types = [], QueryCacheProfile $qcp = null): Result |
|||
252 | { |
||||
253 | 1114 | $this->prepareLogger(); |
|||
254 | |||||
255 | 1114 | return $this->runOrReconnect(fn () => parent::executeQuery($sql, $params, $types, $qcp)); |
|||
256 | } |
||||
257 | |||||
258 | /** |
||||
259 | * {@inheritdoc} |
||||
260 | */ |
||||
261 | 1095 | public function executeStatement($sql, array $params = [], array $types = []) |
|||
262 | { |
||||
263 | 1095 | $this->prepareLogger(); |
|||
264 | |||||
265 | 1095 | return $this->runOrReconnect(fn () => parent::executeStatement($sql, $params, $types)); |
|||
266 | } |
||||
267 | |||||
268 | /** |
||||
269 | * {@inheritdoc} |
||||
270 | * |
||||
271 | * @throws PrimeException |
||||
272 | */ |
||||
273 | 519 | public function prepare(string $sql): Statement |
|||
274 | { |
||||
275 | 519 | return $this->runOrReconnect(fn () => parent::prepare($sql)); |
|||
276 | } |
||||
277 | |||||
278 | /** |
||||
279 | * {@inheritdoc} |
||||
280 | */ |
||||
281 | 863 | public function execute(Compilable $query): ResultSetInterface |
|||
282 | { |
||||
283 | try { |
||||
284 | 863 | $statement = $query->compile(); |
|||
285 | |||||
286 | 860 | if ($statement instanceof Statement) { |
|||
287 | 715 | return $this->executePrepared($statement, $query); |
|||
288 | } |
||||
289 | |||||
290 | // $statement is a SQL query |
||||
291 | 798 | if ($query->type() === Compilable::TYPE_SELECT) { |
|||
292 | 741 | return new DoctrineResultSet($this->executeQuery($statement, $query->getBindings())); |
|||
293 | } |
||||
294 | |||||
295 | 674 | return new UpdateResultSet((int) $this->executeStatement($statement, $query->getBindings())); |
|||
296 | 13 | } catch (DriverException $e) { |
|||
297 | 8 | throw new QueryExecutionException( |
|||
298 | 8 | 'Error on execute : ' . $e->getMessage(), |
|||
299 | 8 | $e->getCode(), |
|||
300 | 8 | $e, |
|||
301 | 8 | $e->getQuery() ? $e->getQuery()->getSQL() : null, |
|||
302 | 8 | $e->getQuery() ? $e->getQuery()->getParams() : null |
|||
303 | 8 | ); |
|||
304 | 5 | } catch (DoctrineDBALException $e) { |
|||
305 | /** @psalm-suppress InvalidScalarArgument */ |
||||
306 | throw new QueryExecutionException('Error on execute : '.$e->getMessage(), $e->getCode(), $e); |
||||
307 | } |
||||
308 | } |
||||
309 | |||||
310 | /** |
||||
311 | * Execute a prepared statement |
||||
312 | * |
||||
313 | * @param Statement $statement |
||||
314 | * @param Compilable $query |
||||
315 | * |
||||
316 | * @return ResultSetInterface The query result |
||||
317 | * |
||||
318 | * @throws DoctrineDBALException |
||||
319 | * @throws PrimeException |
||||
320 | * |
||||
321 | * @psalm-suppress InternalMethod |
||||
322 | */ |
||||
323 | 715 | protected function executePrepared(Statement $statement, Compilable $query) |
|||
324 | { |
||||
325 | 715 | $bindings = $query->getBindings(); |
|||
326 | 715 | $isRead = $query->type() === Compilable::TYPE_SELECT; |
|||
327 | |||||
328 | 715 | $this->prepareLogger(); |
|||
329 | |||||
330 | try { |
||||
331 | 715 | $result = $isRead |
|||
332 | 309 | ? new DoctrineResultSet($statement->executeQuery($bindings)) |
|||
333 | 715 | : new UpdateResultSet($statement->executeStatement($bindings)) |
|||
334 | 715 | ; |
|||
335 | 9 | } catch (DoctrineDBALException $exception) { |
|||
336 | // Prepared query on SQLite for PHP < 7.2 invalidates the query when schema change |
||||
337 | // This process may be removed on PHP 7.2 |
||||
338 | 9 | if ($this->causedBySchemaChange($exception)) { |
|||
339 | 2 | $statement = $query->compile(true); |
|||
340 | 2 | $result = $isRead |
|||
341 | ? new DoctrineResultSet($statement->executeQuery($query->getBindings())) |
||||
342 | 2 | : new UpdateResultSet($statement->executeStatement($query->getBindings())) |
|||
343 | 2 | ; |
|||
344 | 7 | } elseif ($this->causedByLostConnection($exception->getPrevious())) { // If the connection is lost, the query must be recompiled |
|||
345 | $this->close(); |
||||
346 | $this->connect(); |
||||
347 | |||||
348 | $statement = $query->compile(true); |
||||
349 | $result = $isRead |
||||
350 | ? new DoctrineResultSet($statement->executeQuery($query->getBindings())) |
||||
351 | : new UpdateResultSet($statement->executeStatement($query->getBindings())) |
||||
352 | ; |
||||
353 | } else { |
||||
354 | 7 | throw $exception; |
|||
355 | } |
||||
356 | } |
||||
357 | |||||
358 | 713 | return $result; |
|||
359 | } |
||||
360 | |||||
361 | /** |
||||
362 | * {@inheritdoc} |
||||
363 | */ |
||||
364 | 1101 | public function beginTransaction(): bool |
|||
365 | { |
||||
366 | 1101 | $this->prepareLogger(); |
|||
367 | |||||
368 | 1101 | return parent::beginTransaction() ?? true; |
|||
369 | } |
||||
370 | |||||
371 | /** |
||||
372 | * {@inheritdoc} |
||||
373 | */ |
||||
374 | 57 | public function commit(): bool |
|||
375 | { |
||||
376 | 57 | $this->prepareLogger(); |
|||
377 | |||||
378 | 57 | return parent::commit() ?? true; |
|||
379 | } |
||||
380 | |||||
381 | /** |
||||
382 | * {@inheritdoc} |
||||
383 | */ |
||||
384 | 1076 | public function rollBack(): bool |
|||
385 | { |
||||
386 | 1076 | $this->prepareLogger(); |
|||
387 | |||||
388 | 1076 | return parent::rollBack() ?? true; |
|||
389 | } |
||||
390 | |||||
391 | /** |
||||
392 | * {@inheritdoc} |
||||
393 | */ |
||||
394 | 16 | public function close(): void |
|||
395 | { |
||||
396 | 16 | parent::close(); |
|||
397 | |||||
398 | 16 | $this->_eventManager->dispatchEvent(ConnectionClosedListenerInterface::EVENT_NAME); |
|||
0 ignored issues
–
show
The property
Doctrine\DBAL\Connection::$_eventManager has been deprecated.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
399 | } |
||||
400 | |||||
401 | /** |
||||
402 | * Setup the logger by setting the connection |
||||
403 | * |
||||
404 | * @return void |
||||
405 | */ |
||||
406 | 1252 | protected function prepareLogger(): void |
|||
407 | { |
||||
408 | /** @psalm-suppress InternalMethod */ |
||||
409 | 1252 | $logger = $this->getConfiguration()->getSQLLogger(); |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Configuration::getSQLLogger() has been deprecated.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
410 | |||||
411 | 1252 | if ($logger && $logger instanceof ConnectionAwareInterface) { |
|||
412 | $logger->setConnection($this); |
||||
413 | } |
||||
414 | } |
||||
415 | |||||
416 | /** |
||||
417 | * Execute a query. Try to reconnect if needed |
||||
418 | * |
||||
419 | * @param Closure():T $callback |
||||
420 | * |
||||
421 | * @return T The query result |
||||
422 | * |
||||
423 | * @throws QueryExecutionException When an error occurs during query execution |
||||
424 | * @throws DBALException When any other error occurs |
||||
425 | * |
||||
426 | * @template T |
||||
427 | * |
||||
428 | * @psalm-suppress InternalMethod |
||||
429 | */ |
||||
430 | 1116 | protected function runOrReconnect(Closure $callback) |
|||
431 | { |
||||
432 | try { |
||||
433 | try { |
||||
434 | 1116 | return $callback(); |
|||
435 | 69 | } catch (DoctrineDBALException $exception) { |
|||
436 | 69 | if ($this->causedByLostConnection($exception->getPrevious())) { |
|||
437 | // Should check for active transaction. |
||||
438 | // Only reconnect the start transaction. |
||||
439 | // Should raise exception during transaction. |
||||
440 | 2 | $this->close(); |
|||
441 | /** @psalm-suppress InternalMethod */ |
||||
442 | 2 | $this->connect(); |
|||
443 | |||||
444 | 2 | return $callback(); |
|||
445 | } |
||||
446 | |||||
447 | 67 | throw $exception; |
|||
448 | } |
||||
449 | 67 | } catch (DriverException $e) { |
|||
450 | 67 | throw new QueryExecutionException( |
|||
451 | 67 | 'Error on execute : ' . $e->getMessage(), |
|||
452 | 67 | $e->getCode(), |
|||
453 | 67 | $e, |
|||
454 | 67 | $e->getQuery() ? $e->getQuery()->getSQL() : null, |
|||
455 | 67 | $e->getQuery() ? $e->getQuery()->getParams() : null |
|||
456 | 67 | ); |
|||
457 | } catch (DoctrineDBALException $e) { |
||||
458 | /** @psalm-suppress InvalidScalarArgument */ |
||||
459 | throw new DBALException('Error on execute : '.$e->getMessage(), $e->getCode(), $e); |
||||
460 | } |
||||
461 | } |
||||
462 | } |
||||
463 |