Connection.php 32KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118
  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 LGPL. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\DBAL;
  20. use PDO, Closure, Exception,
  21. Doctrine\DBAL\Types\Type,
  22. Doctrine\DBAL\Driver\Connection as DriverConnection,
  23. Doctrine\Common\EventManager,
  24. Doctrine\DBAL\DBALException;
  25. /**
  26. * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
  27. * events, transaction isolation levels, configuration, emulated transaction nesting,
  28. * lazy connecting and more.
  29. *
  30. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
  31. * @link www.doctrine-project.org
  32. * @since 2.0
  33. * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
  34. * @author Jonathan Wage <jonwage@gmail.com>
  35. * @author Roman Borschel <roman@code-factory.org>
  36. * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
  37. * @author Lukas Smith <smith@pooteeweet.org> (MDB2 library)
  38. * @author Benjamin Eberlei <kontakt@beberlei.de>
  39. */
  40. class Connection implements DriverConnection
  41. {
  42. /**
  43. * Constant for transaction isolation level READ UNCOMMITTED.
  44. */
  45. const TRANSACTION_READ_UNCOMMITTED = 1;
  46. /**
  47. * Constant for transaction isolation level READ COMMITTED.
  48. */
  49. const TRANSACTION_READ_COMMITTED = 2;
  50. /**
  51. * Constant for transaction isolation level REPEATABLE READ.
  52. */
  53. const TRANSACTION_REPEATABLE_READ = 3;
  54. /**
  55. * Constant for transaction isolation level SERIALIZABLE.
  56. */
  57. const TRANSACTION_SERIALIZABLE = 4;
  58. /**
  59. * Represents an array of ints to be expanded by Doctrine SQL parsing.
  60. *
  61. * @var int
  62. */
  63. const PARAM_INT_ARRAY = 101;
  64. /**
  65. * Represents an array of strings to be expanded by Doctrine SQL parsing.
  66. *
  67. * @var int
  68. */
  69. const PARAM_STR_ARRAY = 102;
  70. /**
  71. * Offset by which PARAM_* constants are detected as arrays of the param type.
  72. *
  73. * @var int
  74. */
  75. const ARRAY_PARAM_OFFSET = 100;
  76. /**
  77. * The wrapped driver connection.
  78. *
  79. * @var Doctrine\DBAL\Driver\Connection
  80. */
  81. protected $_conn;
  82. /**
  83. * @var Doctrine\DBAL\Configuration
  84. */
  85. protected $_config;
  86. /**
  87. * @var Doctrine\Common\EventManager
  88. */
  89. protected $_eventManager;
  90. /**
  91. * @var Doctrine\DBAL\Query\ExpressionBuilder
  92. */
  93. protected $_expr;
  94. /**
  95. * Whether or not a connection has been established.
  96. *
  97. * @var boolean
  98. */
  99. private $_isConnected = false;
  100. /**
  101. * The transaction nesting level.
  102. *
  103. * @var integer
  104. */
  105. private $_transactionNestingLevel = 0;
  106. /**
  107. * The currently active transaction isolation level.
  108. *
  109. * @var integer
  110. */
  111. private $_transactionIsolationLevel;
  112. /**
  113. * If nested transations should use savepoints
  114. *
  115. * @var integer
  116. */
  117. private $_nestTransactionsWithSavepoints;
  118. /**
  119. * The parameters used during creation of the Connection instance.
  120. *
  121. * @var array
  122. */
  123. private $_params = array();
  124. /**
  125. * The DatabasePlatform object that provides information about the
  126. * database platform used by the connection.
  127. *
  128. * @var Doctrine\DBAL\Platforms\AbstractPlatform
  129. */
  130. protected $_platform;
  131. /**
  132. * The schema manager.
  133. *
  134. * @var Doctrine\DBAL\Schema\SchemaManager
  135. */
  136. protected $_schemaManager;
  137. /**
  138. * The used DBAL driver.
  139. *
  140. * @var Doctrine\DBAL\Driver
  141. */
  142. protected $_driver;
  143. /**
  144. * Flag that indicates whether the current transaction is marked for rollback only.
  145. *
  146. * @var boolean
  147. */
  148. private $_isRollbackOnly = false;
  149. /**
  150. * Initializes a new instance of the Connection class.
  151. *
  152. * @param array $params The connection parameters.
  153. * @param Driver $driver
  154. * @param Configuration $config
  155. * @param EventManager $eventManager
  156. */
  157. public function __construct(array $params, Driver $driver, Configuration $config = null,
  158. EventManager $eventManager = null)
  159. {
  160. $this->_driver = $driver;
  161. $this->_params = $params;
  162. if (isset($params['pdo'])) {
  163. $this->_conn = $params['pdo'];
  164. $this->_isConnected = true;
  165. }
  166. // Create default config and event manager if none given
  167. if ( ! $config) {
  168. $config = new Configuration();
  169. }
  170. if ( ! $eventManager) {
  171. $eventManager = new EventManager();
  172. }
  173. $this->_config = $config;
  174. $this->_eventManager = $eventManager;
  175. $this->_expr = new Query\Expression\ExpressionBuilder($this);
  176. if ( ! isset($params['platform'])) {
  177. $this->_platform = $driver->getDatabasePlatform();
  178. } else if ($params['platform'] instanceof Platforms\AbstractPlatform) {
  179. $this->_platform = $params['platform'];
  180. } else {
  181. throw DBALException::invalidPlatformSpecified();
  182. }
  183. $this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel();
  184. }
  185. /**
  186. * Gets the parameters used during instantiation.
  187. *
  188. * @return array $params
  189. */
  190. public function getParams()
  191. {
  192. return $this->_params;
  193. }
  194. /**
  195. * Gets the name of the database this Connection is connected to.
  196. *
  197. * @return string $database
  198. */
  199. public function getDatabase()
  200. {
  201. return $this->_driver->getDatabase($this);
  202. }
  203. /**
  204. * Gets the hostname of the currently connected database.
  205. *
  206. * @return string
  207. */
  208. public function getHost()
  209. {
  210. return isset($this->_params['host']) ? $this->_params['host'] : null;
  211. }
  212. /**
  213. * Gets the port of the currently connected database.
  214. *
  215. * @return mixed
  216. */
  217. public function getPort()
  218. {
  219. return isset($this->_params['port']) ? $this->_params['port'] : null;
  220. }
  221. /**
  222. * Gets the username used by this connection.
  223. *
  224. * @return string
  225. */
  226. public function getUsername()
  227. {
  228. return isset($this->_params['user']) ? $this->_params['user'] : null;
  229. }
  230. /**
  231. * Gets the password used by this connection.
  232. *
  233. * @return string
  234. */
  235. public function getPassword()
  236. {
  237. return isset($this->_params['password']) ? $this->_params['password'] : null;
  238. }
  239. /**
  240. * Gets the DBAL driver instance.
  241. *
  242. * @return Doctrine\DBAL\Driver
  243. */
  244. public function getDriver()
  245. {
  246. return $this->_driver;
  247. }
  248. /**
  249. * Gets the Configuration used by the Connection.
  250. *
  251. * @return Doctrine\DBAL\Configuration
  252. */
  253. public function getConfiguration()
  254. {
  255. return $this->_config;
  256. }
  257. /**
  258. * Gets the EventManager used by the Connection.
  259. *
  260. * @return Doctrine\Common\EventManager
  261. */
  262. public function getEventManager()
  263. {
  264. return $this->_eventManager;
  265. }
  266. /**
  267. * Gets the DatabasePlatform for the connection.
  268. *
  269. * @return Doctrine\DBAL\Platforms\AbstractPlatform
  270. */
  271. public function getDatabasePlatform()
  272. {
  273. return $this->_platform;
  274. }
  275. /**
  276. * Gets the ExpressionBuilder for the connection.
  277. *
  278. * @return Doctrine\DBAL\Query\ExpressionBuilder
  279. */
  280. public function getExpressionBuilder()
  281. {
  282. return $this->_expr;
  283. }
  284. /**
  285. * Establishes the connection with the database.
  286. *
  287. * @return boolean TRUE if the connection was successfully established, FALSE if
  288. * the connection is already open.
  289. */
  290. public function connect()
  291. {
  292. if ($this->_isConnected) return false;
  293. $driverOptions = isset($this->_params['driverOptions']) ?
  294. $this->_params['driverOptions'] : array();
  295. $user = isset($this->_params['user']) ? $this->_params['user'] : null;
  296. $password = isset($this->_params['password']) ?
  297. $this->_params['password'] : null;
  298. $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
  299. $this->_isConnected = true;
  300. if ($this->_eventManager->hasListeners(Events::postConnect)) {
  301. $eventArgs = new Event\ConnectionEventArgs($this);
  302. $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
  303. }
  304. return true;
  305. }
  306. /**
  307. * Prepares and executes an SQL query and returns the first row of the result
  308. * as an associative array.
  309. *
  310. * @param string $statement The SQL query.
  311. * @param array $params The query parameters.
  312. * @return array
  313. */
  314. public function fetchAssoc($statement, array $params = array())
  315. {
  316. return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_ASSOC);
  317. }
  318. /**
  319. * Prepares and executes an SQL query and returns the first row of the result
  320. * as a numerically indexed array.
  321. *
  322. * @param string $statement sql query to be executed
  323. * @param array $params prepared statement params
  324. * @return array
  325. */
  326. public function fetchArray($statement, array $params = array())
  327. {
  328. return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_NUM);
  329. }
  330. /**
  331. * Prepares and executes an SQL query and returns the value of a single column
  332. * of the first row of the result.
  333. *
  334. * @param string $statement sql query to be executed
  335. * @param array $params prepared statement params
  336. * @param int $colnum 0-indexed column number to retrieve
  337. * @return mixed
  338. */
  339. public function fetchColumn($statement, array $params = array(), $colnum = 0)
  340. {
  341. return $this->executeQuery($statement, $params)->fetchColumn($colnum);
  342. }
  343. /**
  344. * Whether an actual connection to the database is established.
  345. *
  346. * @return boolean
  347. */
  348. public function isConnected()
  349. {
  350. return $this->_isConnected;
  351. }
  352. /**
  353. * Checks whether a transaction is currently active.
  354. *
  355. * @return boolean TRUE if a transaction is currently active, FALSE otherwise.
  356. */
  357. public function isTransactionActive()
  358. {
  359. return $this->_transactionNestingLevel > 0;
  360. }
  361. /**
  362. * Executes an SQL DELETE statement on a table.
  363. *
  364. * @param string $table The name of the table on which to delete.
  365. * @param array $identifier The deletion criteria. An associateve array containing column-value pairs.
  366. * @return integer The number of affected rows.
  367. */
  368. public function delete($tableName, array $identifier)
  369. {
  370. $this->connect();
  371. $criteria = array();
  372. foreach (array_keys($identifier) as $columnName) {
  373. $criteria[] = $columnName . ' = ?';
  374. }
  375. $query = 'DELETE FROM ' . $tableName . ' WHERE ' . implode(' AND ', $criteria);
  376. return $this->executeUpdate($query, array_values($identifier));
  377. }
  378. /**
  379. * Closes the connection.
  380. *
  381. * @return void
  382. */
  383. public function close()
  384. {
  385. unset($this->_conn);
  386. $this->_isConnected = false;
  387. }
  388. /**
  389. * Sets the transaction isolation level.
  390. *
  391. * @param integer $level The level to set.
  392. */
  393. public function setTransactionIsolation($level)
  394. {
  395. $this->_transactionIsolationLevel = $level;
  396. return $this->executeUpdate($this->_platform->getSetTransactionIsolationSQL($level));
  397. }
  398. /**
  399. * Gets the currently active transaction isolation level.
  400. *
  401. * @return integer The current transaction isolation level.
  402. */
  403. public function getTransactionIsolation()
  404. {
  405. return $this->_transactionIsolationLevel;
  406. }
  407. /**
  408. * Executes an SQL UPDATE statement on a table.
  409. *
  410. * @param string $table The name of the table to update.
  411. * @param array $identifier The update criteria. An associative array containing column-value pairs.
  412. * @return integer The number of affected rows.
  413. */
  414. public function update($tableName, array $data, array $identifier)
  415. {
  416. $this->connect();
  417. $set = array();
  418. foreach ($data as $columnName => $value) {
  419. $set[] = $columnName . ' = ?';
  420. }
  421. $params = array_merge(array_values($data), array_values($identifier));
  422. $sql = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $set)
  423. . ' WHERE ' . implode(' = ? AND ', array_keys($identifier))
  424. . ' = ?';
  425. return $this->executeUpdate($sql, $params);
  426. }
  427. /**
  428. * Inserts a table row with specified data.
  429. *
  430. * @param string $table The name of the table to insert data into.
  431. * @param array $data An associative array containing column-value pairs.
  432. * @return integer The number of affected rows.
  433. */
  434. public function insert($tableName, array $data)
  435. {
  436. $this->connect();
  437. // column names are specified as array keys
  438. $cols = array();
  439. $placeholders = array();
  440. foreach ($data as $columnName => $value) {
  441. $cols[] = $columnName;
  442. $placeholders[] = '?';
  443. }
  444. $query = 'INSERT INTO ' . $tableName
  445. . ' (' . implode(', ', $cols) . ')'
  446. . ' VALUES (' . implode(', ', $placeholders) . ')';
  447. return $this->executeUpdate($query, array_values($data));
  448. }
  449. /**
  450. * Sets the given charset on the current connection.
  451. *
  452. * @param string $charset The charset to set.
  453. */
  454. public function setCharset($charset)
  455. {
  456. $this->executeUpdate($this->_platform->getSetCharsetSQL($charset));
  457. }
  458. /**
  459. * Quote a string so it can be safely used as a table or column name, even if
  460. * it is a reserved name.
  461. *
  462. * Delimiting style depends on the underlying database platform that is being used.
  463. *
  464. * NOTE: Just because you CAN use quoted identifiers does not mean
  465. * you SHOULD use them. In general, they end up causing way more
  466. * problems than they solve.
  467. *
  468. * @param string $str The name to be quoted.
  469. * @return string The quoted name.
  470. */
  471. public function quoteIdentifier($str)
  472. {
  473. return $this->_platform->quoteIdentifier($str);
  474. }
  475. /**
  476. * Quotes a given input parameter.
  477. *
  478. * @param mixed $input Parameter to be quoted.
  479. * @param string $type Type of the parameter.
  480. * @return string The quoted parameter.
  481. */
  482. public function quote($input, $type = null)
  483. {
  484. $this->connect();
  485. return $this->_conn->quote($input, $type);
  486. }
  487. /**
  488. * Prepares and executes an SQL query and returns the result as an associative array.
  489. *
  490. * @param string $sql The SQL query.
  491. * @param array $params The query parameters.
  492. * @return array
  493. */
  494. public function fetchAll($sql, array $params = array())
  495. {
  496. return $this->executeQuery($sql, $params)->fetchAll(PDO::FETCH_ASSOC);
  497. }
  498. /**
  499. * Prepares an SQL statement.
  500. *
  501. * @param string $statement The SQL statement to prepare.
  502. * @return Doctrine\DBAL\Driver\Statement The prepared statement.
  503. */
  504. public function prepare($statement)
  505. {
  506. $this->connect();
  507. return new Statement($statement, $this);
  508. }
  509. /**
  510. * Executes an, optionally parameterized, SQL query.
  511. *
  512. * If the query is parameterized, a prepared statement is used.
  513. * If an SQLLogger is configured, the execution is logged.
  514. *
  515. * @param string $query The SQL query to execute.
  516. * @param array $params The parameters to bind to the query, if any.
  517. * @return Doctrine\DBAL\Driver\Statement The executed statement.
  518. * @internal PERF: Directly prepares a driver statement, not a wrapper.
  519. */
  520. public function executeQuery($query, array $params = array(), $types = array())
  521. {
  522. $this->connect();
  523. $hasLogger = $this->_config->getSQLLogger() !== null;
  524. if ($hasLogger) {
  525. $this->_config->getSQLLogger()->startQuery($query, $params, $types);
  526. }
  527. if ($params) {
  528. list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
  529. $stmt = $this->_conn->prepare($query);
  530. if ($types) {
  531. $this->_bindTypedValues($stmt, $params, $types);
  532. $stmt->execute();
  533. } else {
  534. $stmt->execute($params);
  535. }
  536. } else {
  537. $stmt = $this->_conn->query($query);
  538. }
  539. if ($hasLogger) {
  540. $this->_config->getSQLLogger()->stopQuery();
  541. }
  542. return $stmt;
  543. }
  544. /**
  545. * Executes an, optionally parameterized, SQL query and returns the result,
  546. * applying a given projection/transformation function on each row of the result.
  547. *
  548. * @param string $query The SQL query to execute.
  549. * @param array $params The parameters, if any.
  550. * @param Closure $mapper The transformation function that is applied on each row.
  551. * The function receives a single paramater, an array, that
  552. * represents a row of the result set.
  553. * @return mixed The projected result of the query.
  554. */
  555. public function project($query, array $params, Closure $function)
  556. {
  557. $result = array();
  558. $stmt = $this->executeQuery($query, $params ?: array());
  559. while ($row = $stmt->fetch()) {
  560. $result[] = $function($row);
  561. }
  562. $stmt->closeCursor();
  563. return $result;
  564. }
  565. /**
  566. * Executes an SQL statement, returning a result set as a Statement object.
  567. *
  568. * @param string $statement
  569. * @param integer $fetchType
  570. * @return Doctrine\DBAL\Driver\Statement
  571. */
  572. public function query()
  573. {
  574. $this->connect();
  575. $args = func_get_args();
  576. $logger = $this->getConfiguration()->getSQLLogger();
  577. if ($logger) {
  578. $logger->startQuery($args[0]);
  579. }
  580. $statement = call_user_func_array(array($this->_conn, 'query'), $args);
  581. if ($logger) {
  582. $logger->stopQuery();
  583. }
  584. return $statement;
  585. }
  586. /**
  587. * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
  588. * and returns the number of affected rows.
  589. *
  590. * This method supports PDO binding types as well as DBAL mapping types.
  591. *
  592. * @param string $query The SQL query.
  593. * @param array $params The query parameters.
  594. * @param array $types The parameter types.
  595. * @return integer The number of affected rows.
  596. * @internal PERF: Directly prepares a driver statement, not a wrapper.
  597. */
  598. public function executeUpdate($query, array $params = array(), array $types = array())
  599. {
  600. $this->connect();
  601. $hasLogger = $this->_config->getSQLLogger() !== null;
  602. if ($hasLogger) {
  603. $this->_config->getSQLLogger()->startQuery($query, $params, $types);
  604. }
  605. if ($params) {
  606. list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
  607. $stmt = $this->_conn->prepare($query);
  608. if ($types) {
  609. $this->_bindTypedValues($stmt, $params, $types);
  610. $stmt->execute();
  611. } else {
  612. $stmt->execute($params);
  613. }
  614. $result = $stmt->rowCount();
  615. } else {
  616. $result = $this->_conn->exec($query);
  617. }
  618. if ($hasLogger) {
  619. $this->_config->getSQLLogger()->stopQuery();
  620. }
  621. return $result;
  622. }
  623. /**
  624. * Execute an SQL statement and return the number of affected rows.
  625. *
  626. * @param string $statement
  627. * @return integer The number of affected rows.
  628. */
  629. public function exec($statement)
  630. {
  631. $this->connect();
  632. return $this->_conn->exec($statement);
  633. }
  634. /**
  635. * Returns the current transaction nesting level.
  636. *
  637. * @return integer The nesting level. A value of 0 means there's no active transaction.
  638. */
  639. public function getTransactionNestingLevel()
  640. {
  641. return $this->_transactionNestingLevel;
  642. }
  643. /**
  644. * Fetch the SQLSTATE associated with the last database operation.
  645. *
  646. * @return integer The last error code.
  647. */
  648. public function errorCode()
  649. {
  650. $this->connect();
  651. return $this->_conn->errorCode();
  652. }
  653. /**
  654. * Fetch extended error information associated with the last database operation.
  655. *
  656. * @return array The last error information.
  657. */
  658. public function errorInfo()
  659. {
  660. $this->connect();
  661. return $this->_conn->errorInfo();
  662. }
  663. /**
  664. * Returns the ID of the last inserted row, or the last value from a sequence object,
  665. * depending on the underlying driver.
  666. *
  667. * Note: This method may not return a meaningful or consistent result across different drivers,
  668. * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
  669. * columns or sequences.
  670. *
  671. * @param string $seqName Name of the sequence object from which the ID should be returned.
  672. * @return string A string representation of the last inserted ID.
  673. */
  674. public function lastInsertId($seqName = null)
  675. {
  676. $this->connect();
  677. return $this->_conn->lastInsertId($seqName);
  678. }
  679. /**
  680. * Executes a function in a transaction.
  681. *
  682. * The function gets passed this Connection instance as an (optional) parameter.
  683. *
  684. * If an exception occurs during execution of the function or transaction commit,
  685. * the transaction is rolled back and the exception re-thrown.
  686. *
  687. * @param Closure $func The function to execute transactionally.
  688. */
  689. public function transactional(Closure $func)
  690. {
  691. $this->beginTransaction();
  692. try {
  693. $func($this);
  694. $this->commit();
  695. } catch (Exception $e) {
  696. $this->rollback();
  697. throw $e;
  698. }
  699. }
  700. /**
  701. * Set if nested transactions should use savepoints
  702. *
  703. * @param boolean
  704. * @return void
  705. */
  706. public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
  707. {
  708. if ($this->_transactionNestingLevel > 0) {
  709. throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
  710. }
  711. if (!$this->_platform->supportsSavepoints()) {
  712. throw ConnectionException::savepointsNotSupported();
  713. }
  714. $this->_nestTransactionsWithSavepoints = $nestTransactionsWithSavepoints;
  715. }
  716. /**
  717. * Get if nested transactions should use savepoints
  718. *
  719. * @return boolean
  720. */
  721. public function getNestTransactionsWithSavepoints()
  722. {
  723. return $this->_nestTransactionsWithSavepoints;
  724. }
  725. /**
  726. * Returns the savepoint name to use for nested transactions are false if they are not supported
  727. * "savepointFormat" parameter is not set
  728. *
  729. * @return mixed a string with the savepoint name or false
  730. */
  731. protected function _getNestedTransactionSavePointName()
  732. {
  733. return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel;
  734. }
  735. /**
  736. * Starts a transaction by suspending auto-commit mode.
  737. *
  738. * @return void
  739. */
  740. public function beginTransaction()
  741. {
  742. $this->connect();
  743. ++$this->_transactionNestingLevel;
  744. if ($this->_transactionNestingLevel == 1) {
  745. $this->_conn->beginTransaction();
  746. } else if ($this->_nestTransactionsWithSavepoints) {
  747. $this->createSavepoint($this->_getNestedTransactionSavePointName());
  748. }
  749. }
  750. /**
  751. * Commits the current transaction.
  752. *
  753. * @return void
  754. * @throws ConnectionException If the commit failed due to no active transaction or
  755. * because the transaction was marked for rollback only.
  756. */
  757. public function commit()
  758. {
  759. if ($this->_transactionNestingLevel == 0) {
  760. throw ConnectionException::noActiveTransaction();
  761. }
  762. if ($this->_isRollbackOnly) {
  763. throw ConnectionException::commitFailedRollbackOnly();
  764. }
  765. $this->connect();
  766. if ($this->_transactionNestingLevel == 1) {
  767. $this->_conn->commit();
  768. } else if ($this->_nestTransactionsWithSavepoints) {
  769. $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
  770. }
  771. --$this->_transactionNestingLevel;
  772. }
  773. /**
  774. * Cancel any database changes done during the current transaction.
  775. *
  776. * this method can be listened with onPreTransactionRollback and onTransactionRollback
  777. * eventlistener methods
  778. *
  779. * @throws ConnectionException If the rollback operation failed.
  780. */
  781. public function rollback()
  782. {
  783. if ($this->_transactionNestingLevel == 0) {
  784. throw ConnectionException::noActiveTransaction();
  785. }
  786. $this->connect();
  787. if ($this->_transactionNestingLevel == 1) {
  788. $this->_transactionNestingLevel = 0;
  789. $this->_conn->rollback();
  790. $this->_isRollbackOnly = false;
  791. } else if ($this->_nestTransactionsWithSavepoints) {
  792. $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
  793. --$this->_transactionNestingLevel;
  794. } else {
  795. $this->_isRollbackOnly = true;
  796. --$this->_transactionNestingLevel;
  797. }
  798. }
  799. /**
  800. * createSavepoint
  801. * creates a new savepoint
  802. *
  803. * @param string $savepoint name of a savepoint to set
  804. * @return void
  805. */
  806. public function createSavepoint($savepoint)
  807. {
  808. if (!$this->_platform->supportsSavepoints()) {
  809. throw ConnectionException::savepointsNotSupported();
  810. }
  811. $this->_conn->exec($this->_platform->createSavePoint($savepoint));
  812. }
  813. /**
  814. * releaseSavePoint
  815. * releases given savepoint
  816. *
  817. * @param string $savepoint name of a savepoint to release
  818. * @return void
  819. */
  820. public function releaseSavepoint($savepoint)
  821. {
  822. if (!$this->_platform->supportsSavepoints()) {
  823. throw ConnectionException::savepointsNotSupported();
  824. }
  825. if ($this->_platform->supportsReleaseSavepoints()) {
  826. $this->_conn->exec($this->_platform->releaseSavePoint($savepoint));
  827. }
  828. }
  829. /**
  830. * rollbackSavePoint
  831. * releases given savepoint
  832. *
  833. * @param string $savepoint name of a savepoint to rollback to
  834. * @return void
  835. */
  836. public function rollbackSavepoint($savepoint)
  837. {
  838. if (!$this->_platform->supportsSavepoints()) {
  839. throw ConnectionException::savepointsNotSupported();
  840. }
  841. $this->_conn->exec($this->_platform->rollbackSavePoint($savepoint));
  842. }
  843. /**
  844. * Gets the wrapped driver connection.
  845. *
  846. * @return Doctrine\DBAL\Driver\Connection
  847. */
  848. public function getWrappedConnection()
  849. {
  850. $this->connect();
  851. return $this->_conn;
  852. }
  853. /**
  854. * Gets the SchemaManager that can be used to inspect or change the
  855. * database schema through the connection.
  856. *
  857. * @return Doctrine\DBAL\Schema\SchemaManager
  858. */
  859. public function getSchemaManager()
  860. {
  861. if ( ! $this->_schemaManager) {
  862. $this->_schemaManager = $this->_driver->getSchemaManager($this);
  863. }
  864. return $this->_schemaManager;
  865. }
  866. /**
  867. * Marks the current transaction so that the only possible
  868. * outcome for the transaction to be rolled back.
  869. *
  870. * @throws ConnectionException If no transaction is active.
  871. */
  872. public function setRollbackOnly()
  873. {
  874. if ($this->_transactionNestingLevel == 0) {
  875. throw ConnectionException::noActiveTransaction();
  876. }
  877. $this->_isRollbackOnly = true;
  878. }
  879. /**
  880. * Check whether the current transaction is marked for rollback only.
  881. *
  882. * @return boolean
  883. * @throws ConnectionException If no transaction is active.
  884. */
  885. public function isRollbackOnly()
  886. {
  887. if ($this->_transactionNestingLevel == 0) {
  888. throw ConnectionException::noActiveTransaction();
  889. }
  890. return $this->_isRollbackOnly;
  891. }
  892. /**
  893. * Converts a given value to its database representation according to the conversion
  894. * rules of a specific DBAL mapping type.
  895. *
  896. * @param mixed $value The value to convert.
  897. * @param string $type The name of the DBAL mapping type.
  898. * @return mixed The converted value.
  899. */
  900. public function convertToDatabaseValue($value, $type)
  901. {
  902. return Type::getType($type)->convertToDatabaseValue($value, $this->_platform);
  903. }
  904. /**
  905. * Converts a given value to its PHP representation according to the conversion
  906. * rules of a specific DBAL mapping type.
  907. *
  908. * @param mixed $value The value to convert.
  909. * @param string $type The name of the DBAL mapping type.
  910. * @return mixed The converted type.
  911. */
  912. public function convertToPHPValue($value, $type)
  913. {
  914. return Type::getType($type)->convertToPHPValue($value, $this->_platform);
  915. }
  916. /**
  917. * Binds a set of parameters, some or all of which are typed with a PDO binding type
  918. * or DBAL mapping type, to a given statement.
  919. *
  920. * @param $stmt The statement to bind the values to.
  921. * @param array $params The map/list of named/positional parameters.
  922. * @param array $types The parameter types (PDO binding types or DBAL mapping types).
  923. * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
  924. * raw PDOStatement instances.
  925. */
  926. private function _bindTypedValues($stmt, array $params, array $types)
  927. {
  928. // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
  929. if (is_int(key($params))) {
  930. // Positional parameters
  931. $typeOffset = array_key_exists(0, $types) ? -1 : 0;
  932. $bindIndex = 1;
  933. foreach ($params as $position => $value) {
  934. $typeIndex = $bindIndex + $typeOffset;
  935. if (isset($types[$typeIndex])) {
  936. $type = $types[$typeIndex];
  937. if (is_string($type)) {
  938. $type = Type::getType($type);
  939. }
  940. if ($type instanceof Type) {
  941. $value = $type->convertToDatabaseValue($value, $this->_platform);
  942. $bindingType = $type->getBindingType();
  943. } else {
  944. $bindingType = $type; // PDO::PARAM_* constants
  945. }
  946. $stmt->bindValue($bindIndex, $value, $bindingType);
  947. } else {
  948. $stmt->bindValue($bindIndex, $value);
  949. }
  950. ++$bindIndex;
  951. }
  952. } else {
  953. // Named parameters
  954. foreach ($params as $name => $value) {
  955. if (isset($types[$name])) {
  956. $type = $types[$name];
  957. if (is_string($type)) {
  958. $type = Type::getType($type);
  959. }
  960. if ($type instanceof Type) {
  961. $value = $type->convertToDatabaseValue($value, $this->_platform);
  962. $bindingType = $type->getBindingType();
  963. } else {
  964. $bindingType = $type; // PDO::PARAM_* constants
  965. }
  966. $stmt->bindValue($name, $value, $bindingType);
  967. } else {
  968. $stmt->bindValue($name, $value);
  969. }
  970. }
  971. }
  972. }
  973. /**
  974. * Create a new instance of a SQL query builder.
  975. *
  976. * @return Query\QueryBuilder
  977. */
  978. public function createQueryBuilder()
  979. {
  980. return new Query\QueryBuilder($this);
  981. }
  982. }