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