Skip to content

Commit

Permalink
Start integrating the yet unwritten QueryCacher into ORM\Query.
Browse files Browse the repository at this point in the history
I figure that caching query results is separate enough from building
& executing queries that a separate object is justified. Having
a separate object will make writing tests much simpler as well.
  • Loading branch information
markstory committed Jan 4, 2014
1 parent 84263ae commit f47e844
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 5 deletions.
73 changes: 68 additions & 5 deletions Cake/ORM/Query.php
Expand Up @@ -21,6 +21,8 @@
use Cake\Database\Statement\BufferedStatement;
use Cake\Database\Statement\CallbackStatement;
use Cake\Event\Event;
use Cake\ORM\QueryCacher;
use Cake\ORM\Table;

/**
* Extends the base Query class to provide new methods related to association
Expand Down Expand Up @@ -125,6 +127,13 @@ class Query extends DatabaseQuery {
*/
protected $_hydrate = true;

/**
* A query cacher instance if this query has caching enabled.
*
* @var Cake\ORM\QueryCacher
*/
protected $_cache;

/**
* @param Cake\Database\Connection $connection
* @param Cake\ORM\Table $table
Expand Down Expand Up @@ -400,6 +409,56 @@ public function setResult($results) {
return $this;
}

/**
* Enable result caching for this query.
*
* If a query has caching enabled, it will do the following when executed:
*
* - Check the cache for $key. If there are results no SQL will be executed.
* Instead the cached results will be returned.
* - When the cached data is stale/missing the result set will be cached as the query
* is executed.
*
* ## Usage
*
* {{{
* // Simple string key + config
* $query->cache('my_key', 'db_results');
*
* // Function to generate key.
* $query->cache(function($q) {
* $key = serialize($q->clause('select'));
* $key .= serialize($q->clause('where'));
* return md5($key);
* });
*
* // Using a pre-built cache engine.
* $query->cache('my_key', $engine);
*
*
* // Disable caching
* $query->cache(false);
* }}}
*
* @param false|string|Closure $key Either the cache key or a function to generate the cache key.
* When using a function, this query instance will be supplied as an argument.
* @param string|CacheEngine $config Either the name of the cache config to use, or
* a cache config instance.
* @return Query The query instance.
* @throws \RuntimeException When you attempt to cache a non-select query.
*/
public function cache($key, $config = 'default') {
if ($this->_type !== 'select' && $this->_type !== null) {
throw new \RuntimeException('You cannot cache the results of non-select queries.');
}
if ($key === false) {
$this->_cache = null;
return $this;
}
$this->_cache = new QueryCacher($key, $config);
return $this;
}

/**
* Executes this query and returns a results iterator. This function is required
* for implementing the IteratorAggregate interface and allows the query to be
Expand Down Expand Up @@ -451,11 +510,15 @@ public function getResults() {
if (isset($this->_results)) {
return $this->_results;
}

$this->_results = $this->_decorateResults(
new ResultSet($this, $this->execute())
);

if ($this->_cache) {
$results = $this->_cache->fetch($this);
}
if (!isset($results)) {
$results = $this->_decorateResults(
new ResultSet($this, $this->execute())
);
}
$this->_results = $results;
return $this->_results;
}

Expand Down
13 changes: 13 additions & 0 deletions Cake/Test/TestCase/ORM/QueryTest.php
Expand Up @@ -1534,4 +1534,17 @@ public function testCollectionProxyBadMethod() {
TableRegistry::get('articles')->find('all')->derpFilter();
}

/**
* cache() should fail on non select queries.
*
* @expectedException RuntimeException
* @return void
*/
public function testCacheErrorOnNonSelect() {
$table = TableRegistry::get('articles', ['table' => 'articles']);
$query = new Query($this->connection, $table);
$query->insert(['test']);
$query->cache('my_key');
}

}

0 comments on commit f47e844

Please sign in to comment.