TYPO3 CMS  TYPO3_7-6
PreparedStatement.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
32 {
38  const PARAM_NULL = 0;
39 
45  const PARAM_INT = 1;
46 
52  const PARAM_STR = 2;
53 
59  const PARAM_BOOL = 3;
60 
66  const PARAM_AUTOTYPE = 4;
67 
76  const FETCH_ASSOC = 2;
77 
84  const FETCH_NUM = 3;
85 
91  protected $query;
92 
99 
105  protected $table;
106 
112  protected $parameters;
113 
119  protected $defaultFetchMode = self::FETCH_ASSOC;
120 
126  protected $statement;
127 
131  protected $fields;
132 
136  protected $buffer;
137 
145 
160  public function __construct($query, $table, array $precompiledQueryParts = [])
161  {
162  $this->query = $query;
163  $this->precompiledQueryParts = $precompiledQueryParts;
164  $this->table = $table;
165  $this->parameters = [];
166 
167  // Test if named placeholders are used
168  if ($this->hasNamedPlaceholders($query) || !empty($precompiledQueryParts)) {
169  $this->statement = null;
170  } else {
171  // Only question mark placeholders are used
172  $this->statement = $GLOBALS['TYPO3_DB']->prepare_PREPAREDquery($this->query, $this->precompiledQueryParts);
173  }
174 
175  $this->parameterWrapToken = $this->generateParameterWrapToken();
176  }
177 
198  public function bindValues(array $values)
199  {
200  foreach ($values as $parameter => $value) {
201  $key = is_int($parameter) ? $parameter + 1 : $parameter;
202  $this->bindValue($key, $value, self::PARAM_AUTOTYPE);
203  }
204  return $this;
205  }
206 
231  public function bindValue($parameter, $value, $data_type = self::PARAM_AUTOTYPE)
232  {
233  switch ($data_type) {
234  case self::PARAM_INT:
235  if (!is_int($value)) {
236  throw new \InvalidArgumentException('$value is not an integer as expected: ' . $value, 1281868686);
237  }
238  break;
239  case self::PARAM_BOOL:
240  if (!is_bool($value)) {
241  throw new \InvalidArgumentException('$value is not a boolean as expected: ' . $value, 1281868687);
242  }
243  break;
244  case self::PARAM_NULL:
245  if (!is_null($value)) {
246  throw new \InvalidArgumentException('$value is not NULL as expected: ' . $value, 1282489834);
247  }
248  break;
249  }
250  if (!is_int($parameter) && !preg_match('/^:[\\w]+$/', $parameter)) {
251  throw new \InvalidArgumentException('Parameter names must start with ":" followed by an arbitrary number of alphanumerical characters.', 1395055513);
252  }
253  $key = is_int($parameter) ? $parameter - 1 : $parameter;
254  $this->parameters[$key] = [
255  'value' => $value,
256  'type' => $data_type == self::PARAM_AUTOTYPE ? $this->guessValueType($value) : $data_type
257  ];
258  return $this;
259  }
260 
290  public function execute(array $input_parameters = [])
291  {
292  $parameterValues = $this->parameters;
293  if (!empty($input_parameters)) {
294  $parameterValues = [];
295  foreach ($input_parameters as $key => $value) {
296  $parameterValues[$key] = [
297  'value' => $value,
298  'type' => $this->guessValueType($value)
299  ];
300  }
301  }
302 
303  if ($this->statement !== null) {
304  // The statement has already been executed, we try to reset it
305  // for current run but will set it to NULL if it fails for some
306  // reason, just as if it were the first run
307  if (!@$this->statement->reset()) {
308  $this->statement = null;
309  }
310  }
311  if ($this->statement === null) {
312  // The statement has never been executed so we prepare it and
313  // store it for further reuse
316 
318  if (!empty($precompiledQueryParts)) {
319  $query = implode('', $precompiledQueryParts['queryParts']);
320  }
321  $this->statement = $GLOBALS['TYPO3_DB']->prepare_PREPAREDquery($query, $precompiledQueryParts);
322  if ($this->statement === null) {
323  return false;
324  }
325  }
326 
327  $combinedTypes = '';
328  $values = [];
329  foreach ($parameterValues as $parameterValue) {
330  switch ($parameterValue['type']) {
331  case self::PARAM_NULL:
332  $type = 's';
333  $value = null;
334  break;
335  case self::PARAM_INT:
336  $type = 'i';
337  $value = (int)$parameterValue['value'];
338  break;
339  case self::PARAM_STR:
340  $type = 's';
341  $value = $parameterValue['value'];
342  break;
343  case self::PARAM_BOOL:
344  $type = 'i';
345  $value = $parameterValue['value'] ? 1 : 0;
346  break;
347  default:
348  throw new \InvalidArgumentException(sprintf('Unknown type %s used for parameter %s.', $parameterValue['type'], $key), 1281859196);
349  }
350 
351  $combinedTypes .= $type;
352  $values[] = $value;
353  }
354 
355  // ->bind_param requires second up to last arguments as references
356  if (!empty($combinedTypes)) {
357  $bindParamArguments = [];
358  $bindParamArguments[] = $combinedTypes;
359  $numberOfExtraParamArguments = count($values);
360  for ($i = 0; $i < $numberOfExtraParamArguments; $i++) {
361  $bindParamArguments[] = &$values[$i];
362  }
363 
364  call_user_func_array([$this->statement, 'bind_param'], $bindParamArguments);
365  }
366 
367  $success = $this->statement->execute();
368 
369  // Store result
370  if (!$success || $this->statement->store_result() === false) {
371  return false;
372  }
373 
374  if (empty($this->fields)) {
375  // Store the list of fields
376  if ($this->statement instanceof \mysqli_stmt) {
377  $result = $this->statement->result_metadata();
378  if ($result instanceof \mysqli_result) {
379  $fields = $result->fetch_fields();
380  $result->close();
381  }
382  } else {
383  $fields = $this->statement->fetch_fields();
384  }
385  if (is_array($fields)) {
386  foreach ($fields as $field) {
387  $this->fields[] = $field->name;
388  }
389  }
390  }
391 
392  // New result set available
393  $this->buffer = null;
394 
395  // Empty binding parameters
396  $this->parameters = [];
397 
398  // Return the success flag
399  return $success;
400  }
401 
409  public function fetch($fetch_style = 0)
410  {
411  if ($fetch_style == 0) {
412  $fetch_style = $this->defaultFetchMode;
413  }
414 
415  if ($this->statement instanceof \mysqli_stmt) {
416  if ($this->buffer === null) {
417  $variables = [];
418  $this->buffer = [];
419  foreach ($this->fields as $field) {
420  $this->buffer[$field] = null;
421  $variables[] = &$this->buffer[$field];
422  }
423 
424  call_user_func_array([$this->statement, 'bind_result'], $variables);
425  }
426  $success = $this->statement->fetch();
427  $columns = $this->buffer;
428  } else {
429  $columns = $this->statement->fetch();
430  $success = is_array($columns);
431  }
432 
433  if ($success) {
434  $row = [];
435  foreach ($columns as $key => $value) {
436  switch ($fetch_style) {
437  case self::FETCH_ASSOC:
438  $row[$key] = $value;
439  break;
440  case self::FETCH_NUM:
441  $row[] = $value;
442  break;
443  default:
444  throw new \InvalidArgumentException('$fetch_style must be either TYPO3\\CMS\\Core\\Database\\PreparedStatement::FETCH_ASSOC or TYPO3\\CMS\\Core\\Database\\PreparedStatement::FETCH_NUM', 1281646455);
445  }
446  }
447  } else {
448  $row = false;
449  }
450 
451  return $row;
452  }
453 
461  public function seek($rowNumber)
462  {
463  $success = $this->statement->data_seek((int)$rowNumber);
464  if ($this->statement instanceof \mysqli_stmt) {
465  // data_seek() does not return anything
466  $success = true;
467  }
468  return $success;
469  }
470 
478  public function fetchAll($fetch_style = 0)
479  {
480  $rows = [];
481  while (($row = $this->fetch($fetch_style)) !== false) {
482  $rows[] = $row;
483  }
484  return $rows;
485  }
486 
494  public function free()
495  {
496  $this->statement->close();
497  }
498 
505  public function rowCount()
506  {
507  return $this->statement->num_rows;
508  }
509 
516  public function errorCode()
517  {
518  return $this->statement->errno;
519  }
520 
531  public function errorInfo()
532  {
533  return [
534  $this->statement->errno,
535  $this->statement->error
536  ];
537  }
538 
546  public function setFetchMode($mode)
547  {
548  switch ($mode) {
549  case self::FETCH_ASSOC:
550  case self::FETCH_NUM:
551  $this->defaultFetchMode = $mode;
552  break;
553  default:
554  throw new \InvalidArgumentException('$mode must be either TYPO3\\CMS\\Core\\Database\\PreparedStatement::FETCH_ASSOC or TYPO3\\CMS\\Core\\Database\\PreparedStatement::FETCH_NUM', 1281875340);
555  }
556  }
557 
564  protected function guessValueType($value)
565  {
566  if (is_bool($value)) {
567  $type = self::PARAM_BOOL;
568  } elseif (is_int($value)) {
569  $type = self::PARAM_INT;
570  } elseif (is_null($value)) {
571  $type = self::PARAM_NULL;
572  } else {
573  $type = self::PARAM_STR;
574  }
575  return $type;
576  }
577 
584  protected function hasNamedPlaceholders($query)
585  {
586  $matches = preg_match('/(?<![\\w:]):[\\w]+\\b/', $query);
587  return $matches > 0;
588  }
589 
598  protected function convertNamedPlaceholdersToQuestionMarks(&$query, array &$parameterValues, array &$precompiledQueryParts)
599  {
600  $queryPartsCount = is_array($precompiledQueryParts['queryParts']) ? count($precompiledQueryParts['queryParts']) : 0;
601  $newParameterValues = [];
602  $hasNamedPlaceholders = false;
603 
604  if ($queryPartsCount === 0) {
605  $hasNamedPlaceholders = $this->hasNamedPlaceholders($query);
606  if ($hasNamedPlaceholders) {
607  $query = $this->tokenizeQueryParameterMarkers($query, $parameterValues);
608  }
609  } elseif (!empty($parameterValues)) {
610  $hasNamedPlaceholders = !is_int(key($parameterValues));
611  if ($hasNamedPlaceholders) {
612  for ($i = 1; $i < $queryPartsCount; $i += 2) {
613  $key = $precompiledQueryParts['queryParts'][$i];
614  $precompiledQueryParts['queryParts'][$i] = '?';
615  $newParameterValues[] = $parameterValues[$key];
616  }
617  }
618  }
619 
620  if ($hasNamedPlaceholders) {
621  if ($queryPartsCount === 0) {
622  // Convert named placeholders to standard question mark placeholders
623  $quotedParamWrapToken = preg_quote($this->parameterWrapToken, '/');
624  while (preg_match(
625  '/' . $quotedParamWrapToken . '(.*?)' . $quotedParamWrapToken . '/',
626  $query,
627  $matches
628  )) {
629  $key = $matches[1];
630 
631  $newParameterValues[] = $parameterValues[$key];
632  $query = preg_replace(
633  '/' . $quotedParamWrapToken . $key . $quotedParamWrapToken . '/',
634  '?',
635  $query,
636  1
637  );
638  }
639  }
640 
641  $parameterValues = $newParameterValues;
642  }
643  }
644 
653  protected function tokenizeQueryParameterMarkers($query, array $parameterValues)
654  {
655  $unnamedParameterCount = 0;
656  foreach ($parameterValues as $key => $typeValue) {
657  if (!is_int($key)) {
658  if (!preg_match('/^:[\\w]+$/', $key)) {
659  throw new \InvalidArgumentException('Parameter names must start with ":" followed by an arbitrary number of alphanumerical characters.', 1282348825);
660  }
661  // Replace the marker (not preceded by a word character or a ':' but
662  // followed by a word boundary)
663  $query = preg_replace('/(?<![\\w:])' . preg_quote($key, '/') . '\\b/', $this->parameterWrapToken . $key . $this->parameterWrapToken, $query);
664  } else {
665  $unnamedParameterCount++;
666  }
667  }
668  $parts = explode('?', $query, $unnamedParameterCount + 1);
669  $query = implode($this->parameterWrapToken . '?' . $this->parameterWrapToken, $parts);
670  return $query;
671  }
672 
678  protected function generateParameterWrapToken()
679  {
681  }
682 }
bindValue($parameter, $value, $data_type=self::PARAM_AUTOTYPE)
__construct($query, $table, array $precompiledQueryParts=[])
convertNamedPlaceholdersToQuestionMarks(&$query, array &$parameterValues, array &$precompiledQueryParts)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
tokenizeQueryParameterMarkers($query, array $parameterValues)