Connection.php 49 KB

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