PageRenderTime 63ms CodeModel.GetById 40ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/Database/StatementPrefetch.php

https://gitlab.com/reasonat/test8
PHP | 537 lines | 288 code | 59 blank | 190 comment | 45 complexity | 938d756bcd18614552358bc6bf9b56e5 MD5 | raw file
  1. <?php
  2. namespace Drupal\Core\Database;
  3. /**
  4. * An implementation of StatementInterface that prefetches all data.
  5. *
  6. * This class behaves very similar to a \PDOStatement but as it always fetches
  7. * every row it is possible to manipulate those results.
  8. */
  9. class StatementPrefetch implements \Iterator, StatementInterface {
  10. /**
  11. * The query string.
  12. *
  13. * @var string
  14. */
  15. protected $queryString;
  16. /**
  17. * Driver-specific options. Can be used by child classes.
  18. *
  19. * @var Array
  20. */
  21. protected $driverOptions;
  22. /**
  23. * Reference to the Drupal database connection object for this statement.
  24. *
  25. * @var \Drupal\Core\Database\Connection
  26. */
  27. public $dbh;
  28. /**
  29. * Reference to the PDO connection object for this statement.
  30. *
  31. * @var \PDO
  32. */
  33. protected $pdoConnection;
  34. /**
  35. * Main data store.
  36. *
  37. * @var Array
  38. */
  39. protected $data = array();
  40. /**
  41. * The current row, retrieved in \PDO::FETCH_ASSOC format.
  42. *
  43. * @var Array
  44. */
  45. protected $currentRow = NULL;
  46. /**
  47. * The key of the current row.
  48. *
  49. * @var int
  50. */
  51. protected $currentKey = NULL;
  52. /**
  53. * The list of column names in this result set.
  54. *
  55. * @var Array
  56. */
  57. protected $columnNames = NULL;
  58. /**
  59. * The number of rows affected by the last query.
  60. *
  61. * @var int
  62. */
  63. protected $rowCount = NULL;
  64. /**
  65. * The number of rows in this result set.
  66. *
  67. * @var int
  68. */
  69. protected $resultRowCount = 0;
  70. /**
  71. * Holds the current fetch style (which will be used by the next fetch).
  72. * @see \PDOStatement::fetch()
  73. *
  74. * @var int
  75. */
  76. protected $fetchStyle = \PDO::FETCH_OBJ;
  77. /**
  78. * Holds supplementary current fetch options (which will be used by the next fetch).
  79. *
  80. * @var Array
  81. */
  82. protected $fetchOptions = array(
  83. 'class' => 'stdClass',
  84. 'constructor_args' => array(),
  85. 'object' => NULL,
  86. 'column' => 0,
  87. );
  88. /**
  89. * Holds the default fetch style.
  90. *
  91. * @var int
  92. */
  93. protected $defaultFetchStyle = \PDO::FETCH_OBJ;
  94. /**
  95. * Holds supplementary default fetch options.
  96. *
  97. * @var Array
  98. */
  99. protected $defaultFetchOptions = array(
  100. 'class' => 'stdClass',
  101. 'constructor_args' => array(),
  102. 'object' => NULL,
  103. 'column' => 0,
  104. );
  105. /**
  106. * Is rowCount() execution allowed.
  107. *
  108. * @var bool
  109. */
  110. public $allowRowCount = FALSE;
  111. public function __construct(\PDO $pdo_connection, Connection $connection, $query, array $driver_options = array()) {
  112. $this->pdoConnection = $pdo_connection;
  113. $this->dbh = $connection;
  114. $this->queryString = $query;
  115. $this->driverOptions = $driver_options;
  116. }
  117. /**
  118. * {@inheritdoc}
  119. */
  120. public function execute($args = array(), $options = array()) {
  121. if (isset($options['fetch'])) {
  122. if (is_string($options['fetch'])) {
  123. // Default to an object. Note: db fields will be added to the object
  124. // before the constructor is run. If you need to assign fields after
  125. // the constructor is run. See https://www.drupal.org/node/315092.
  126. $this->setFetchMode(\PDO::FETCH_CLASS, $options['fetch']);
  127. }
  128. else {
  129. $this->setFetchMode($options['fetch']);
  130. }
  131. }
  132. $logger = $this->dbh->getLogger();
  133. if (!empty($logger)) {
  134. $query_start = microtime(TRUE);
  135. }
  136. // Prepare the query.
  137. $statement = $this->getStatement($this->queryString, $args);
  138. if (!$statement) {
  139. $this->throwPDOException();
  140. }
  141. $return = $statement->execute($args);
  142. if (!$return) {
  143. $this->throwPDOException();
  144. }
  145. if ($options['return'] == Database::RETURN_AFFECTED) {
  146. $this->rowCount = $statement->rowCount();
  147. }
  148. // Fetch all the data from the reply, in order to release any lock
  149. // as soon as possible.
  150. $this->data = $statement->fetchAll(\PDO::FETCH_ASSOC);
  151. // Destroy the statement as soon as possible. See the documentation of
  152. // \Drupal\Core\Database\Driver\sqlite\Statement for an explanation.
  153. unset($statement);
  154. $this->resultRowCount = count($this->data);
  155. if ($this->resultRowCount) {
  156. $this->columnNames = array_keys($this->data[0]);
  157. }
  158. else {
  159. $this->columnNames = array();
  160. }
  161. if (!empty($logger)) {
  162. $query_end = microtime(TRUE);
  163. $logger->log($this, $args, $query_end - $query_start);
  164. }
  165. // Initialize the first row in $this->currentRow.
  166. $this->next();
  167. return $return;
  168. }
  169. /**
  170. * Throw a PDO Exception based on the last PDO error.
  171. */
  172. protected function throwPDOException() {
  173. $error_info = $this->dbh->errorInfo();
  174. // We rebuild a message formatted in the same way as PDO.
  175. $exception = new \PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]);
  176. $exception->errorInfo = $error_info;
  177. throw $exception;
  178. }
  179. /**
  180. * Grab a PDOStatement object from a given query and its arguments.
  181. *
  182. * Some drivers (including SQLite) will need to perform some preparation
  183. * themselves to get the statement right.
  184. *
  185. * @param $query
  186. * The query.
  187. * @param array $args
  188. * An array of arguments.
  189. * @return \PDOStatement
  190. * A PDOStatement object.
  191. */
  192. protected function getStatement($query, &$args = array()) {
  193. return $this->dbh->prepare($query);
  194. }
  195. /**
  196. * {@inheritdoc}
  197. */
  198. public function getQueryString() {
  199. return $this->queryString;
  200. }
  201. /**
  202. * {@inheritdoc}
  203. */
  204. public function setFetchMode($mode, $a1 = NULL, $a2 = array()) {
  205. $this->defaultFetchStyle = $mode;
  206. switch ($mode) {
  207. case \PDO::FETCH_CLASS:
  208. $this->defaultFetchOptions['class'] = $a1;
  209. if ($a2) {
  210. $this->defaultFetchOptions['constructor_args'] = $a2;
  211. }
  212. break;
  213. case \PDO::FETCH_COLUMN:
  214. $this->defaultFetchOptions['column'] = $a1;
  215. break;
  216. case \PDO::FETCH_INTO:
  217. $this->defaultFetchOptions['object'] = $a1;
  218. break;
  219. }
  220. // Set the values for the next fetch.
  221. $this->fetchStyle = $this->defaultFetchStyle;
  222. $this->fetchOptions = $this->defaultFetchOptions;
  223. }
  224. /**
  225. * Return the current row formatted according to the current fetch style.
  226. *
  227. * This is the core method of this class. It grabs the value at the current
  228. * array position in $this->data and format it according to $this->fetchStyle
  229. * and $this->fetchMode.
  230. *
  231. * @return mixed
  232. * The current row formatted as requested.
  233. */
  234. public function current() {
  235. if (isset($this->currentRow)) {
  236. switch ($this->fetchStyle) {
  237. case \PDO::FETCH_ASSOC:
  238. return $this->currentRow;
  239. case \PDO::FETCH_BOTH:
  240. // \PDO::FETCH_BOTH returns an array indexed by both the column name
  241. // and the column number.
  242. return $this->currentRow + array_values($this->currentRow);
  243. case \PDO::FETCH_NUM:
  244. return array_values($this->currentRow);
  245. case \PDO::FETCH_LAZY:
  246. // We do not do lazy as everything is fetched already. Fallback to
  247. // \PDO::FETCH_OBJ.
  248. case \PDO::FETCH_OBJ:
  249. return (object) $this->currentRow;
  250. case \PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE:
  251. $class_name = array_unshift($this->currentRow);
  252. // Deliberate no break.
  253. case \PDO::FETCH_CLASS:
  254. if (!isset($class_name)) {
  255. $class_name = $this->fetchOptions['class'];
  256. }
  257. if (count($this->fetchOptions['constructor_args'])) {
  258. $reflector = new \ReflectionClass($class_name);
  259. $result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']);
  260. }
  261. else {
  262. $result = new $class_name();
  263. }
  264. foreach ($this->currentRow as $k => $v) {
  265. $result->$k = $v;
  266. }
  267. return $result;
  268. case \PDO::FETCH_INTO:
  269. foreach ($this->currentRow as $k => $v) {
  270. $this->fetchOptions['object']->$k = $v;
  271. }
  272. return $this->fetchOptions['object'];
  273. case \PDO::FETCH_COLUMN:
  274. if (isset($this->columnNames[$this->fetchOptions['column']])) {
  275. return $this->currentRow[$k][$this->columnNames[$this->fetchOptions['column']]];
  276. }
  277. else {
  278. return;
  279. }
  280. }
  281. }
  282. }
  283. /**
  284. * {@inheritdoc}
  285. */
  286. public function key() {
  287. return $this->currentKey;
  288. }
  289. /**
  290. * {@inheritdoc}
  291. */
  292. public function rewind() {
  293. // Nothing to do: our DatabaseStatement can't be rewound.
  294. }
  295. /**
  296. * {@inheritdoc}
  297. */
  298. public function next() {
  299. if (!empty($this->data)) {
  300. $this->currentRow = reset($this->data);
  301. $this->currentKey = key($this->data);
  302. unset($this->data[$this->currentKey]);
  303. }
  304. else {
  305. $this->currentRow = NULL;
  306. }
  307. }
  308. /**
  309. * {@inheritdoc}
  310. */
  311. public function valid() {
  312. return isset($this->currentRow);
  313. }
  314. /**
  315. * {@inheritdoc}
  316. */
  317. public function rowCount() {
  318. // SELECT query should not use the method.
  319. if ($this->allowRowCount) {
  320. return $this->rowCount;
  321. }
  322. else {
  323. throw new RowCountException();
  324. }
  325. }
  326. /**
  327. * {@inheritdoc}
  328. */
  329. public function fetch($fetch_style = NULL, $cursor_orientation = \PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) {
  330. if (isset($this->currentRow)) {
  331. // Set the fetch parameter.
  332. $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
  333. $this->fetchOptions = $this->defaultFetchOptions;
  334. // Grab the row in the format specified above.
  335. $return = $this->current();
  336. // Advance the cursor.
  337. $this->next();
  338. // Reset the fetch parameters to the value stored using setFetchMode().
  339. $this->fetchStyle = $this->defaultFetchStyle;
  340. $this->fetchOptions = $this->defaultFetchOptions;
  341. return $return;
  342. }
  343. else {
  344. return FALSE;
  345. }
  346. }
  347. public function fetchColumn($index = 0) {
  348. if (isset($this->currentRow) && isset($this->columnNames[$index])) {
  349. // We grab the value directly from $this->data, and format it.
  350. $return = $this->currentRow[$this->columnNames[$index]];
  351. $this->next();
  352. return $return;
  353. }
  354. else {
  355. return FALSE;
  356. }
  357. }
  358. /**
  359. * {@inheritdoc}
  360. */
  361. public function fetchField($index = 0) {
  362. return $this->fetchColumn($index);
  363. }
  364. /**
  365. * {@inheritdoc}
  366. */
  367. public function fetchObject($class_name = NULL, $constructor_args = array()) {
  368. if (isset($this->currentRow)) {
  369. if (!isset($class_name)) {
  370. // Directly cast to an object to avoid a function call.
  371. $result = (object) $this->currentRow;
  372. }
  373. else {
  374. $this->fetchStyle = \PDO::FETCH_CLASS;
  375. $this->fetchOptions = array('constructor_args' => $constructor_args);
  376. // Grab the row in the format specified above.
  377. $result = $this->current();
  378. // Reset the fetch parameters to the value stored using setFetchMode().
  379. $this->fetchStyle = $this->defaultFetchStyle;
  380. $this->fetchOptions = $this->defaultFetchOptions;
  381. }
  382. $this->next();
  383. return $result;
  384. }
  385. else {
  386. return FALSE;
  387. }
  388. }
  389. /**
  390. * {@inheritdoc}
  391. */
  392. public function fetchAssoc() {
  393. if (isset($this->currentRow)) {
  394. $result = $this->currentRow;
  395. $this->next();
  396. return $result;
  397. }
  398. else {
  399. return FALSE;
  400. }
  401. }
  402. /**
  403. * {@inheritdoc}
  404. */
  405. public function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL) {
  406. $this->fetchStyle = isset($mode) ? $mode : $this->defaultFetchStyle;
  407. $this->fetchOptions = $this->defaultFetchOptions;
  408. if (isset($column_index)) {
  409. $this->fetchOptions['column'] = $column_index;
  410. }
  411. if (isset($constructor_arguments)) {
  412. $this->fetchOptions['constructor_args'] = $constructor_arguments;
  413. }
  414. $result = array();
  415. // Traverse the array as PHP would have done.
  416. while (isset($this->currentRow)) {
  417. // Grab the row in the format specified above.
  418. $result[] = $this->current();
  419. $this->next();
  420. }
  421. // Reset the fetch parameters to the value stored using setFetchMode().
  422. $this->fetchStyle = $this->defaultFetchStyle;
  423. $this->fetchOptions = $this->defaultFetchOptions;
  424. return $result;
  425. }
  426. /**
  427. * {@inheritdoc}
  428. */
  429. public function fetchCol($index = 0) {
  430. if (isset($this->columnNames[$index])) {
  431. $result = array();
  432. // Traverse the array as PHP would have done.
  433. while (isset($this->currentRow)) {
  434. $result[] = $this->currentRow[$this->columnNames[$index]];
  435. $this->next();
  436. }
  437. return $result;
  438. }
  439. else {
  440. return array();
  441. }
  442. }
  443. /**
  444. * {@inheritdoc}
  445. */
  446. public function fetchAllKeyed($key_index = 0, $value_index = 1) {
  447. if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index]))
  448. return array();
  449. $key = $this->columnNames[$key_index];
  450. $value = $this->columnNames[$value_index];
  451. $result = array();
  452. // Traverse the array as PHP would have done.
  453. while (isset($this->currentRow)) {
  454. $result[$this->currentRow[$key]] = $this->currentRow[$value];
  455. $this->next();
  456. }
  457. return $result;
  458. }
  459. /**
  460. * {@inheritdoc}
  461. */
  462. public function fetchAllAssoc($key, $fetch_style = NULL) {
  463. $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
  464. $this->fetchOptions = $this->defaultFetchOptions;
  465. $result = array();
  466. // Traverse the array as PHP would have done.
  467. while (isset($this->currentRow)) {
  468. // Grab the row in its raw \PDO::FETCH_ASSOC format.
  469. $result_row = $this->current();
  470. $result[$this->currentRow[$key]] = $result_row;
  471. $this->next();
  472. }
  473. // Reset the fetch parameters to the value stored using setFetchMode().
  474. $this->fetchStyle = $this->defaultFetchStyle;
  475. $this->fetchOptions = $this->defaultFetchOptions;
  476. return $result;
  477. }
  478. }