Hi all,
I've written a caching wrapper for find()
and findFirst()
. However, strangely and in very unique circumstances, when caching is enabled it causes an infinite loop that triggers tons of queries and results in a seg fault. Disabling caching makes the problem go away.
I can't see anything in my code that should cause this, so I'm hoping someone else can:
Relevant code:
class Base extends \Phalcon\Mvc\Model{
/** @var array A cache of queries so they don't have to be run multiple times
* @see find()
*/
private static $_find_query_cache = [];
private static $_findFirst_query_cache = [];
/** @var caching Whether or not find() caches by default. May be useful if doing a bunch of updates
on records you know will be requeried. Note that this applies to ALL models, not just a specific
model.
Change with doCache();
*/
private static $caching = TRUE;
public static $queryCount = 0;
public static function doCache($cache){
self::$caching = $cache;
}
public static function getCacheKeys($mode = 'all'){
return ($mode = 'all') ? array_keys(self::$_find_query_cache) : array_keys(self::$_findFirst_query_cache);
}
/**
* Extending default find() to add a caching option. Caching option can be overridden with
* cache directives (below) or by setting the class static "caching" with doCache()
*
* @param array $params Same params as default find() except it also also accepts cache directive keys
* that take effect regardless of their value (ie: fresh=>FALSE will still apply)
* * fresh - will force a re-query. New result will be cached.
* * ninja - will force a re-query. New result will not be cached.
*
* @return mixed Whatever default find() returns. May return NULL if my if conditions are broken
*/
public static function find($params = NULL){
return self::findBody($params,'all');
}
/**
* Just like find() above, but for findFirst()
*/
public static function findFirst($params = NULL){
return self::findBody($params,'one');
}
/**
* Does the caching work for find() and findFirst()
* @see self::find()
*/
public static function findBody($params,$mode = 'all'){
self::$queryCount++;
// the DI gets passed as a parameter sometimes, but it's not serializable
// so it needs to be removed
$serializable_params = (!is_array($params))
? $params
: array_filter($params,function($key){
return $key != 'di';
}, \ARRAY_FILTER_USE_KEY);
$key = static::class.serialize($serializable_params);
$result = NULL;
// Can't use assignment by reference and the ternary operator - PHP gets messed up.
$cache =& self::$_find_query_cache;
if($mode == 'one'){
$cache =& self::$_findFirst_query_cache;
}
// Return a cached copy if possible
if(!isset($params['fresh']) && !isset($params['ninja']) && self::$caching && isset($cache[$key])){
return $cache[$key];
}
else{
$result = ($mode == 'all') ? parent::find($params) : parent::findFirst($params);
}
// Store the result if possible
if(self::$caching && !isset($params['ninja'])){
$cache[$key] = $result;
}
return $result;
}
}
As an added strangeness, the queries that get repeatedly run are a SELECT COUNT(*) AS rowcount ...
query, and 2 UPDATE queries - which don't involve the cache at all.