TYPO3 API  SVNRelease
class.t3lib_db_preparedstatement.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 2010-2011 Xavier Perseguers <typo3@perseguers.ch>
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *  A copy is found in the textfile GPL.txt and important notices to the license
00017  *  from the author is found in LICENSE.txt distributed with these scripts.
00018  *
00019  *
00020  *  This script is distributed in the hope that it will be useful,
00021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  *  GNU General Public License for more details.
00024  *
00025  *  This copyright notice MUST APPEAR in all copies of the script!
00026  ***************************************************************/
00027 
00028 /**
00029  * TYPO3 prepared statement for t3lib_db class.
00030  *
00031  * USE:
00032  * In all TYPO3 scripts when you need to create a prepared query:
00033  * <code>
00034  * $statement = $GLOBALS['TYPO3_DB']->prepare_SELECTquery('*', 'pages', 'uid = :uid');
00035  * $statement->execute(array(':uid' => 2));
00036  * while (($row = $statement->fetch()) !== FALSE) {
00037  *  // ...
00038  * }
00039  * $statement->free();
00040  * </code>
00041  *
00042  * @author  Xavier Perseguers <typo3@perseguers.ch>
00043  * @package TYPO3
00044  * @subpackage t3lib
00045  */
00046 class t3lib_db_PreparedStatement {
00047 
00048     /**
00049      * Represents the SQL NULL data type.
00050      * @var integer
00051      */
00052     const PARAM_NULL = 0;
00053 
00054     /**
00055      * Represents the SQL INTEGER data type.
00056      * @var integer
00057      */
00058     const PARAM_INT = 1;
00059 
00060     /**
00061      * Represents the SQL CHAR, VARCHAR, or other string data type.
00062      * @var integer
00063      */
00064     const PARAM_STR = 2;
00065 
00066     /**
00067      * Represents a boolean data type.
00068      * @var integer
00069      */
00070     const PARAM_BOOL = 3;
00071 
00072     /**
00073      * Automatically detects underlying type
00074      * @var integer
00075      */
00076     const PARAM_AUTOTYPE = 4;
00077 
00078     /**
00079      * Specifies that the fetch method shall return each row as an array indexed by
00080      * column name as returned in the corresponding result set. If the result set
00081      * contains multiple columns with the same name, t3lib_db_PreparedStatement::FETCH_ASSOC
00082      * returns only a single value per column name.
00083      * @var integer
00084      */
00085     const FETCH_ASSOC = 2;
00086 
00087     /**
00088      * Specifies that the fetch method shall return each row as an array indexed by
00089      * column number as returned in the corresponding result set, starting at column 0.
00090      * @var integer
00091      */
00092     const FETCH_NUM = 3;
00093 
00094     /**
00095      * Query to be executed.
00096      * @var string
00097      */
00098     protected $query;
00099 
00100     /**
00101      * Components of the query to be executed.
00102      * @var array
00103      */
00104     protected $precompiledQueryParts;
00105 
00106     /**
00107      * Table (used to call $GLOBALS['TYPO3_DB']->fullQuoteStr().
00108      * @var string
00109      */
00110     protected $table;
00111 
00112     /**
00113      * Binding parameters.
00114      * @var array
00115      */
00116     protected $parameters;
00117 
00118     /**
00119      * Default fetch mode.
00120      * @var integer
00121      */
00122     protected $defaultFetchMode = self::FETCH_ASSOC;
00123 
00124     /**
00125      * MySQL result pointer (of SELECT query) / DBAL object.
00126      * @var pointer
00127      */
00128     protected $resource;
00129 
00130     /**
00131      * Creates a new PreparedStatement. Either $query or $queryComponents
00132      * should be used. Typically $query will be used by native MySQL TYPO3_DB
00133      * on a ready-to-be-executed query. On the other hand, DBAL will have
00134      * parse the query and will be able to safely know where parameters are used
00135      * and will use $queryComponents instead.
00136      * This constructor may only be used by t3lib_DB.
00137      *
00138      * @param string $query SQL query to be executed
00139      * @param string FROM table, used to call $GLOBALS['TYPO3_DB']->fullQuoteStr().
00140      * @param array $precompiledQueryParts Components of the query to be executed
00141      * @access private
00142      */
00143     public function __construct($query, $table, array $precompiledQueryParts = array()) {
00144         $this->query = $query;
00145         $this->precompiledQueryParts = $precompiledQueryParts;
00146         $this->table = $table;
00147         $this->parameters = array();
00148         $this->resource = NULL;
00149     }
00150 
00151     /**
00152      * Binds an array of values to corresponding named or question mark placeholders in the SQL
00153      * statement that was use to prepare the statement.
00154      *
00155      * Example 1:
00156      * <code>
00157      * $statement = $GLOBALS['TYPO3_DB']->prepare_SELECTquery('*', 'bugs', 'reported_by = ? AND bug_status = ?');
00158      * $statement->bindValues(array('goofy', 'FIXED'));
00159      * </code>
00160      *
00161      * Example 2:
00162      * <code>
00163      * $statement = $GLOBALS['TYPO3_DB']->prepare_SELECTquery('*', 'bugs', 'reported_by = :nickname AND bug_status = :status');
00164      * $statement->bindValues(array(':nickname' => 'goofy', ':status' => 'FIXED'));
00165      * </code>
00166      *
00167      * @param array $values The values to bind to the parameter. The PHP type of each array value will be used to decide which PARAM_* type to use (int, string, boolean, null), so make sure your variables are properly casted, if needed.
00168      * @return t3lib_db_PreparedStatement The current prepared statement to allow method chaining
00169      * @api
00170      */
00171     public function bindValues(array $values) {
00172         foreach ($values as $parameter => $value) {
00173             $key = is_int($parameter) ? $parameter + 1 : $parameter;
00174             $this->bindValue($key, $value, self::PARAM_AUTOTYPE);
00175         }
00176 
00177         return $this;
00178     }
00179 
00180     /**
00181      * Binds a value to a corresponding named or question mark placeholder in the SQL
00182      * statement that was use to prepare the statement.
00183      *
00184      * Example 1:
00185      * <code>
00186      * $statement = $GLOBALS['TYPO3_DB']->prepare_SELECTquery('*', 'bugs', 'reported_by = ? AND bug_status = ?');
00187      * $statement->bindValue(1, 'goofy');
00188      * $statement->bindValue(2, 'FIXED');
00189      * </code>
00190      *
00191      * Example 2:
00192      * <code>
00193      * $statement = $GLOBALS['TYPO3_DB']->prepare_SELECTquery('*', 'bugs', 'reported_by = :nickname AND bug_status = :status');
00194      * $statement->bindValue(':nickname', 'goofy');
00195      * $statement->bindValue(':status', 'FIXED');
00196      * </code>
00197      *
00198      * @param mixed $parameter Parameter identifier. For a prepared statement using named placeholders, this will be a parameter name of the form :name. For a prepared statement using question mark placeholders, this will be the 1-indexed position of the parameter.
00199      * @param mixed $value The value to bind to the parameter.
00200      * @param integer $data_type Explicit data type for the parameter using the t3lib_db_PreparedStatement::PARAM_* constants. If not given, the PHP type of the value will be used instead (int, string, boolean).
00201      * @return t3lib_db_PreparedStatement The current prepared statement to allow method chaining
00202      * @api
00203      */
00204     public function bindValue($parameter, $value, $data_type = self::PARAM_AUTOTYPE) {
00205         switch ($data_type) {
00206             case self::PARAM_INT:
00207                 if (!is_int($value)) {
00208                     throw new InvalidArgumentException('$value is not an integer as expected: ' . $value, 1281868686);
00209                 }
00210                 break;
00211             case self::PARAM_BOOL:
00212                 if (!is_bool($value)) {
00213                     throw new InvalidArgumentException('$value is not a boolean as expected: ' . $value, 1281868687);
00214                 }
00215                 break;
00216             case self::PARAM_NULL:
00217                 if (!is_null($value)) {
00218                     throw new InvalidArgumentException('$value is not NULL as expected: ' . $value, 1282489834);
00219                 }
00220                 break;
00221         }
00222 
00223         $key = is_int($parameter) ? $parameter - 1 : $parameter;
00224         $this->parameters[$key] = array(
00225             'value' => $value,
00226             'type' => ($data_type == self::PARAM_AUTOTYPE ? $this->guessValueType($value) : $data_type),
00227         );
00228 
00229         return $this;
00230     }
00231 
00232     /**
00233      * Executes the prepared statement. If the prepared statement included parameter
00234      * markers, you must either:
00235      * <ul>
00236      *   <li>call {@link t3lib_db_PreparedStatement::bindParam()} to bind PHP variables
00237      *   to the parameter markers: bound variables pass their value as input</li>
00238      *   <li>or pass an array of input-only parameter values</li>
00239      * </ul>
00240      *
00241      * $input_parameters behave as in {@link t3lib_db_PreparedStatement::bindParams()}
00242      * and work for both named parameters and question mark parameters.
00243      *
00244      * Example 1:
00245      * <code>
00246      * $statement = $GLOBALS['TYPO3_DB']->prepare_SELECTquery('*', 'bugs', 'reported_by = ? AND bug_status = ?');
00247      * $statement->execute(array('goofy', 'FIXED'));
00248      * </code>
00249      *
00250      * Example 2:
00251      * <code>
00252      * $statement = $GLOBALS['TYPO3_DB']->prepare_SELECTquery('*', 'bugs', 'reported_by = :nickname AND bug_status = :status');
00253      * $statement->execute(array(':nickname' => 'goofy', ':status' => 'FIXED'));
00254      * </code>
00255      *
00256      * @param array $input_parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. The PHP type of each array value will be used to decide which PARAM_* type to use (int, string, boolean, null), so make sure your variables are properly casted, if needed.
00257      * @return boolean Returns TRUE on success or FALSE on failure.
00258      * @api
00259      */
00260     public function execute(array $input_parameters = array()) {
00261         $query = $this->query;
00262         $precompiledQueryParts = $this->precompiledQueryParts;
00263         $parameterValues = $this->parameters;
00264 
00265         if (count($input_parameters) > 0) {
00266             $parameterValues = array();
00267             foreach ($input_parameters as $key => $value) {
00268                 $parameterValues[$key] = array(
00269                     'value' => $value,
00270                     'type' => $this->guessValueType($value),
00271                 );
00272             }
00273         }
00274 
00275         $this->replaceValuesInQuery($query, $precompiledQueryParts, $parameterValues);
00276         if (count($precompiledQueryParts) > 0) {
00277             $query = implode('', $precompiledQueryParts['queryParts']);
00278         }
00279         $this->resource = $GLOBALS['TYPO3_DB']->exec_PREPAREDquery($query, $precompiledQueryParts);
00280 
00281             // Empty binding parameters
00282         $this->parameters = array();
00283 
00284             // Return the success flag
00285         return ($this->resource ? TRUE : FALSE);
00286     }
00287 
00288     /**
00289      * Fetches a row from a result set associated with a t3lib_db_PreparedStatement object.
00290      *
00291      * @param integer $fetch_style Controls how the next row will be returned to the caller. This value must be one of the t3lib_db_PreparedStatement::FETCH_* constants. If omitted, default fetch mode for this prepared query will be used.
00292      * @return array Array of rows or FALSE if there are no more rows.
00293      * @api
00294      */
00295     public function fetch($fetch_style = 0) {
00296         if ($fetch_style == 0) {
00297             $fetch_style = $this->defaultFetchMode;
00298         }
00299         switch ($fetch_style) {
00300             case self::FETCH_ASSOC:
00301                 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($this->resource);
00302                 break;
00303             case self::FETCH_NUM:
00304                 $row = $GLOBALS['TYPO3_DB']->sql_fetch_row($this->resource);
00305                 break;
00306             default:
00307                 throw new InvalidArgumentException('$fetch_style must be either t3lib_db_PreparedStatement::FETCH_ASSOC or t3lib_db_PreparedStatement::FETCH_NUM', 1281646455);
00308         }
00309         return $row;
00310     }
00311 
00312     /**
00313      * Moves internal result pointer.
00314      *
00315      * @param integer $rowNumber Where to place the result pointer (0 = start)
00316      * @return boolean Returns TRUE on success or FALSE on failure.
00317      * @api
00318      */
00319     public function seek($rowNumber) {
00320         return $GLOBALS['TYPO3_DB']->sql_data_seek($this->resource, intval($rowNumber));
00321     }
00322 
00323     /**
00324      * Returns an array containing all of the result set rows.
00325      *
00326      * @param integer $fetch_style Controls the contents of the returned array as documented in {@link t3lib_db_PreparedStatement::fetch()}.
00327      * @return array Array of rows.
00328      * @api
00329      */
00330     public function fetchAll($fetch_style = 0) {
00331         $rows = array();
00332         while (($row = $this->fetch($fetch_style)) !== FALSE) {
00333             $rows[] = $row;
00334         }
00335         return $rows;
00336     }
00337 
00338     /**
00339      * Releases the cursor. Should always be call after having fetched rows from
00340      * a query execution.
00341      *
00342      * @return void
00343      * @api
00344      */
00345     public function free() {
00346         $GLOBALS['TYPO3_DB']->sql_free_result($this->resource);
00347     }
00348 
00349     /**
00350      * Returns the number of rows affected by the last SQL statement.
00351      *
00352      * @return integer The number of rows.
00353      * @api
00354      */
00355     public function rowCount() {
00356         return $GLOBALS['TYPO3_DB']->sql_num_rows($this->resource);
00357     }
00358 
00359     /**
00360      * Returns the error number on the last execute() call.
00361      *
00362      * @return integer Driver specific error code.
00363      * @api
00364      */
00365     public function errorCode() {
00366         return $GLOBALS['TYPO3_DB']->sql_errno();
00367     }
00368 
00369     /**
00370      * Returns an array of error information about the last operation performed by this statement handle.
00371      * The array consists of the following fields:
00372      * <ol start="0">
00373      *  <li>Driver specific error code.</li>
00374      *  <li>Driver specific error message</li>
00375      * </ol>
00376      *
00377      * @return array Array of error information.
00378      */
00379     public function errorInfo() {
00380         return array(
00381             $GLOBALS['TYPO3_DB']->sql_errno(),
00382             $GLOBALS['TYPO3_DB']->sql_error(),
00383         );
00384     }
00385 
00386     /**
00387      * Sets the default fetch mode for this prepared query.
00388      *
00389      * @param integer $mode One of the t3lib_db_PreparedStatement::FETCH_* constants
00390      * @return void
00391      * @api
00392      */
00393     public function setFetchMode($mode) {
00394         switch ($mode) {
00395             case self::FETCH_ASSOC:
00396             case self::FETCH_NUM:
00397                 $this->defaultFetchMode = $mode;
00398                 break;
00399             default:
00400                 throw new InvalidArgumentException('$mode must be either t3lib_db_PreparedStatement::FETCH_ASSOC or t3lib_db_PreparedStatement::FETCH_NUM', 1281875340);
00401         }
00402     }
00403 
00404     /**
00405      * Guesses the type of a given value.
00406      *
00407      * @param mixed $value
00408      * @return integer One of the t3lib_db_PreparedStatement::PARAM_* constants
00409      */
00410     protected function guessValueType($value) {
00411         if (is_bool($value)) {
00412             $type = self::PARAM_BOOL;
00413         } elseif (is_int($value)) {
00414             $type = self::PARAM_INT;
00415         } elseif (is_null($value)) {
00416             $type = self::PARAM_NULL;
00417         } else {
00418             $type = self::PARAM_STR;
00419         }
00420 
00421         return $type;
00422     }
00423 
00424     /**
00425      * Replaces values for each parameter in a query.
00426      *
00427      * @param string $query
00428      * @param array $precompiledQueryParts
00429      * @param array $parameterValues
00430      * @return void
00431      */
00432     protected function replaceValuesInQuery(&$query, array &$precompiledQueryParts, array $parameterValues) {
00433         foreach ($parameterValues as $key => $typeValue) {
00434             switch ($typeValue['type']) {
00435                 case self::PARAM_NULL:
00436                     $value = 'NULL';
00437                     break;
00438                 case self::PARAM_INT:
00439                     $value = intval($typeValue['value']);
00440                     break;
00441                 case self::PARAM_STR:
00442                     $value = $GLOBALS['TYPO3_DB']->fullQuoteStr($typeValue['value'], $this->table);
00443                     break;
00444                 case self::PARAM_BOOL:
00445                     $value = $typeValue['value'] ? 1 : 0;
00446                     break;
00447                 default:
00448                     throw new InvalidArgumentException(
00449                         sprintf('Unknown type %s used for parameter %s.', $typeValue['type'], $key),
00450                         1281859196
00451                     );
00452             }
00453 
00454             if (is_int($key)) {
00455                 if (count($precompiledQueryParts['queryParts']) > 0) {
00456                     $precompiledQueryParts['queryParts'][2 * $key + 1] = $value;
00457                 } else {
00458                     $parts = explode('?', $query, 2);
00459                     $parts[0] .= $value;
00460                     $query = implode('', $parts);
00461                 }
00462             } else {
00463                 if (!preg_match('/^:[\w]+$/', $key)) {
00464                     throw new InvalidArgumentException('Parameter names must start with ":" followed by an arbitrary number of alphanumerical characters.', 1282348825);
00465                 }
00466 
00467                 for ($i = 1; $i < count($precompiledQueryParts['queryParts']); $i++) {
00468                     if ($precompiledQueryParts['queryParts'][$i] === $key) {
00469                         $precompiledQueryParts['queryParts'][$i] = $value;
00470                     }
00471                 }
00472                     // Replace the marker (not preceeded by a word character or a ':' but
00473                     // followed by a word boundary)
00474                 $query = preg_replace('/(?<![\w:])' . $key . '\b/', $value, $query);
00475             }
00476         }
00477     }
00478 }
00479 
00480 
00481 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/db/class.t3lib_db_PreparedStatement.php'])) {
00482     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/db/class.t3lib_db_PreparedStatement.php']);
00483 }
00484 
00485 ?>