. */ namespace Doctrine\DBAL\Cache; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Connection; use Doctrine\Common\Cache\Cache; use PDO; /** * Cache statement for SQL results. * * A result is saved in multiple cache keys, there is the originally specified * cache key which is just pointing to result rows by key. The following things * have to be ensured: * * 1. lifetime of the original key has to be longer than that of all the individual rows keys * 2. if any one row key is missing the query has to be re-executed. * * Also you have to realize that the cache will load the whole result into memory at once to ensure 2. * This means that the memory usage for cached results might increase by using this feature. */ class ResultCacheStatement implements \IteratorAggregate, ResultStatement { /** * @var \Doctrine\Common\Cache\Cache */ private $resultCache; /** * * @var string */ private $cacheKey; /** * @var string */ private $realKey; /** * @var int */ private $lifetime; /** * @var Doctrine\DBAL\Driver\Statement */ private $statement; /** * Did we reach the end of the statement? * * @var bool */ private $emptied = false; /** * @var array */ private $data; /** * @var int */ private $defaultFetchStyle = PDO::FETCH_BOTH; /** * @param Statement $stmt * @param Cache $resultCache * @param string $cacheKey * @param string $realKey * @param int $lifetime */ public function __construct(Statement $stmt, Cache $resultCache, $cacheKey, $realKey, $lifetime) { $this->statement = $stmt; $this->resultCache = $resultCache; $this->cacheKey = $cacheKey; $this->realKey = $realKey; $this->lifetime = $lifetime; } /** * Closes the cursor, enabling the statement to be executed again. * * @return boolean Returns TRUE on success or FALSE on failure. */ public function closeCursor() { $this->statement->closeCursor(); if ($this->emptied && $this->data !== null) { $data = $this->resultCache->fetch($this->cacheKey); if (!$data) { $data = array(); } $data[$this->realKey] = $this->data; $this->resultCache->save($this->cacheKey, $data, $this->lifetime); unset($this->data); } } /** * columnCount * Returns the number of columns in the result set * * @return integer Returns the number of columns in the result set represented * by the PDOStatement object. If there is no result set, * this method should return 0. */ public function columnCount() { return $this->statement->columnCount(); } public function setFetchMode($fetchStyle) { $this->defaultFetchStyle = $fetchStyle; } public function getIterator() { $data = $this->fetchAll($this->defaultFetchStyle); return new \ArrayIterator($data); } /** * fetch * * @see Query::HYDRATE_* constants * @param integer $fetchStyle Controls how the next row will be returned to the caller. * This value must be one of the Query::HYDRATE_* constants, * defaulting to Query::HYDRATE_BOTH * * @param integer $cursorOrientation For a PDOStatement object representing a scrollable cursor, * this value determines which row will be returned to the caller. * This value must be one of the Query::HYDRATE_ORI_* constants, defaulting to * Query::HYDRATE_ORI_NEXT. To request a scrollable cursor for your * PDOStatement object, * you must set the PDO::ATTR_CURSOR attribute to Doctrine::CURSOR_SCROLL when you * prepare the SQL statement with Doctrine_Adapter_Interface->prepare(). * * @param integer $cursorOffset For a PDOStatement object representing a scrollable cursor for which the * $cursorOrientation parameter is set to Query::HYDRATE_ORI_ABS, this value specifies * the absolute number of the row in the result set that shall be fetched. * * For a PDOStatement object representing a scrollable cursor for * which the $cursorOrientation parameter is set to Query::HYDRATE_ORI_REL, this value * specifies the row to fetch relative to the cursor position before * PDOStatement->fetch() was called. * * @return mixed */ public function fetch($fetchStyle = PDO::FETCH_BOTH) { if ($this->data === null) { $this->data = array(); } $row = $this->statement->fetch(PDO::FETCH_ASSOC); if ($row) { $this->data[] = $row; if ($fetchStyle == PDO::FETCH_ASSOC) { return $row; } else if ($fetchStyle == PDO::FETCH_NUM) { return array_values($row); } else if ($fetchStyle == PDO::FETCH_BOTH) { return array_merge($row, array_values($row)); } else { throw new \InvalidArgumentException("Invalid fetch-style given for caching result."); } } $this->emptied = true; return false; } /** * Returns an array containing all of the result set rows * * @param integer $fetchStyle Controls how the next row will be returned to the caller. * This value must be one of the Query::HYDRATE_* constants, * defaulting to Query::HYDRATE_BOTH * * @param integer $columnIndex Returns the indicated 0-indexed column when the value of $fetchStyle is * Query::HYDRATE_COLUMN. Defaults to 0. * * @return array */ public function fetchAll($fetchStyle = PDO::FETCH_BOTH) { $rows = array(); while ($row = $this->fetch($fetchStyle)) { $rows[] = $row; } return $rows; } /** * fetchColumn * Returns a single column from the next row of a * result set or FALSE if there are no more rows. * * @param integer $columnIndex 0-indexed number of the column you wish to retrieve from the row. If no * value is supplied, PDOStatement->fetchColumn() * fetches the first column. * * @return string returns a single column in the next row of a result set. */ public function fetchColumn($columnIndex = 0) { $row = $this->fetch(PDO::FETCH_NUM); if (!isset($row[$columnIndex])) { // TODO: verify this is correct behavior return false; } return $row[$columnIndex]; } /** * rowCount * rowCount() returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement * executed by the corresponding object. * * If the last SQL statement executed by the associated Statement object was a SELECT statement, * some databases may return the number of rows returned by that statement. However, * this behaviour is not guaranteed for all databases and should not be * relied on for portable applications. * * @return integer Returns the number of rows. */ public function rowCount() { return $this->statement->rowCount(); } }