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); |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
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) { |
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 |