[Solar-svn] Revision 3164
pmjones at solarphp.com
pmjones at solarphp.com
Fri May 16 20:27:53 CDT 2008
Solar_Sql_Model: [ADD] Automatic versioned data caching.
* [ADD] Config key 'cache', property $_cache, and method _fixCache() to build
the caching object for versioned data.
* [ADD] Config key 'auto_cache' turns caching-by-default on and off for all
fetches.
* [ADD] Method _fetchResultSelect() to provide a unified way to check the
cache, fetch results from the database on a cache miss, and add the results
to the cache as needed.
* [CHG] The method fixSelectParams(), and therefor the various fetch*()
methods now recognize two new params: 'cache' (whether or not to use the
cache), and 'cache_key' (an explicit cache key to use for the fetch; the
default key is constructed from the params, so you generally shouldn't need
to specify a key).
* [CHG] Methods insert(), update(), and delete() now delete cached data when
successful.
* [FIX] Method newSelect() $params is now optional.
Modified: trunk/Solar/Sql/Model.php
===================================================================
--- trunk/Solar/Sql/Model.php 2008-05-17 00:55:01 UTC (rev 3163)
+++ trunk/Solar/Sql/Model.php 2008-05-17 01:27:52 UTC (rev 3164)
@@ -2,7 +2,8 @@
/**
*
* An SQL-centric Model class combining TableModule and TableDataGateway,
- * using a Collection of Record objects for returns.
+ * using a Collection of Record objects for returns, with integrated caching
+ * of versioned result data.
*
* @category Solar
*
@@ -26,24 +27,41 @@
* `sql`
* : (dependency) A Solar_Sql dependency.
*
+ * `cache`
+ * : (dependency) A Solar_Cache dependency for the Solar_Sql_Model_Cache
+ * object.
+ *
* @var array
*
*/
protected $_Solar_Sql_Model = array(
- 'sql' => 'sql',
+ 'sql' => 'sql',
+ 'cache' => array(
+ 'adapter' => 'Solar_Cache_Adapter_Var',
+ ),
+ 'auto_cache' => false,
);
/**
*
* A Solar_Sql dependency object.
*
- * @var Solar_Sql
+ * @var Solar_Sql_Adapter
*
*/
protected $_sql = null;
/**
*
+ * A Solar_Sql_Model_Cache object.
+ *
+ * @var Solar_Sql_Model_Cache
+ *
+ */
+ protected $_cache = null;
+
+ /**
+ *
* When data values for this model are part of an array, use this name
* as the array key for those values.
*
@@ -119,6 +137,8 @@
*/
protected $_filter_class = null;
+ protected $_cache_class = 'Solar_Sql_Model_Cache';
+
// -----------------------------------------------------------------
//
// Table and index definition
@@ -440,12 +460,12 @@
// main construction
parent::__construct($config);
+ // our class name so that we don't call get_class() all the time
+ $this->_class = get_class($this);
+
// connect to the database
$this->_sql = Solar::dependency('Solar_Sql', $this->_config['sql']);
- // our class name so that we don't call get_class() all the time
- $this->_class = get_class($this);
-
// user-defined setup
$this->_setup();
@@ -458,6 +478,13 @@
$this->_fixOrder();
$this->_fixPropertyCols();
$this->_fixFilters(); // including filter class
+ $this->_fixCache(); // including cache class
+
+ // create the cache object and set its model
+ $this->_cache = Solar::factory($this->_cache_class, array(
+ 'cache' => $this->_config['cache'],
+ ));
+ $this->_cache->setModel($this);
}
/**
@@ -473,6 +500,8 @@
foreach ($this->_related as $key => $val) {
unset($this->_related[$key]);
}
+
+ unset($this->_cache);
}
/**
@@ -690,20 +719,23 @@
* `bind`
* : (array) Key-value pairs to bind into the query.
*
+ * `cache`
+ * : (bool) Use the cache?
+ *
+ * `cache_key`
+ * : (bool) An explicit cache key to use; otherwise, defaults to the
+ * serialized SELECT params.
+ *
* @param array $params An array of parameters for the fetch, with keys
- * for 'cols', 'where', 'group', 'having, 'order', and 'page'.
+ * for 'cols', 'where', 'group', 'having', 'order', etc.
*
* @return Solar_Sql_Model_Collection A collection object.
*
*/
public function fetchAll($params = array())
{
- // prepare
- $params = $this->fixSelectParams($params);
- $select = $this->newSelect($params);
-
- // fetch
- $result = $select->fetchAll();
+ // fetch the result array and select object
+ list($result, $select) = $this->_fetchResultSelect('all', $params);
if (! $result) {
return array();
}
@@ -762,20 +794,23 @@
* `bind`
* : (array) Key-value pairs to bind into the query.
*
+ * `cache`
+ * : (bool) Use the cache?
+ *
+ * `cache_key`
+ * : (bool) An explicit cache key to use; otherwise, defaults to the
+ * serialized SELECT params.
+ *
* @param array $params An array of parameters for the fetch, with keys
- * for 'cols', 'where', 'group', 'having, 'order', and 'page'.
+ * for 'cols', 'where', 'group', 'having', 'order', etc.
*
* @return Solar_Sql_Model_Collection A collection object.
*
*/
public function fetchAssoc($params = array())
{
- // prepare
- $params = $this->fixSelectParams($params);
- $select = $this->newSelect($params);
-
- // fetch
- $result = $select->fetchAssoc();
+ // fetch the result array and select object
+ list($result, $select) = $this->_fetchResultSelect('assoc', $params);
if (! $result) {
return array();
}
@@ -856,20 +891,23 @@
* `bind`
* : (array) Key-value pairs to bind into the query.
*
+ * `cache`
+ * : (bool) Use the cache?
+ *
+ * `cache_key`
+ * : (bool) An explicit cache key to use; otherwise, defaults to the
+ * serialized SELECT params.
+ *
* @param array $params An array of parameters for the fetch, with keys
- * for 'cols', 'where', 'group', 'having, and 'order'.
+ * for 'cols', 'where', 'group', 'having', 'order', etc.
*
* @return Solar_Sql_Model_Record A record object.
*
*/
public function fetchOne($params = array())
{
- // prepare
- $params = $this->fixSelectParams($params);
- $select = $this->newSelect($params);
-
- // fetch
- $result = $select->fetchOne();
+ // fetch the result array and select object
+ list($result, $select) = $this->_fetchResultSelect('one', $params);
if (! $result) {
return null;
}
@@ -922,20 +960,23 @@
* `bind`
* : (array) Key-value pairs to bind into the query.
*
+ * `cache`
+ * : (bool) Use the cache?
+ *
+ * `cache_key`
+ * : (bool) An explicit cache key to use; otherwise, defaults to the
+ * serialized SELECT params.
+ *
* @param array $params An array of parameters for the fetch, with keys
- * for 'cols', 'where', 'group', 'having, and 'order'.
+ * for 'cols', 'where', 'group', 'having', 'order', etc.
*
* @return array
*
*/
public function fetchCol($params = array())
{
- // prepare
- $params = $this->fixSelectParams($params);
- $select = $this->newSelect($params);
-
- // fetch
- $result = $select->fetchCol();
+ // fetch the result array and select object
+ list($result, $select) = $this->_fetchResultSelect('col', $params);
if ($result) {
return $result;
} else {
@@ -977,19 +1018,15 @@
* : (array) Key-value pairs to bind into the query.
*
* @param array $params An array of parameters for the fetch, with keys
- * for 'cols', 'where', 'group', 'having, and 'order'.
+ * for 'cols', 'where', 'group', 'having', 'order', etc.
*
* @return array
*
*/
public function fetchPairs($params = array())
{
- // prepare
- $params = $this->fixSelectParams($params);
- $select = $this->newSelect($params);
-
- // fetch
- $result = $select->fetchPairs();
+ // fetch the result array and select object
+ list($result, $select) = $this->_fetchResultSelect('pairs', $params);
if ($result) {
return $result;
} else {
@@ -1030,17 +1067,68 @@
* `bind`
* : (array) Key-value pairs to bind into the query.
*
+ * `cache`
+ * : (bool) Use the cache?
+ *
+ * `cache_key`
+ * : (bool) An explicit cache key to use; otherwise, defaults to the
+ * serialized SELECT params.
+ *
* @param array $params An array of parameters for the fetch, with keys
- * for 'cols', 'where', 'group', 'having, and 'order'.
+ * for 'cols', 'where', 'group', 'having', 'order', etc.
*
* @return mixed The single value from the model query, or null.
*
*/
public function fetchValue($params = array())
{
+ // fetch the result array and select object
+ list($result, $select) = $this->_fetchResultSelect('value', $params);
+ return $result;
+ }
+
+ /**
+ *
+ * Returns a data result and the select used to fetch the data.
+ *
+ * If caching is turned on, this will fetch from the cache (if available)
+ * and save the result back to the cache (if needed).
+ *
+ * @param string $type The type of fetch to perform: 'all', 'one', etc.
+ *
+ * @param array &$params A reference to the params for the select; these
+ * will be passed through fixSelectParams(), so the calling code doesn't
+ * have to do it twice.
+ *
+ * @return array An array of two elements; element 0 is the result data,
+ * element 1 is the Solar_Sql_Select object used to fetch the data. Note
+ * that if the
+ */
+ public function _fetchResultSelect($type, &$params)
+ {
$params = $this->fixSelectParams($params);
$select = $this->newSelect($params);
- return $select->fetchValue();
+
+ // fetch from cache?
+ if ($params['cache']) {
+ $key = $this->_cache->entry($params);
+ $result = $this->_cache->fetch($key);
+ if ($result !== false) {
+ // found some data!
+ return array($result, $select);
+ }
+ }
+
+ // attempt to fetch from database, and add to the cache
+ $result = $select->fetch($type);
+
+ // add to cache? (use 'add' to avoid race conditions.)
+ if ($params['cache']) {
+ $this->_cache->add($key, $result);
+ }
+
+ // done
+ return array($result, $select);
}
/**
@@ -1169,6 +1257,47 @@
*
* "Cleans up" SELECT clause parameters.
*
+ * `eager`
+ * : (string|array) Eager-fetch records from these related models.
+ *
+ * `distinct`
+ * : (bool) Use DISTINCT?
+ *
+ * `cols`
+ * : (string|array) Return only these columns.
+ *
+ * `where`
+ * : (string|array) A Solar_Sql_Select::multiWhere() value parameter to
+ * restrict which records are returned.
+ *
+ * `group`
+ * : (string|array) GROUP BY these columns.
+ *
+ * `having`
+ * : (string|array) HAVING these column values.
+ *
+ * `order`
+ * : (string|array) ORDER BY these columns.
+ *
+ * `paging`
+ * : (int) Return this many records per page.
+ *
+ * `page`
+ * : (int) Return only records from this page-number.
+ *
+ * `bind`
+ * : (array) Key-value pairs to bind into the query.
+ *
+ * `count_pages`
+ * : (bool) Perform a second query for count and pages.
+ *
+ * `cache`
+ * : (bool) Use the cache?
+ *
+ * `cache_key`
+ * : (bool) An explicit cache key to use; otherwise, defaults to the
+ * serialized SELECT params.
+ *
* @param array $params The parameters for the SELECT clauses.
*
* @return array A normalized set of clause params.
@@ -1178,6 +1307,16 @@
{
settype($params, 'array');
+ // if we have eager values, make sure they're unique
+ if (! empty($params['eager'])) {
+ $params['eager'] = array_unique((array) $params['eager']);
+ }
+
+ // even after uniqing, the eager values might still be empty
+ if (empty($params['eager'])) {
+ $params['eager'] = null;
+ }
+
if (empty($params['distinct'])) {
$params['distinct'] = null;
}
@@ -1192,16 +1331,6 @@
$params['cols'] = array_keys($this->_table_cols);
}
- // if we have eager values, make sure they're unique
- if (! empty($params['eager'])) {
- $params['eager'] = array_unique((array) $params['eager']);
- }
-
- // even after uniqing, the eager values might still be empty
- if (empty($params['eager'])) {
- $params['eager'] = null;
- }
-
if (empty($params['where'])) {
$params['where'] = null;
}
@@ -1234,6 +1363,22 @@
$params['count_pages'] = false;
}
+ // go by array_key_exists() so that an explicit "false" does not
+ // accidentally get overwritten
+ if (! array_key_exists('cache', $params)) {
+ // key not present, use the default
+ $params['cache'] = $this->_config['auto_cache'];
+ }
+
+ // force to boolean
+ $params['cache'] = (bool) $params['cache'];
+
+ // explicit cache key?
+ if (empty($params['cache_key'])) {
+ $params['cache_key'] = false;
+ }
+
+ // done
return $params;
}
@@ -1242,14 +1387,15 @@
* Returns a new Solar_Sql_Select tool, with the proper SQL object
* injected automatically, and with eager "to-one" associations joined.
*
- * @param array $params An array of SELECT parameters (esp. the 'eager'
- * param).
+ * @param array $params An array of SELECT parameters.
*
* @return Solar_Sql_Select
*
*/
- public function newSelect($params)
+ public function newSelect($params = null)
{
+ $params = $this->fixSelectParams($params);
+
// get the select object
$select = Solar::factory(
$this->_select_class,
@@ -1466,13 +1612,15 @@
/**
*
- * Inserts one row to the model table.
+ * Inserts one row to the model table and deletes cache entries.
*
* @param array|Solar_Sql_Model_Record $spec The row data to insert.
*
* @return array The data as inserted, including auto-incremented values,
* auto-sequence values, created/updated/inherit values, etc.
*
+ * @see Solar_Sql_Model_Cache::deleteAll()
+ *
*/
public function insert($spec)
{
@@ -1552,6 +1700,7 @@
* Post-insert
*/
// no exception thrown, so it must have worked.
+
// if there was an autoincrement column, set its value in the data.
foreach ($this->_table_cols as $key => $val) {
if ($val['autoinc']) {
@@ -1561,6 +1710,9 @@
}
}
+ // clear the cache for this model and related models
+ $this->_cache->deleteAll();
+
// refresh the table data in the record
if ($spec instanceof Solar_Sql_Model_Record) {
// set the primary column so refresh will work
@@ -1577,7 +1729,7 @@
/**
*
- * Updates rows in the model table.
+ * Updates rows in the model table and deletes cache entries.
*
* @param array|Solar_Sql_Model_Record $spec The row data to insert.
*
@@ -1586,6 +1738,8 @@
*
* @return array The data as updated.
*
+ * @see Solar_Sql_Model_Cache::deleteAll()
+ *
*/
public function update($spec, $where)
{
@@ -1674,6 +1828,9 @@
$this->_sql->update($this->_table_name, $data, $where);
$this->unserializeCols($data);
+ // clear the cache for this model and related models
+ $this->_cache->deleteAll();
+
// refresh the table data in the record
if ($spec instanceof Solar_Sql_Model_Record) {
$spec->refresh();
@@ -1687,13 +1844,15 @@
/**
*
- * Deletes rows from the model table.
+ * Deletes rows from the model table and deletes cache entries.
*
* @param string|array|Solar_Sql_Model_Record $spec The WHERE clause to
* identify which rows to delete, or a record to delete.
*
* @return void
*
+ * @see Solar_Sql_Model_Cache::deleteAll()
+ *
*/
public function delete($spec)
{
@@ -1704,7 +1863,14 @@
$where = $spec;
}
- return $this->_sql->delete($this->_table_name, $where);
+ // perform the deletion
+ $result = $this->_sql->delete($this->_table_name, $where);
+
+ // clear the cache for this model and related models
+ $this->_cache->deleteAll();
+
+ // done
+ return $result;
}
/**
@@ -2299,6 +2465,25 @@
/**
*
+ * Fixes the cache class name.
+ *
+ * @return void
+ *
+ */
+ protected function _fixCache()
+ {
+ // make sure we have a cache class
+ if (empty($this->_cache_class)) {
+ $class = $this->_stack->load('Cache', false);
+ if (! $class) {
+ $class = 'Solar_Sql_Model_Cache';
+ }
+ $this->_cache_class = $class;
+ }
+ }
+
+ /**
+ *
* Creates the table and indexes in the database using $this->_table_cols
* and $this->_index.
*
More information about the Solar-svn
mailing list