1 | <?php |
||
2 | /* |
||
3 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||
4 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||
5 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||
6 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||
7 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||
8 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||
9 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||
10 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||
11 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
12 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||
13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
14 | * |
||
15 | * This software consists of voluntary contributions made by many individuals |
||
16 | * and is licensed under the MIT license. For more information, see |
||
17 | * <http://www.doctrine-project.org>. |
||
18 | */ |
||
19 | |||
20 | namespace Doctrine\DBAL; |
||
21 | |||
22 | use Doctrine\DBAL\Driver\ServerInfoAwareConnection; |
||
23 | use Doctrine\DBAL\Exception\InvalidArgumentException; |
||
24 | use PDO; |
||
25 | use Closure; |
||
26 | use Exception; |
||
27 | use Doctrine\DBAL\Types\Type; |
||
28 | use Doctrine\DBAL\Driver\Connection as DriverConnection; |
||
29 | use Doctrine\Common\EventManager; |
||
30 | use Doctrine\DBAL\Cache\ResultCacheStatement; |
||
31 | use Doctrine\DBAL\Cache\QueryCacheProfile; |
||
32 | use Doctrine\DBAL\Cache\ArrayStatement; |
||
33 | use Doctrine\DBAL\Cache\CacheException; |
||
34 | use Doctrine\DBAL\Driver\PingableConnection; |
||
35 | use Throwable; |
||
36 | |||
37 | /** |
||
38 | * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like |
||
39 | * events, transaction isolation levels, configuration, emulated transaction nesting, |
||
40 | * lazy connecting and more. |
||
41 | * |
||
42 | * @link www.doctrine-project.org |
||
43 | * @since 2.0 |
||
44 | * @author Guilherme Blanco <[email protected]> |
||
45 | * @author Jonathan Wage <[email protected]> |
||
46 | * @author Roman Borschel <[email protected]> |
||
47 | * @author Konsta Vesterinen <[email protected]> |
||
48 | * @author Lukas Smith <[email protected]> (MDB2 library) |
||
49 | * @author Benjamin Eberlei <[email protected]> |
||
50 | */ |
||
51 | class Connection implements DriverConnection |
||
52 | { |
||
53 | /** |
||
54 | * Constant for transaction isolation level READ UNCOMMITTED. |
||
55 | */ |
||
56 | const TRANSACTION_READ_UNCOMMITTED = 1; |
||
57 | |||
58 | /** |
||
59 | * Constant for transaction isolation level READ COMMITTED. |
||
60 | */ |
||
61 | const TRANSACTION_READ_COMMITTED = 2; |
||
62 | |||
63 | /** |
||
64 | * Constant for transaction isolation level REPEATABLE READ. |
||
65 | */ |
||
66 | const TRANSACTION_REPEATABLE_READ = 3; |
||
67 | |||
68 | /** |
||
69 | * Constant for transaction isolation level SERIALIZABLE. |
||
70 | */ |
||
71 | const TRANSACTION_SERIALIZABLE = 4; |
||
72 | |||
73 | /** |
||
74 | * Represents an array of ints to be expanded by Doctrine SQL parsing. |
||
75 | * |
||
76 | * @var integer |
||
77 | */ |
||
78 | const PARAM_INT_ARRAY = 101; |
||
79 | |||
80 | /** |
||
81 | * Represents an array of strings to be expanded by Doctrine SQL parsing. |
||
82 | * |
||
83 | * @var integer |
||
84 | */ |
||
85 | const PARAM_STR_ARRAY = 102; |
||
86 | |||
87 | /** |
||
88 | * Offset by which PARAM_* constants are detected as arrays of the param type. |
||
89 | * |
||
90 | * @var integer |
||
91 | */ |
||
92 | const ARRAY_PARAM_OFFSET = 100; |
||
93 | |||
94 | /** |
||
95 | * The wrapped driver connection. |
||
96 | * |
||
97 | * @var \Doctrine\DBAL\Driver\Connection |
||
98 | */ |
||
99 | protected $_conn; |
||
100 | |||
101 | /** |
||
102 | * @var \Doctrine\DBAL\Configuration |
||
103 | */ |
||
104 | protected $_config; |
||
105 | |||
106 | /** |
||
107 | * @var \Doctrine\Common\EventManager |
||
108 | */ |
||
109 | protected $_eventManager; |
||
110 | |||
111 | /** |
||
112 | * @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder |
||
113 | */ |
||
114 | protected $_expr; |
||
115 | |||
116 | /** |
||
117 | * Whether or not a connection has been established. |
||
118 | * |
||
119 | * @var boolean |
||
120 | */ |
||
121 | private $_isConnected = false; |
||
122 | |||
123 | /** |
||
124 | * The current auto-commit mode of this connection. |
||
125 | * |
||
126 | * @var boolean |
||
127 | */ |
||
128 | private $autoCommit = true; |
||
129 | |||
130 | /** |
||
131 | * The transaction nesting level. |
||
132 | * |
||
133 | * @var integer |
||
134 | */ |
||
135 | private $_transactionNestingLevel = 0; |
||
136 | |||
137 | /** |
||
138 | * The currently active transaction isolation level. |
||
139 | * |
||
140 | * @var integer |
||
141 | */ |
||
142 | private $_transactionIsolationLevel; |
||
143 | |||
144 | /** |
||
145 | * If nested transactions should use savepoints. |
||
146 | * |
||
147 | * @var boolean |
||
148 | */ |
||
149 | private $_nestTransactionsWithSavepoints = false; |
||
150 | |||
151 | /** |
||
152 | * The parameters used during creation of the Connection instance. |
||
153 | * |
||
154 | * @var array |
||
155 | */ |
||
156 | private $_params = []; |
||
157 | |||
158 | /** |
||
159 | * The DatabasePlatform object that provides information about the |
||
160 | * database platform used by the connection. |
||
161 | * |
||
162 | * @var \Doctrine\DBAL\Platforms\AbstractPlatform |
||
163 | */ |
||
164 | private $platform; |
||
165 | |||
166 | /** |
||
167 | * The schema manager. |
||
168 | * |
||
169 | * @var \Doctrine\DBAL\Schema\AbstractSchemaManager |
||
170 | */ |
||
171 | protected $_schemaManager; |
||
172 | |||
173 | /** |
||
174 | * The used DBAL driver. |
||
175 | * |
||
176 | * @var \Doctrine\DBAL\Driver |
||
177 | */ |
||
178 | protected $_driver; |
||
179 | |||
180 | /** |
||
181 | * Flag that indicates whether the current transaction is marked for rollback only. |
||
182 | * |
||
183 | * @var boolean |
||
184 | */ |
||
185 | private $_isRollbackOnly = false; |
||
186 | |||
187 | /** |
||
188 | * @var integer |
||
189 | */ |
||
190 | protected $defaultFetchMode = PDO::FETCH_ASSOC; |
||
191 | |||
192 | /** |
||
193 | * Initializes a new instance of the Connection class. |
||
194 | * |
||
195 | * @param array $params The connection parameters. |
||
196 | * @param \Doctrine\DBAL\Driver $driver The driver to use. |
||
197 | * @param \Doctrine\DBAL\Configuration|null $config The configuration, optional. |
||
198 | * @param \Doctrine\Common\EventManager|null $eventManager The event manager, optional. |
||
199 | * |
||
200 | * @throws \Doctrine\DBAL\DBALException |
||
201 | */ |
||
202 | 131 | public function __construct(array $params, Driver $driver, Configuration $config = null, |
|
203 | EventManager $eventManager = null) |
||
204 | { |
||
205 | 131 | $this->_driver = $driver; |
|
206 | 131 | $this->_params = $params; |
|
207 | |||
208 | 131 | if (isset($params['pdo'])) { |
|
209 | 12 | $this->_conn = $params['pdo']; |
|
210 | 12 | $this->_isConnected = true; |
|
211 | 12 | unset($this->_params['pdo']); |
|
212 | } |
||
213 | |||
214 | 131 | if (isset($params["platform"])) { |
|
215 | 28 | if ( ! $params['platform'] instanceof Platforms\AbstractPlatform) { |
|
216 | 1 | throw DBALException::invalidPlatformType($params['platform']); |
|
217 | } |
||
218 | |||
219 | 27 | $this->platform = $params["platform"]; |
|
220 | 27 | unset($this->_params["platform"]); |
|
221 | } |
||
222 | |||
223 | // Create default config and event manager if none given |
||
224 | 131 | if ( ! $config) { |
|
225 | 32 | $config = new Configuration(); |
|
226 | } |
||
227 | |||
228 | 131 | if ( ! $eventManager) { |
|
229 | 32 | $eventManager = new EventManager(); |
|
230 | } |
||
231 | |||
232 | 131 | $this->_config = $config; |
|
233 | 131 | $this->_eventManager = $eventManager; |
|
234 | |||
235 | 131 | $this->_expr = new Query\Expression\ExpressionBuilder($this); |
|
236 | |||
237 | 131 | $this->autoCommit = $config->getAutoCommit(); |
|
238 | 131 | } |
|
239 | |||
240 | /** |
||
241 | * Gets the parameters used during instantiation. |
||
242 | * |
||
243 | * @return array |
||
244 | */ |
||
245 | 97 | public function getParams() |
|
246 | { |
||
247 | 97 | return $this->_params; |
|
248 | } |
||
249 | |||
250 | /** |
||
251 | * Gets the name of the database this Connection is connected to. |
||
252 | * |
||
253 | * @return string |
||
254 | */ |
||
255 | 48 | public function getDatabase() |
|
256 | { |
||
257 | 48 | return $this->_driver->getDatabase($this); |
|
258 | } |
||
259 | |||
260 | /** |
||
261 | * Gets the hostname of the currently connected database. |
||
262 | * |
||
263 | * @return string|null |
||
264 | */ |
||
265 | 1 | public function getHost() |
|
266 | { |
||
267 | 1 | return isset($this->_params['host']) ? $this->_params['host'] : null; |
|
268 | } |
||
269 | |||
270 | /** |
||
271 | * Gets the port of the currently connected database. |
||
272 | * |
||
273 | * @return mixed |
||
274 | */ |
||
275 | 1 | public function getPort() |
|
276 | { |
||
277 | 1 | return isset($this->_params['port']) ? $this->_params['port'] : null; |
|
278 | } |
||
279 | |||
280 | /** |
||
281 | * Gets the username used by this connection. |
||
282 | * |
||
283 | * @return string|null |
||
284 | */ |
||
285 | 1 | public function getUsername() |
|
286 | { |
||
287 | 1 | return isset($this->_params['user']) ? $this->_params['user'] : null; |
|
288 | } |
||
289 | |||
290 | /** |
||
291 | * Gets the password used by this connection. |
||
292 | * |
||
293 | * @return string|null |
||
294 | */ |
||
295 | 1 | public function getPassword() |
|
296 | { |
||
297 | 1 | return isset($this->_params['password']) ? $this->_params['password'] : null; |
|
298 | } |
||
299 | |||
300 | /** |
||
301 | * Gets the DBAL driver instance. |
||
302 | * |
||
303 | * @return \Doctrine\DBAL\Driver |
||
304 | */ |
||
305 | 52 | public function getDriver() |
|
306 | { |
||
307 | 52 | return $this->_driver; |
|
308 | } |
||
309 | |||
310 | /** |
||
311 | * Gets the Configuration used by the Connection. |
||
312 | * |
||
313 | * @return \Doctrine\DBAL\Configuration |
||
314 | */ |
||
315 | 256 | public function getConfiguration() |
|
316 | { |
||
317 | 256 | return $this->_config; |
|
318 | } |
||
319 | |||
320 | /** |
||
321 | * Gets the EventManager used by the Connection. |
||
322 | * |
||
323 | * @return \Doctrine\Common\EventManager |
||
324 | */ |
||
325 | 10 | public function getEventManager() |
|
326 | { |
||
327 | 10 | return $this->_eventManager; |
|
328 | } |
||
329 | |||
330 | /** |
||
331 | * Gets the DatabasePlatform for the connection. |
||
332 | * |
||
333 | * @return \Doctrine\DBAL\Platforms\AbstractPlatform |
||
334 | * |
||
335 | * @throws \Doctrine\DBAL\DBALException |
||
336 | */ |
||
337 | 201 | public function getDatabasePlatform() |
|
338 | { |
||
339 | 201 | if (null === $this->platform) { |
|
340 | 27 | $this->detectDatabasePlatform(); |
|
341 | } |
||
342 | |||
343 | 200 | return $this->platform; |
|
344 | } |
||
345 | |||
346 | /** |
||
347 | * Gets the ExpressionBuilder for the connection. |
||
348 | * |
||
349 | * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder |
||
350 | */ |
||
351 | public function getExpressionBuilder() |
||
352 | { |
||
353 | return $this->_expr; |
||
354 | } |
||
355 | |||
356 | /** |
||
357 | * Establishes the connection with the database. |
||
358 | * |
||
359 | * @return boolean TRUE if the connection was successfully established, FALSE if |
||
360 | * the connection is already open. |
||
361 | */ |
||
362 | 262 | public function connect() |
|
363 | { |
||
364 | 262 | if ($this->_isConnected) { |
|
365 | 245 | return false; |
|
366 | } |
||
367 | |||
368 | 46 | $driverOptions = isset($this->_params['driverOptions']) ? |
|
369 | 46 | $this->_params['driverOptions'] : []; |
|
370 | 46 | $user = isset($this->_params['user']) ? $this->_params['user'] : null; |
|
371 | 46 | $password = isset($this->_params['password']) ? |
|
372 | 46 | $this->_params['password'] : null; |
|
373 | |||
374 | 46 | $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions); |
|
375 | 44 | $this->_isConnected = true; |
|
376 | |||
377 | 44 | if (false === $this->autoCommit) { |
|
378 | 3 | $this->beginTransaction(); |
|
379 | } |
||
380 | |||
381 | 44 | View Code Duplication | if ($this->_eventManager->hasListeners(Events::postConnect)) { |
382 | 1 | $eventArgs = new Event\ConnectionEventArgs($this); |
|
383 | 1 | $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); |
|
384 | } |
||
385 | |||
386 | 44 | return true; |
|
387 | } |
||
388 | |||
389 | /** |
||
390 | * Detects and sets the database platform. |
||
391 | * |
||
392 | * Evaluates custom platform class and version in order to set the correct platform. |
||
393 | * |
||
394 | * @throws DBALException if an invalid platform was specified for this connection. |
||
395 | */ |
||
396 | 27 | private function detectDatabasePlatform() |
|
397 | { |
||
398 | 27 | $version = $this->getDatabasePlatformVersion(); |
|
399 | |||
400 | 26 | if (null !== $version) { |
|
401 | 1 | $this->platform = $this->_driver->createDatabasePlatformForVersion($version); |
|
402 | } else { |
||
403 | 25 | $this->platform = $this->_driver->getDatabasePlatform(); |
|
404 | } |
||
405 | |||
406 | 26 | $this->platform->setEventManager($this->_eventManager); |
|
407 | 26 | } |
|
408 | |||
409 | /** |
||
410 | * Returns the version of the related platform if applicable. |
||
411 | * |
||
412 | * Returns null if either the driver is not capable to create version |
||
413 | * specific platform instances, no explicit server version was specified |
||
414 | * or the underlying driver connection cannot determine the platform |
||
415 | * version without having to query it (performance reasons). |
||
416 | * |
||
417 | * @return string|null |
||
418 | * |
||
419 | * @throws Exception |
||
420 | */ |
||
421 | 27 | private function getDatabasePlatformVersion() |
|
422 | { |
||
423 | // Driver does not support version specific platforms. |
||
424 | 27 | if ( ! $this->_driver instanceof VersionAwarePlatformDriver) { |
|
425 | 24 | return null; |
|
426 | } |
||
427 | |||
428 | // Explicit platform version requested (supersedes auto-detection). |
||
429 | 3 | if (isset($this->_params['serverVersion'])) { |
|
430 | return $this->_params['serverVersion']; |
||
431 | } |
||
432 | |||
433 | // If not connected, we need to connect now to determine the platform version. |
||
434 | 3 | if (null === $this->_conn) { |
|
435 | try { |
||
436 | 3 | $this->connect(); |
|
437 | 1 | } catch (\Exception $originalException) { |
|
438 | 1 | if (empty($this->_params['dbname'])) { |
|
439 | throw $originalException; |
||
440 | } |
||
441 | |||
442 | // The database to connect to might not yet exist. |
||
443 | // Retry detection without database name connection parameter. |
||
444 | 1 | $databaseName = $this->_params['dbname']; |
|
445 | 1 | $this->_params['dbname'] = null; |
|
446 | |||
447 | try { |
||
448 | 1 | $this->connect(); |
|
449 | 1 | } catch (\Exception $fallbackException) { |
|
450 | // Either the platform does not support database-less connections |
||
451 | // or something else went wrong. |
||
452 | // Reset connection parameters and rethrow the original exception. |
||
453 | 1 | $this->_params['dbname'] = $databaseName; |
|
454 | |||
455 | 1 | throw $originalException; |
|
456 | } |
||
457 | |||
458 | // Reset connection parameters. |
||
459 | $this->_params['dbname'] = $databaseName; |
||
460 | $serverVersion = $this->getServerVersion(); |
||
461 | |||
462 | // Close "temporary" connection to allow connecting to the real database again. |
||
463 | $this->close(); |
||
464 | |||
465 | return $serverVersion; |
||
466 | } |
||
467 | |||
468 | } |
||
469 | |||
470 | 2 | return $this->getServerVersion(); |
|
471 | } |
||
472 | |||
473 | /** |
||
474 | * Returns the database server version if the underlying driver supports it. |
||
475 | * |
||
476 | * @return string|null |
||
477 | */ |
||
478 | 2 | private function getServerVersion() |
|
479 | { |
||
480 | // Automatic platform version detection. |
||
481 | 2 | if ($this->_conn instanceof ServerInfoAwareConnection && |
|
482 | 2 | ! $this->_conn->requiresQueryForServerVersion() |
|
483 | ) { |
||
484 | 1 | return $this->_conn->getServerVersion(); |
|
485 | } |
||
486 | |||
487 | // Unable to detect platform version. |
||
488 | 1 | return null; |
|
489 | } |
||
490 | |||
491 | /** |
||
492 | * Returns the current auto-commit mode for this connection. |
||
493 | * |
||
494 | * @return boolean True if auto-commit mode is currently enabled for this connection, false otherwise. |
||
495 | * |
||
496 | * @see setAutoCommit |
||
497 | */ |
||
498 | 2 | public function isAutoCommit() |
|
499 | { |
||
500 | 2 | return true === $this->autoCommit; |
|
501 | } |
||
502 | |||
503 | /** |
||
504 | * Sets auto-commit mode for this connection. |
||
505 | * |
||
506 | * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual |
||
507 | * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either |
||
508 | * the method commit or the method rollback. By default, new connections are in auto-commit mode. |
||
509 | * |
||
510 | * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is |
||
511 | * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op. |
||
512 | * |
||
513 | * @param boolean $autoCommit True to enable auto-commit mode; false to disable it. |
||
514 | * |
||
515 | * @see isAutoCommit |
||
516 | */ |
||
517 | 5 | public function setAutoCommit($autoCommit) |
|
518 | { |
||
519 | 5 | $autoCommit = (boolean) $autoCommit; |
|
520 | |||
521 | // Mode not changed, no-op. |
||
522 | 5 | if ($autoCommit === $this->autoCommit) { |
|
523 | 1 | return; |
|
524 | } |
||
525 | |||
526 | 5 | $this->autoCommit = $autoCommit; |
|
527 | |||
528 | // Commit all currently active transactions if any when switching auto-commit mode. |
||
529 | 5 | if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) { |
|
530 | 1 | $this->commitAll(); |
|
531 | } |
||
532 | 5 | } |
|
533 | |||
534 | /** |
||
535 | * Sets the fetch mode. |
||
536 | * |
||
537 | * @param integer $fetchMode |
||
538 | * |
||
539 | * @return void |
||
540 | */ |
||
541 | 1 | public function setFetchMode($fetchMode) |
|
542 | { |
||
543 | 1 | $this->defaultFetchMode = $fetchMode; |
|
544 | 1 | } |
|
545 | |||
546 | /** |
||
547 | * Prepares and executes an SQL query and returns the first row of the result |
||
548 | * as an associative array. |
||
549 | * |
||
550 | * @param string $statement The SQL query. |
||
551 | * @param array $params The query parameters. |
||
552 | * @param array $types The query parameter types. |
||
553 | * |
||
554 | * @return array|bool False is returned if no rows are found. |
||
555 | * |
||
556 | * @throws \Doctrine\DBAL\DBALException |
||
557 | */ |
||
558 | 42 | public function fetchAssoc($statement, array $params = [], array $types = []) |
|
559 | { |
||
560 | 42 | return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_ASSOC); |
|
561 | } |
||
562 | |||
563 | /** |
||
564 | * Prepares and executes an SQL query and returns the first row of the result |
||
565 | * as a numerically indexed array. |
||
566 | * |
||
567 | * @param string $statement The SQL query to be executed. |
||
568 | * @param array $params The prepared statement params. |
||
569 | * @param array $types The query parameter types. |
||
570 | * |
||
571 | * @return array|bool False is returned if no rows are found. |
||
572 | */ |
||
573 | 4 | public function fetchArray($statement, array $params = [], array $types = []) |
|
574 | { |
||
575 | 4 | return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_NUM); |
|
576 | } |
||
577 | |||
578 | /** |
||
579 | * Prepares and executes an SQL query and returns the value of a single column |
||
580 | * of the first row of the result. |
||
581 | * |
||
582 | * @param string $statement The SQL query to be executed. |
||
583 | * @param array $params The prepared statement params. |
||
584 | * @param integer $column The 0-indexed column number to retrieve. |
||
585 | * @param array $types The query parameter types. |
||
586 | * |
||
587 | * @return mixed|bool False is returned if no rows are found. |
||
588 | * |
||
589 | * @throws \Doctrine\DBAL\DBALException |
||
590 | */ |
||
591 | 28 | public function fetchColumn($statement, array $params = [], $column = 0, array $types = []) |
|
592 | { |
||
593 | 28 | return $this->executeQuery($statement, $params, $types)->fetchColumn($column); |
|
594 | } |
||
595 | |||
596 | /** |
||
597 | * Whether an actual connection to the database is established. |
||
598 | * |
||
599 | * @return boolean |
||
600 | */ |
||
601 | 4 | public function isConnected() |
|
602 | { |
||
603 | 4 | return $this->_isConnected; |
|
604 | } |
||
605 | |||
606 | /** |
||
607 | * Checks whether a transaction is currently active. |
||
608 | * |
||
609 | * @return boolean TRUE if a transaction is currently active, FALSE otherwise. |
||
610 | */ |
||
611 | 255 | public function isTransactionActive() |
|
612 | { |
||
613 | 255 | return $this->_transactionNestingLevel > 0; |
|
614 | } |
||
615 | |||
616 | /** |
||
617 | * Gathers conditions for an update or delete call. |
||
618 | * |
||
619 | * @param array $identifiers Input array of columns to values |
||
620 | * |
||
621 | * @return string[][] a triplet with: |
||
622 | * - the first key being the columns |
||
623 | * - the second key being the values |
||
624 | * - the third key being the conditions |
||
625 | */ |
||
626 | 13 | private function gatherConditions(array $identifiers) |
|
627 | { |
||
628 | 13 | $columns = []; |
|
629 | 13 | $values = []; |
|
630 | 13 | $conditions = []; |
|
631 | |||
632 | 13 | foreach ($identifiers as $columnName => $value) { |
|
633 | 13 | if (null === $value) { |
|
634 | 4 | $conditions[] = $this->getDatabasePlatform()->getIsNullExpression($columnName); |
|
635 | 4 | continue; |
|
636 | } |
||
637 | |||
638 | 11 | $columns[] = $columnName; |
|
639 | 11 | $values[] = $value; |
|
640 | 11 | $conditions[] = $columnName . ' = ?'; |
|
641 | } |
||
642 | |||
643 | 13 | return [$columns, $values, $conditions]; |
|
644 | } |
||
645 | |||
646 | /** |
||
647 | * Executes an SQL DELETE statement on a table. |
||
648 | * |
||
649 | * Table expression and columns are not escaped and are not safe for user-input. |
||
650 | * |
||
651 | * @param string $tableExpression The expression of the table on which to delete. |
||
652 | * @param array $identifier The deletion criteria. An associative array containing column-value pairs. |
||
653 | * @param array $types The types of identifiers. |
||
654 | * |
||
655 | * @return integer The number of affected rows. |
||
656 | * |
||
657 | * @throws \Doctrine\DBAL\DBALException |
||
658 | * @throws InvalidArgumentException |
||
659 | */ |
||
660 | 6 | public function delete($tableExpression, array $identifier, array $types = []) |
|
661 | { |
||
662 | 6 | if (empty($identifier)) { |
|
663 | 1 | throw InvalidArgumentException::fromEmptyCriteria(); |
|
664 | } |
||
665 | |||
666 | 5 | list($columns, $values, $conditions) = $this->gatherConditions($identifier); |
|
667 | |||
668 | 5 | return $this->executeUpdate( |
|
669 | 5 | 'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions), |
|
670 | 5 | $values, |
|
671 | 5 | is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types |
|
672 | ); |
||
673 | } |
||
674 | |||
675 | /** |
||
676 | * Closes the connection. |
||
677 | * |
||
678 | * @return void |
||
679 | */ |
||
680 | 28 | public function close() |
|
681 | { |
||
682 | 28 | $this->_conn = null; |
|
683 | |||
684 | 28 | $this->_isConnected = false; |
|
685 | 28 | } |
|
686 | |||
687 | /** |
||
688 | * Sets the transaction isolation level. |
||
689 | * |
||
690 | * @param integer $level The level to set. |
||
691 | * |
||
692 | * @return integer |
||
693 | */ |
||
694 | public function setTransactionIsolation($level) |
||
695 | { |
||
696 | $this->_transactionIsolationLevel = $level; |
||
697 | |||
698 | return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level)); |
||
699 | } |
||
700 | |||
701 | /** |
||
702 | * Gets the currently active transaction isolation level. |
||
703 | * |
||
704 | * @return integer The current transaction isolation level. |
||
705 | */ |
||
706 | public function getTransactionIsolation() |
||
707 | { |
||
708 | if (null === $this->_transactionIsolationLevel) { |
||
709 | $this->_transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel(); |
||
710 | } |
||
711 | |||
712 | return $this->_transactionIsolationLevel; |
||
713 | } |
||
714 | |||
715 | /** |
||
716 | * Executes an SQL UPDATE statement on a table. |
||
717 | * |
||
718 | * Table expression and columns are not escaped and are not safe for user-input. |
||
719 | * |
||
720 | * @param string $tableExpression The expression of the table to update quoted or unquoted. |
||
721 | * @param array $data An associative array containing column-value pairs. |
||
722 | * @param array $identifier The update criteria. An associative array containing column-value pairs. |
||
723 | * @param array $types Types of the merged $data and $identifier arrays in that order. |
||
724 | * |
||
725 | * @return integer The number of affected rows. |
||
726 | * |
||
727 | * @throws \Doctrine\DBAL\DBALException |
||
728 | */ |
||
729 | 8 | public function update($tableExpression, array $data, array $identifier, array $types = []) |
|
730 | { |
||
731 | 8 | $setColumns = []; |
|
732 | 8 | $setValues = []; |
|
733 | 8 | $set = []; |
|
734 | |||
735 | 8 | foreach ($data as $columnName => $value) { |
|
736 | 8 | $setColumns[] = $columnName; |
|
737 | 8 | $setValues[] = $value; |
|
738 | 8 | $set[] = $columnName . ' = ?'; |
|
739 | } |
||
740 | |||
741 | 8 | list($conditionColumns, $conditionValues, $conditions) = $this->gatherConditions($identifier); |
|
742 | 8 | $columns = array_merge($setColumns, $conditionColumns); |
|
743 | 8 | $values = array_merge($setValues, $conditionValues); |
|
744 | |||
745 | 8 | if (is_string(key($types))) { |
|
746 | 5 | $types = $this->extractTypeValues($columns, $types); |
|
747 | } |
||
748 | |||
749 | 8 | $sql = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set) |
|
750 | 8 | . ' WHERE ' . implode(' AND ', $conditions); |
|
751 | |||
752 | 8 | return $this->executeUpdate($sql, $values, $types); |
|
753 | } |
||
754 | |||
755 | /** |
||
756 | * Inserts a table row with specified data. |
||
757 | * |
||
758 | * Table expression and columns are not escaped and are not safe for user-input. |
||
759 | * |
||
760 | * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted. |
||
761 | * @param array $data An associative array containing column-value pairs. |
||
762 | * @param array $types Types of the inserted data. |
||
763 | * |
||
764 | * @return integer The number of affected rows. |
||
765 | * |
||
766 | * @throws \Doctrine\DBAL\DBALException |
||
767 | */ |
||
768 | 78 | public function insert($tableExpression, array $data, array $types = []) |
|
769 | { |
||
770 | 78 | if (empty($data)) { |
|
771 | 1 | return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()'); |
|
772 | } |
||
773 | |||
774 | 77 | $columns = []; |
|
775 | 77 | $values = []; |
|
776 | 77 | $set = []; |
|
777 | |||
778 | 77 | foreach ($data as $columnName => $value) { |
|
779 | 77 | $columns[] = $columnName; |
|
780 | 77 | $values[] = $value; |
|
781 | 77 | $set[] = '?'; |
|
782 | } |
||
783 | |||
784 | 77 | return $this->executeUpdate( |
|
785 | 77 | 'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' . |
|
786 | 77 | ' VALUES (' . implode(', ', $set) . ')', |
|
787 | 77 | $values, |
|
788 | 77 | is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types |
|
789 | ); |
||
790 | } |
||
791 | |||
792 | /** |
||
793 | * Extract ordered type list from an ordered column list and type map. |
||
794 | * |
||
795 | * @param array $columnList |
||
796 | * @param array $types |
||
797 | * |
||
798 | * @return array |
||
799 | */ |
||
800 | 9 | private function extractTypeValues(array $columnList, array $types) |
|
801 | { |
||
802 | 9 | $typeValues = []; |
|
803 | |||
804 | 9 | foreach ($columnList as $columnIndex => $columnName) { |
|
805 | 9 | $typeValues[] = isset($types[$columnName]) |
|
806 | 9 | ? $types[$columnName] |
|
807 | 9 | : \PDO::PARAM_STR; |
|
808 | } |
||
809 | |||
810 | 9 | return $typeValues; |
|
811 | } |
||
812 | |||
813 | /** |
||
814 | * Quotes a string so it can be safely used as a table or column name, even if |
||
815 | * it is a reserved name. |
||
816 | * |
||
817 | * Delimiting style depends on the underlying database platform that is being used. |
||
818 | * |
||
819 | * NOTE: Just because you CAN use quoted identifiers does not mean |
||
820 | * you SHOULD use them. In general, they end up causing way more |
||
821 | * problems than they solve. |
||
822 | * |
||
823 | * @param string $str The name to be quoted. |
||
824 | * |
||
825 | * @return string The quoted name. |
||
826 | */ |
||
827 | 1 | public function quoteIdentifier($str) |
|
828 | { |
||
829 | 1 | return $this->getDatabasePlatform()->quoteIdentifier($str); |
|
830 | } |
||
831 | |||
832 | /** |
||
833 | * Quotes a given input parameter. |
||
834 | * |
||
835 | * @param mixed $input The parameter to be quoted. |
||
836 | * @param int|null $type The type of the parameter. |
||
837 | * |
||
838 | * @return string The quoted parameter. |
||
839 | */ |
||
840 | 4 | public function quote($input, $type = null) |
|
841 | { |
||
842 | 4 | $this->connect(); |
|
843 | |||
844 | 4 | list($value, $bindingType) = $this->getBindingInfo($input, $type); |
|
845 | |||
846 | 4 | return $this->_conn->quote($value, $bindingType); |
|
847 | } |
||
848 | |||
849 | /** |
||
850 | * Prepares and executes an SQL query and returns the result as an associative array. |
||
851 | * |
||
852 | * @param string $sql The SQL query. |
||
853 | * @param array $params The query parameters. |
||
854 | * @param array $types The query parameter types. |
||
855 | * |
||
856 | * @return array |
||
857 | */ |
||
858 | 81 | public function fetchAll($sql, array $params = [], $types = []) |
|
859 | { |
||
860 | 81 | return $this->executeQuery($sql, $params, $types)->fetchAll(); |
|
861 | } |
||
862 | |||
863 | /** |
||
864 | * Prepares an SQL statement. |
||
865 | * |
||
866 | * @param string $statement The SQL statement to prepare. |
||
867 | * |
||
868 | * @return \Doctrine\DBAL\Statement The prepared statement. |
||
869 | * |
||
870 | * @throws \Doctrine\DBAL\DBALException |
||
871 | */ |
||
872 | 40 | public function prepare($statement) |
|
873 | { |
||
874 | try { |
||
875 | 40 | $stmt = new Statement($statement, $this); |
|
876 | 1 | } catch (Exception $ex) { |
|
877 | 1 | throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement); |
|
878 | } |
||
879 | |||
880 | 39 | $stmt->setFetchMode($this->defaultFetchMode); |
|
881 | |||
882 | 39 | return $stmt; |
|
883 | } |
||
884 | |||
885 | /** |
||
886 | * Executes an, optionally parametrized, SQL query. |
||
887 | * |
||
888 | * If the query is parametrized, a prepared statement is used. |
||
889 | * If an SQLLogger is configured, the execution is logged. |
||
890 | * |
||
891 | * @param string $query The SQL query to execute. |
||
892 | * @param array $params The parameters to bind to the query, if any. |
||
893 | * @param array $types The types the previous parameters are in. |
||
894 | * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp The query cache profile, optional. |
||
895 | * |
||
896 | * @return \Doctrine\DBAL\Driver\Statement The executed statement. |
||
897 | * |
||
898 | * @throws \Doctrine\DBAL\DBALException |
||
899 | */ |
||
900 | 177 | public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null) |
|
901 | { |
||
902 | 177 | if ($qcp !== null) { |
|
903 | 10 | return $this->executeCacheQuery($query, $params, $types, $qcp); |
|
904 | } |
||
905 | |||
906 | 177 | $this->connect(); |
|
907 | |||
908 | 177 | $logger = $this->_config->getSQLLogger(); |
|
909 | 177 | if ($logger) { |
|
910 | 172 | $logger->startQuery($query, $params, $types); |
|
911 | } |
||
912 | |||
913 | try { |
||
914 | 177 | View Code Duplication | if ($params) { |
915 | 31 | list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types); |
|
916 | |||
917 | 31 | $stmt = $this->_conn->prepare($query); |
|
918 | 31 | if ($types) { |
|
919 | 15 | $this->_bindTypedValues($stmt, $params, $types); |
|
920 | 15 | $stmt->execute(); |
|
921 | } else { |
||
922 | 31 | $stmt->execute($params); |
|
923 | } |
||
924 | } else { |
||
925 | 173 | $stmt = $this->_conn->query($query); |
|
926 | } |
||
927 | 8 | } catch (Exception $ex) { |
|
928 | 8 | throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types)); |
|
929 | } |
||
930 | |||
931 | 169 | $stmt->setFetchMode($this->defaultFetchMode); |
|
932 | |||
933 | 169 | if ($logger) { |
|
934 | 165 | $logger->stopQuery(); |
|
935 | } |
||
936 | |||
937 | 169 | return $stmt; |
|
938 | } |
||
939 | |||
940 | /** |
||
941 | * Executes a caching query. |
||
942 | * |
||
943 | * @param string $query The SQL query to execute. |
||
944 | * @param array $params The parameters to bind to the query, if any. |
||
945 | * @param array $types The types the previous parameters are in. |
||
946 | * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp The query cache profile. |
||
947 | * |
||
948 | * @return \Doctrine\DBAL\Driver\ResultStatement |
||
949 | * |
||
950 | * @throws \Doctrine\DBAL\Cache\CacheException |
||
951 | */ |
||
952 | 12 | public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp) |
|
953 | { |
||
954 | 12 | $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl(); |
|
955 | 12 | if ( ! $resultCache) { |
|
956 | throw CacheException::noResultDriverConfigured(); |
||
957 | } |
||
958 | |||
959 | 12 | list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types, $this->getParams()); |
|
960 | |||
961 | // fetch the row pointers entry |
||
962 | 12 | if ($data = $resultCache->fetch($cacheKey)) { |
|
963 | // is the real key part of this row pointers map or is the cache only pointing to other cache keys? |
||
964 | 9 | if (isset($data[$realKey])) { |
|
965 | 9 | $stmt = new ArrayStatement($data[$realKey]); |
|
966 | } elseif (array_key_exists($realKey, $data)) { |
||
967 | $stmt = new ArrayStatement([]); |
||
968 | } |
||
969 | } |
||
970 | |||
971 | 12 | if (!isset($stmt)) { |
|
972 | 10 | $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime()); |
|
973 | } |
||
974 | |||
975 | 12 | $stmt->setFetchMode($this->defaultFetchMode); |
|
976 | |||
977 | 12 | return $stmt; |
|
978 | } |
||
979 | |||
980 | /** |
||
981 | * Executes an, optionally parametrized, SQL query and returns the result, |
||
982 | * applying a given projection/transformation function on each row of the result. |
||
983 | * |
||
984 | * @param string $query The SQL query to execute. |
||
985 | * @param array $params The parameters, if any. |
||
986 | * @param \Closure $function The transformation function that is applied on each row. |
||
987 | * The function receives a single parameter, an array, that |
||
988 | * represents a row of the result set. |
||
989 | * |
||
990 | * @return array The projected result of the query. |
||
991 | */ |
||
992 | public function project($query, array $params, Closure $function) |
||
993 | { |
||
994 | $result = []; |
||
995 | $stmt = $this->executeQuery($query, $params); |
||
996 | |||
997 | while ($row = $stmt->fetch()) { |
||
998 | $result[] = $function($row); |
||
999 | } |
||
1000 | |||
1001 | $stmt->closeCursor(); |
||
1002 | |||
1003 | return $result; |
||
1004 | } |
||
1005 | |||
1006 | /** |
||
1007 | * Executes an SQL statement, returning a result set as a Statement object. |
||
1008 | * |
||
1009 | * @return \Doctrine\DBAL\Driver\Statement |
||
1010 | * |
||
1011 | * @throws \Doctrine\DBAL\DBALException |
||
1012 | */ |
||
1013 | 8 | public function query() |
|
1014 | { |
||
1015 | 8 | $this->connect(); |
|
1016 | |||
1017 | 8 | $args = func_get_args(); |
|
1018 | |||
1019 | 8 | $logger = $this->_config->getSQLLogger(); |
|
1020 | 8 | if ($logger) { |
|
1021 | 7 | $logger->startQuery($args[0]); |
|
1022 | } |
||
1023 | |||
1024 | try { |
||
1025 | 8 | $statement = $this->_conn->query(...$args); |
|
1026 | 1 | } catch (Exception $ex) { |
|
1027 | 1 | throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]); |
|
1028 | } |
||
1029 | |||
1030 | 7 | $statement->setFetchMode($this->defaultFetchMode); |
|
1031 | |||
1032 | 7 | if ($logger) { |
|
1033 | 7 | $logger->stopQuery(); |
|
1034 | } |
||
1035 | |||
1036 | 7 | return $statement; |
|
1037 | } |
||
1038 | |||
1039 | /** |
||
1040 | * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters |
||
1041 | * and returns the number of affected rows. |
||
1042 | * |
||
1043 | * This method supports PDO binding types as well as DBAL mapping types. |
||
1044 | * |
||
1045 | * @param string $query The SQL query. |
||
1046 | * @param array $params The query parameters. |
||
1047 | * @param array $types The parameter types. |
||
1048 | * |
||
1049 | * @return integer The number of affected rows. |
||
1050 | * |
||
1051 | * @throws \Doctrine\DBAL\DBALException |
||
1052 | */ |
||
1053 | 141 | public function executeUpdate($query, array $params = [], array $types = []) |
|
1054 | { |
||
1055 | 141 | $this->connect(); |
|
1056 | |||
1057 | 141 | $logger = $this->_config->getSQLLogger(); |
|
1058 | 141 | if ($logger) { |
|
1059 | 136 | $logger->startQuery($query, $params, $types); |
|
1060 | } |
||
1061 | |||
1062 | try { |
||
1063 | 141 | View Code Duplication | if ($params) { |
1064 | 83 | list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types); |
|
1065 | |||
1066 | 83 | $stmt = $this->_conn->prepare($query); |
|
1067 | 82 | if ($types) { |
|
1068 | 12 | $this->_bindTypedValues($stmt, $params, $types); |
|
1069 | 12 | $stmt->execute(); |
|
1070 | } else { |
||
1071 | 70 | $stmt->execute($params); |
|
1072 | } |
||
1073 | 81 | $result = $stmt->rowCount(); |
|
1074 | } else { |
||
1075 | 139 | $result = $this->_conn->exec($query); |
|
1076 | } |
||
1077 | 52 | } catch (Exception $ex) { |
|
1078 | 52 | throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types)); |
|
1079 | } |
||
1080 | |||
1081 | 138 | if ($logger) { |
|
1082 | 134 | $logger->stopQuery(); |
|
1083 | } |
||
1084 | |||
1085 | 138 | return $result; |
|
1086 | } |
||
1087 | |||
1088 | /** |
||
1089 | * Executes an SQL statement and return the number of affected rows. |
||
1090 | * |
||
1091 | * @param string $statement |
||
1092 | * |
||
1093 | * @return integer The number of affected rows. |
||
1094 | * |
||
1095 | * @throws \Doctrine\DBAL\DBALException |
||
1096 | */ |
||
1097 | 33 | public function exec($statement) |
|
1098 | { |
||
1099 | 33 | $this->connect(); |
|
1100 | |||
1101 | 32 | $logger = $this->_config->getSQLLogger(); |
|
1102 | 32 | if ($logger) { |
|
1103 | 28 | $logger->startQuery($statement); |
|
1104 | } |
||
1105 | |||
1106 | try { |
||
1107 | 32 | $result = $this->_conn->exec($statement); |
|
1108 | 4 | } catch (Exception $ex) { |
|
1109 | 4 | throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement); |
|
1110 | } |
||
1111 | |||
1112 | 30 | if ($logger) { |
|
1113 | 28 | $logger->stopQuery(); |
|
1114 | } |
||
1115 | |||
1116 | 30 | return $result; |
|
1117 | } |
||
1118 | |||
1119 | /** |
||
1120 | * Returns the current transaction nesting level. |
||
1121 | * |
||
1122 | * @return integer The nesting level. A value of 0 means there's no active transaction. |
||
1123 | */ |
||
1124 | 15 | public function getTransactionNestingLevel() |
|
1125 | { |
||
1126 | 15 | return $this->_transactionNestingLevel; |
|
1127 | } |
||
1128 | |||
1129 | /** |
||
1130 | * Fetches the SQLSTATE associated with the last database operation. |
||
1131 | * |
||
1132 | * @return integer The last error code. |
||
1133 | */ |
||
1134 | public function errorCode() |
||
1135 | { |
||
1136 | $this->connect(); |
||
1137 | |||
1138 | return $this->_conn->errorCode(); |
||
1139 | } |
||
1140 | |||
1141 | /** |
||
1142 | * Fetches extended error information associated with the last database operation. |
||
1143 | * |
||
1144 | * @return array The last error information. |
||
1145 | */ |
||
1146 | public function errorInfo() |
||
1147 | { |
||
1148 | $this->connect(); |
||
1149 | |||
1150 | return $this->_conn->errorInfo(); |
||
1151 | } |
||
1152 | |||
1153 | /** |
||
1154 | * Returns the ID of the last inserted row, or the last value from a sequence object, |
||
1155 | * depending on the underlying driver. |
||
1156 | * |
||
1157 | * Note: This method may not return a meaningful or consistent result across different drivers, |
||
1158 | * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY |
||
1159 | * columns or sequences. |
||
1160 | * |
||
1161 | * @param string|null $seqName Name of the sequence object from which the ID should be returned. |
||
1162 | * |
||
1163 | * @return string A string representation of the last inserted ID. |
||
1164 | */ |
||
1165 | 2 | public function lastInsertId($seqName = null) |
|
1166 | { |
||
1167 | 2 | $this->connect(); |
|
1168 | |||
1169 | 2 | return $this->_conn->lastInsertId($seqName); |
|
1170 | } |
||
1171 | |||
1172 | /** |
||
1173 | * Executes a function in a transaction. |
||
1174 | * |
||
1175 | * The function gets passed this Connection instance as an (optional) parameter. |
||
1176 | * |
||
1177 | * If an exception occurs during execution of the function or transaction commit, |
||
1178 | * the transaction is rolled back and the exception re-thrown. |
||
1179 | * |
||
1180 | * @param \Closure $func The function to execute transactionally. |
||
1181 | * |
||
1182 | * @return mixed The value returned by $func |
||
1183 | * |
||
1184 | * @throws Exception |
||
1185 | * @throws Throwable |
||
1186 | */ |
||
1187 | 4 | public function transactional(Closure $func) |
|
1188 | { |
||
1189 | 4 | $this->beginTransaction(); |
|
1190 | try { |
||
1191 | 4 | $res = $func($this); |
|
1192 | 2 | $this->commit(); |
|
1193 | 2 | return $res; |
|
1194 | 2 | } catch (Exception $e) { |
|
1195 | 1 | $this->rollBack(); |
|
1196 | 1 | throw $e; |
|
1197 | 1 | } catch (Throwable $e) { |
|
1198 | 1 | $this->rollBack(); |
|
1199 | 1 | throw $e; |
|
1200 | } |
||
1201 | } |
||
1202 | |||
1203 | /** |
||
1204 | * Sets if nested transactions should use savepoints. |
||
1205 | * |
||
1206 | * @param boolean $nestTransactionsWithSavepoints |
||
1207 | * |
||
1208 | * @return void |
||
1209 | * |
||
1210 | * @throws \Doctrine\DBAL\ConnectionException |
||
1211 | */ |
||
1212 | 2 | public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints) |
|
1213 | { |
||
1214 | 2 | if ($this->_transactionNestingLevel > 0) { |
|
1215 | 2 | throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction(); |
|
1216 | } |
||
1217 | |||
1218 | 1 | if ( ! $this->getDatabasePlatform()->supportsSavepoints()) { |
|
1219 | throw ConnectionException::savepointsNotSupported(); |
||
1220 | } |
||
1221 | |||
1222 | 1 | $this->_nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints; |
|
1223 | 1 | } |
|
1224 | |||
1225 | /** |
||
1226 | * Gets if nested transactions should use savepoints. |
||
1227 | * |
||
1228 | * @return boolean |
||
1229 | */ |
||
1230 | 1 | public function getNestTransactionsWithSavepoints() |
|
1231 | { |
||
1232 | 1 | return $this->_nestTransactionsWithSavepoints; |
|
1233 | } |
||
1234 | |||
1235 | /** |
||
1236 | * Returns the savepoint name to use for nested transactions are false if they are not supported |
||
1237 | * "savepointFormat" parameter is not set |
||
1238 | * |
||
1239 | * @return mixed A string with the savepoint name or false. |
||
1240 | */ |
||
1241 | 1 | protected function _getNestedTransactionSavePointName() |
|
1242 | { |
||
1243 | 1 | return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel; |
|
1244 | } |
||
1245 | |||
1246 | /** |
||
1247 | * Starts a transaction by suspending auto-commit mode. |
||
1248 | * |
||
1249 | * @return void |
||
1250 | */ |
||
1251 | 18 | public function beginTransaction() |
|
1252 | { |
||
1253 | 18 | $this->connect(); |
|
1254 | |||
1255 | 18 | ++$this->_transactionNestingLevel; |
|
1256 | |||
1257 | 18 | $logger = $this->_config->getSQLLogger(); |
|
1258 | |||
1259 | 18 | View Code Duplication | if ($this->_transactionNestingLevel == 1) { |
1260 | 18 | if ($logger) { |
|
1261 | 13 | $logger->startQuery('"START TRANSACTION"'); |
|
1262 | } |
||
1263 | 18 | $this->_conn->beginTransaction(); |
|
1264 | 18 | if ($logger) { |
|
1265 | 18 | $logger->stopQuery(); |
|
1266 | } |
||
1267 | 3 | } elseif ($this->_nestTransactionsWithSavepoints) { |
|
1268 | 1 | if ($logger) { |
|
1269 | 1 | $logger->startQuery('"SAVEPOINT"'); |
|
1270 | } |
||
1271 | 1 | $this->createSavepoint($this->_getNestedTransactionSavePointName()); |
|
1272 | 1 | if ($logger) { |
|
1273 | 1 | $logger->stopQuery(); |
|
1274 | } |
||
1275 | } |
||
1276 | 18 | } |
|
1277 | |||
1278 | /** |
||
1279 | * Commits the current transaction. |
||
1280 | * |
||
1281 | * @return void |
||
1282 | * |
||
1283 | * @throws \Doctrine\DBAL\ConnectionException If the commit failed due to no active transaction or |
||
1284 | * because the transaction was marked for rollback only. |
||
1285 | */ |
||
1286 | 9 | public function commit() |
|
1287 | { |
||
1288 | 9 | if ($this->_transactionNestingLevel == 0) { |
|
1289 | 1 | throw ConnectionException::noActiveTransaction(); |
|
1290 | } |
||
1291 | 8 | if ($this->_isRollbackOnly) { |
|
1292 | 2 | throw ConnectionException::commitFailedRollbackOnly(); |
|
1293 | } |
||
1294 | |||
1295 | 6 | $this->connect(); |
|
1296 | |||
1297 | 6 | $logger = $this->_config->getSQLLogger(); |
|
1298 | |||
1299 | 6 | View Code Duplication | if ($this->_transactionNestingLevel == 1) { |
1300 | 6 | if ($logger) { |
|
1301 | 4 | $logger->startQuery('"COMMIT"'); |
|
1302 | } |
||
1303 | 6 | $this->_conn->commit(); |
|
1304 | 6 | if ($logger) { |
|
1305 | 6 | $logger->stopQuery(); |
|
1306 | } |
||
1307 | 2 | } elseif ($this->_nestTransactionsWithSavepoints) { |
|
1308 | 1 | if ($logger) { |
|
1309 | 1 | $logger->startQuery('"RELEASE SAVEPOINT"'); |
|
1310 | } |
||
1311 | 1 | $this->releaseSavepoint($this->_getNestedTransactionSavePointName()); |
|
1312 | 1 | if ($logger) { |
|
1313 | 1 | $logger->stopQuery(); |
|
1314 | } |
||
1315 | } |
||
1316 | |||
1317 | 6 | --$this->_transactionNestingLevel; |
|
1318 | |||
1319 | 6 | if (false === $this->autoCommit && 0 === $this->_transactionNestingLevel) { |
|
1320 | 2 | $this->beginTransaction(); |
|
1321 | } |
||
1322 | 6 | } |
|
1323 | |||
1324 | /** |
||
1325 | * Commits all current nesting transactions. |
||
1326 | */ |
||
1327 | 1 | private function commitAll() |
|
1328 | { |
||
1329 | 1 | while (0 !== $this->_transactionNestingLevel) { |
|
1330 | 1 | if (false === $this->autoCommit && 1 === $this->_transactionNestingLevel) { |
|
1331 | // When in no auto-commit mode, the last nesting commit immediately starts a new transaction. |
||
1332 | // Therefore we need to do the final commit here and then leave to avoid an infinite loop. |
||
1333 | 1 | $this->commit(); |
|
1334 | |||
1335 | 1 | return; |
|
1336 | } |
||
1337 | |||
1338 | 1 | $this->commit(); |
|
1339 | } |
||
1340 | 1 | } |
|
1341 | |||
1342 | /** |
||
1343 | * Cancels any database changes done during the current transaction. |
||
1344 | * |
||
1345 | * @throws \Doctrine\DBAL\ConnectionException If the rollback operation failed. |
||
1346 | */ |
||
1347 | 12 | public function rollBack() |
|
1348 | { |
||
1349 | 12 | if ($this->_transactionNestingLevel == 0) { |
|
1350 | 1 | throw ConnectionException::noActiveTransaction(); |
|
1351 | } |
||
1352 | |||
1353 | 11 | $this->connect(); |
|
1354 | |||
1355 | 11 | $logger = $this->_config->getSQLLogger(); |
|
1356 | |||
1357 | 11 | if ($this->_transactionNestingLevel == 1) { |
|
1358 | 10 | if ($logger) { |
|
1359 | 9 | $logger->startQuery('"ROLLBACK"'); |
|
1360 | } |
||
1361 | 10 | $this->_transactionNestingLevel = 0; |
|
1362 | 10 | $this->_conn->rollBack(); |
|
1363 | 10 | $this->_isRollbackOnly = false; |
|
1364 | 10 | if ($logger) { |
|
1365 | 9 | $logger->stopQuery(); |
|
1366 | } |
||
1367 | |||
1368 | 10 | if (false === $this->autoCommit) { |
|
1369 | 10 | $this->beginTransaction(); |
|
1370 | } |
||
1371 | 2 | } elseif ($this->_nestTransactionsWithSavepoints) { |
|
1372 | 1 | if ($logger) { |
|
1373 | 1 | $logger->startQuery('"ROLLBACK TO SAVEPOINT"'); |
|
1374 | } |
||
1375 | 1 | $this->rollbackSavepoint($this->_getNestedTransactionSavePointName()); |
|
1376 | 1 | --$this->_transactionNestingLevel; |
|
1377 | 1 | if ($logger) { |
|
1378 | 1 | $logger->stopQuery(); |
|
1379 | } |
||
1380 | } else { |
||
1381 | 1 | $this->_isRollbackOnly = true; |
|
1382 | 1 | --$this->_transactionNestingLevel; |
|
1383 | } |
||
1384 | 11 | } |
|
1385 | |||
1386 | /** |
||
1387 | * Creates a new savepoint. |
||
1388 | * |
||
1389 | * @param string $savepoint The name of the savepoint to create. |
||
1390 | * |
||
1391 | * @return void |
||
1392 | * |
||
1393 | * @throws \Doctrine\DBAL\ConnectionException |
||
1394 | */ |
||
1395 | 1 | public function createSavepoint($savepoint) |
|
1396 | { |
||
1397 | 1 | if ( ! $this->getDatabasePlatform()->supportsSavepoints()) { |
|
1398 | throw ConnectionException::savepointsNotSupported(); |
||
1399 | } |
||
1400 | |||
1401 | 1 | $this->_conn->exec($this->platform->createSavePoint($savepoint)); |
|
1402 | 1 | } |
|
1403 | |||
1404 | /** |
||
1405 | * Releases the given savepoint. |
||
1406 | * |
||
1407 | * @param string $savepoint The name of the savepoint to release. |
||
1408 | * |
||
1409 | * @return void |
||
1410 | * |
||
1411 | * @throws \Doctrine\DBAL\ConnectionException |
||
1412 | */ |
||
1413 | 1 | public function releaseSavepoint($savepoint) |
|
1414 | { |
||
1415 | 1 | if ( ! $this->getDatabasePlatform()->supportsSavepoints()) { |
|
1416 | throw ConnectionException::savepointsNotSupported(); |
||
1417 | } |
||
1418 | |||
1419 | 1 | if ($this->platform->supportsReleaseSavepoints()) { |
|
1420 | 1 | $this->_conn->exec($this->platform->releaseSavePoint($savepoint)); |
|
1421 | } |
||
1422 | 1 | } |
|
1423 | |||
1424 | /** |
||
1425 | * Rolls back to the given savepoint. |
||
1426 | * |
||
1427 | * @param string $savepoint The name of the savepoint to rollback to. |
||
1428 | * |
||
1429 | * @return void |
||
1430 | * |
||
1431 | * @throws \Doctrine\DBAL\ConnectionException |
||
1432 | */ |
||
1433 | 1 | public function rollbackSavepoint($savepoint) |
|
1434 | { |
||
1435 | 1 | if ( ! $this->getDatabasePlatform()->supportsSavepoints()) { |
|
1436 | throw ConnectionException::savepointsNotSupported(); |
||
1437 | } |
||
1438 | |||
1439 | 1 | $this->_conn->exec($this->platform->rollbackSavePoint($savepoint)); |
|
1440 | 1 | } |
|
1441 | |||
1442 | /** |
||
1443 | * Gets the wrapped driver connection. |
||
1444 | * |
||
1445 | * @return \Doctrine\DBAL\Driver\Connection |
||
1446 | */ |
||
1447 | 47 | public function getWrappedConnection() |
|
1448 | { |
||
1449 | 47 | $this->connect(); |
|
1450 | |||
1451 | 47 | return $this->_conn; |
|
1452 | } |
||
1453 | |||
1454 | /** |
||
1455 | * Gets the SchemaManager that can be used to inspect or change the |
||
1456 | * database schema through the connection. |
||
1457 | * |
||
1458 | * @return \Doctrine\DBAL\Schema\AbstractSchemaManager |
||
1459 | */ |
||
1460 | 135 | public function getSchemaManager() |
|
1461 | { |
||
1462 | 135 | if ( ! $this->_schemaManager) { |
|
1463 | 11 | $this->_schemaManager = $this->_driver->getSchemaManager($this); |
|
1464 | } |
||
1465 | |||
1466 | 135 | return $this->_schemaManager; |
|
1467 | } |
||
1468 | |||
1469 | /** |
||
1470 | * Marks the current transaction so that the only possible |
||
1471 | * outcome for the transaction to be rolled back. |
||
1472 | * |
||
1473 | * @return void |
||
1474 | * |
||
1475 | * @throws \Doctrine\DBAL\ConnectionException If no transaction is active. |
||
1476 | */ |
||
1477 | 2 | public function setRollbackOnly() |
|
1478 | { |
||
1479 | 2 | if ($this->_transactionNestingLevel == 0) { |
|
1480 | 1 | throw ConnectionException::noActiveTransaction(); |
|
1481 | } |
||
1482 | 1 | $this->_isRollbackOnly = true; |
|
1483 | 1 | } |
|
1484 | |||
1485 | /** |
||
1486 | * Checks whether the current transaction is marked for rollback only. |
||
1487 | * |
||
1488 | * @return boolean |
||
1489 | * |
||
1490 | * @throws \Doctrine\DBAL\ConnectionException If no transaction is active. |
||
1491 | */ |
||
1492 | 3 | public function isRollbackOnly() |
|
1493 | { |
||
1494 | 3 | if ($this->_transactionNestingLevel == 0) { |
|
1495 | 1 | throw ConnectionException::noActiveTransaction(); |
|
1496 | } |
||
1497 | |||
1498 | 2 | return $this->_isRollbackOnly; |
|
1499 | } |
||
1500 | |||
1501 | /** |
||
1502 | * Converts a given value to its database representation according to the conversion |
||
1503 | * rules of a specific DBAL mapping type. |
||
1504 | * |
||
1505 | * @param mixed $value The value to convert. |
||
1506 | * @param string $type The name of the DBAL mapping type. |
||
1507 | * |
||
1508 | * @return mixed The converted value. |
||
1509 | */ |
||
1510 | public function convertToDatabaseValue($value, $type) |
||
1511 | { |
||
1512 | return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform()); |
||
1513 | } |
||
1514 | |||
1515 | /** |
||
1516 | * Converts a given value to its PHP representation according to the conversion |
||
1517 | * rules of a specific DBAL mapping type. |
||
1518 | * |
||
1519 | * @param mixed $value The value to convert. |
||
1520 | * @param string $type The name of the DBAL mapping type. |
||
1521 | * |
||
1522 | * @return mixed The converted type. |
||
1523 | */ |
||
1524 | public function convertToPHPValue($value, $type) |
||
1525 | { |
||
1526 | return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform()); |
||
1527 | } |
||
1528 | |||
1529 | /** |
||
1530 | * Binds a set of parameters, some or all of which are typed with a PDO binding type |
||
1531 | * or DBAL mapping type, to a given statement. |
||
1532 | * |
||
1533 | * @param \Doctrine\DBAL\Driver\Statement $stmt The statement to bind the values to. |
||
1534 | * @param array $params The map/list of named/positional parameters. |
||
1535 | * @param array $types The parameter types (PDO binding types or DBAL mapping types). |
||
1536 | * |
||
1537 | * @return void |
||
1538 | * |
||
1539 | * @internal Duck-typing used on the $stmt parameter to support driver statements as well as |
||
1540 | * raw PDOStatement instances. |
||
1541 | */ |
||
1542 | 26 | private function _bindTypedValues($stmt, array $params, array $types) |
|
1543 | { |
||
1544 | // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO. |
||
1545 | 26 | if (is_int(key($params))) { |
|
1546 | // Positional parameters |
||
1547 | 26 | $typeOffset = array_key_exists(0, $types) ? -1 : 0; |
|
1548 | 26 | $bindIndex = 1; |
|
1549 | 26 | foreach ($params as $value) { |
|
1550 | 26 | $typeIndex = $bindIndex + $typeOffset; |
|
1551 | 26 | View Code Duplication | if (isset($types[$typeIndex])) { |
1552 | 26 | $type = $types[$typeIndex]; |
|
1553 | 26 | list($value, $bindingType) = $this->getBindingInfo($value, $type); |
|
1554 | 26 | $stmt->bindValue($bindIndex, $value, $bindingType); |
|
1555 | } else { |
||
1556 | 1 | $stmt->bindValue($bindIndex, $value); |
|
1557 | } |
||
1558 | 26 | ++$bindIndex; |
|
1559 | } |
||
1560 | } else { |
||
1561 | // Named parameters |
||
1562 | foreach ($params as $name => $value) { |
||
1563 | View Code Duplication | if (isset($types[$name])) { |
|
1564 | $type = $types[$name]; |
||
1565 | list($value, $bindingType) = $this->getBindingInfo($value, $type); |
||
1566 | $stmt->bindValue($name, $value, $bindingType); |
||
1567 | } else { |
||
1568 | $stmt->bindValue($name, $value); |
||
1569 | } |
||
1570 | } |
||
1571 | } |
||
1572 | 26 | } |
|
1573 | |||
1574 | /** |
||
1575 | * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type. |
||
1576 | * |
||
1577 | * @param mixed $value The value to bind. |
||
1578 | * @param mixed $type The type to bind (PDO or DBAL). |
||
1579 | * |
||
1580 | * @return array [0] => the (escaped) value, [1] => the binding type. |
||
1581 | */ |
||
1582 | 30 | private function getBindingInfo($value, $type) |
|
1583 | { |
||
1584 | 30 | if (is_string($type)) { |
|
1585 | 12 | $type = Type::getType($type); |
|
1586 | } |
||
1587 | 30 | View Code Duplication | if ($type instanceof Type) { |
0 ignored issues
–
show
|
|||
1588 | 12 | $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform()); |
|
1589 | 12 | $bindingType = $type->getBindingType(); |
|
1590 | } else { |
||
1591 | 24 | $bindingType = $type; // PDO::PARAM_* constants |
|
1592 | } |
||
1593 | |||
1594 | 30 | return [$value, $bindingType]; |
|
1595 | } |
||
1596 | |||
1597 | /** |
||
1598 | * Resolves the parameters to a format which can be displayed. |
||
1599 | * |
||
1600 | * @internal This is a purely internal method. If you rely on this method, you are advised to |
||
1601 | * copy/paste the code as this method may change, or be removed without prior notice. |
||
1602 | * |
||
1603 | * @param array $params |
||
1604 | * @param array $types |
||
1605 | * |
||
1606 | * @return array |
||
1607 | */ |
||
1608 | 60 | public function resolveParams(array $params, array $types) |
|
1609 | { |
||
1610 | 60 | $resolvedParams = []; |
|
1611 | |||
1612 | // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO. |
||
1613 | 60 | if (is_int(key($params))) { |
|
1614 | // Positional parameters |
||
1615 | 8 | $typeOffset = array_key_exists(0, $types) ? -1 : 0; |
|
1616 | 8 | $bindIndex = 1; |
|
1617 | 8 | foreach ($params as $value) { |
|
1618 | 8 | $typeIndex = $bindIndex + $typeOffset; |
|
1619 | 8 | View Code Duplication | if (isset($types[$typeIndex])) { |
1620 | $type = $types[$typeIndex]; |
||
1621 | list($value,) = $this->getBindingInfo($value, $type); |
||
1622 | $resolvedParams[$bindIndex] = $value; |
||
1623 | } else { |
||
1624 | 8 | $resolvedParams[$bindIndex] = $value; |
|
1625 | } |
||
1626 | 8 | ++$bindIndex; |
|
1627 | } |
||
1628 | } else { |
||
1629 | // Named parameters |
||
1630 | 52 | foreach ($params as $name => $value) { |
|
1631 | View Code Duplication | if (isset($types[$name])) { |
|
1632 | $type = $types[$name]; |
||
1633 | list($value,) = $this->getBindingInfo($value, $type); |
||
1634 | $resolvedParams[$name] = $value; |
||
1635 | } else { |
||
1636 | $resolvedParams[$name] = $value; |
||
1637 | } |
||
1638 | } |
||
1639 | } |
||
1640 | |||
1641 | 60 | return $resolvedParams; |
|
1642 | } |
||
1643 | |||
1644 | /** |
||
1645 | * Creates a new instance of a SQL query builder. |
||
1646 | * |
||
1647 | * @return \Doctrine\DBAL\Query\QueryBuilder |
||
1648 | */ |
||
1649 | public function createQueryBuilder() |
||
1650 | { |
||
1651 | return new Query\QueryBuilder($this); |
||
1652 | } |
||
1653 | |||
1654 | /** |
||
1655 | * Ping the server |
||
1656 | * |
||
1657 | * When the server is not available the method returns FALSE. |
||
1658 | * It is responsibility of the developer to handle this case |
||
1659 | * and abort the request or reconnect manually: |
||
1660 | * |
||
1661 | * @example |
||
1662 | * |
||
1663 | * if ($conn->ping() === false) { |
||
1664 | * $conn->close(); |
||
1665 | * $conn->connect(); |
||
1666 | * } |
||
1667 | * |
||
1668 | * It is undefined if the underlying driver attempts to reconnect |
||
1669 | * or disconnect when the connection is not available anymore |
||
1670 | * as long it returns TRUE when a reconnect succeeded and |
||
1671 | * FALSE when the connection was dropped. |
||
1672 | * |
||
1673 | * @return bool |
||
1674 | */ |
||
1675 | 1 | public function ping() |
|
1676 | { |
||
1677 | 1 | $this->connect(); |
|
1678 | |||
1679 | 1 | if ($this->_conn instanceof PingableConnection) { |
|
1680 | return $this->_conn->ping(); |
||
1681 | } |
||
1682 | |||
1683 | try { |
||
1684 | 1 | $this->query($this->getDatabasePlatform()->getDummySelectSQL()); |
|
1685 | |||
1686 | 1 | return true; |
|
1687 | } catch (DBALException $e) { |
||
1688 | return false; |
||
1689 | } |
||
1690 | } |
||
1691 | } |
||
1692 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.