Last active
March 2, 2021 11:13
-
-
Save talal424/4c0e661f9c41359b354c54a0b5957103 to your computer and use it in GitHub Desktop.
Phalcon auto cache model ( with many to many relationships ) for Phalcon 3.4.x
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Phalcon auto cache model ( with many to many relationships ) for Phalcon 3.4.x | |
* | |
* @version 1.0.0 | |
* @author Talal Alenizi <talal.alenizi@gmail.com> <@talal_alenizi> | |
* @license BSD License (3-clause) | |
* @link https://gist.github.com/talal424/ | |
* @link https://github.com/talal424/ | |
* | |
* | |
* Usage: | |
* Adjust the Phalcon service in your services file | |
* <code> | |
* $di->setShared('modelsCache', function () { | |
* $config = $this->getConfig(); | |
* $frontCache = new FrontData( | |
* [ | |
* "lifetime" => 172800, | |
* ] | |
* ); | |
* return new BackFile( | |
* $frontCache, | |
* [ | |
* "cacheDir" => $config->application->cacheDir, | |
* ] | |
* ); | |
* }); | |
* </code> | |
* | |
* Extend this class in all your models; | |
* <code> | |
* class Parts extends Cache | |
* {} | |
* </code> | |
* | |
* | |
* set $enableCache to true to enable cache or call Cache::enableCache(true) | |
* | |
* Change $cacheServiceName to your Phalcon Cache Service | |
* or call Cache::setCacheServiceName('modelsCache') | |
* | |
* Change $masterKey to your App name or call Cache::setMasterKey('SomeApp') | |
* | |
* all queries goes throw Phalcon\Model should be cached automatically | |
* | |
* PHQL queries: | |
* | |
* <code> | |
* // get model cache service | |
* $modelsCache = $this->modelsCache; | |
* | |
* // create unique cache key based on paramaters for this query with key name (RobotsParts_with_Parts) | |
* // this would help to this remove cache later on when looking for model name in keys | |
* $cacheKey = RobotsParts::getCacheKey(['robots_id'=>'1'],'RobotsParts_Parts'); | |
* | |
* // check if this paramaters and key name used before with this query if cache is enabled | |
* if (Cache::cacheEnabled() && $modelsCache->exists($cacheKey)) { | |
* return $modelsCache->get($cacheKey); | |
* } | |
* | |
* // get Phalcon\Di | |
* $di = \Phalcon\Di::getDefault(); | |
* | |
* // get model manager | |
* $modelsManager = $di->getModelsManager(); | |
* | |
* // set phql query | |
* $phql = "SELECT RobotsParts.*, Parts.* FROM RobotsParts | |
* LEFT JOIN Parts ON RobotsParts.parts_id = Parts.id | |
* WHERE RobotsParts.robots_id = :robots_id:"; | |
* | |
* // excute query and bind paramaters | |
* $results = $modelsManager->executeQuery($phql,['robots_id'=>'1']); | |
* | |
* // cache results if enabled | |
* if (Cache::cacheEnabled()) { | |
* $modelsCache->save($cacheKey,$results,172800); | |
* } | |
* </code> | |
* | |
* if you call `Cache::resetCache()` it remove cache made using PHQL queries if the models' names used in key | |
* | |
* if you override afterSave/afterDelete don't forget to call parent::afterSave/afterDelete | |
* or call $this->resetCache() to reset cache | |
*/ | |
use \Phalcon\Text; | |
abstract class Cache extends \Phalcon\Mvc\Model | |
{ | |
/** | |
* the status of cache model. | |
* | |
* @var bool | |
*/ | |
protected static $enableCache = true; | |
/** | |
* a string that will be prepended to every key created. | |
* | |
* @var string | |
*/ | |
protected static $masterKey = 'SomeApp'; | |
/** | |
* your Phalcon Cache service name. | |
* | |
* @var string | |
*/ | |
protected static $cacheServiceName = 'modelsCache'; | |
/** | |
* whether encrypting paramaters or not ( which included in cache key ) | |
* recommended when under windows because of the file name restrictions | |
* if you use file cache. | |
* | |
* @var bool | |
*/ | |
protected static $useMd5 = true; | |
/** | |
* enables/disables cache model. | |
* | |
* @param bool $toggle | |
* @return void | |
*/ | |
public static function enableCache($toggle = null) | |
{ | |
if (is_bool($toggle)) { | |
self::$enableCache = $toggle; | |
} | |
} | |
/** | |
* Returns the status of the cache model. | |
* | |
* @return bool | |
*/ | |
public static function cacheEnabled() | |
{ | |
return self::$enableCache; | |
} | |
/** | |
* Sets the master key. | |
* | |
* @param string $key | |
* @return void | |
*/ | |
public static function setMasterKey($key = null) | |
{ | |
self::$masterKey = $key; | |
} | |
/** | |
* Sets Phalcon Cache service name. | |
* | |
* @param string $serviceName | |
* @return void | |
*/ | |
public static function setCacheServiceName($serviceName = null) | |
{ | |
self::$cacheServiceName = $serviceName; | |
} | |
/** | |
* enables/disables using md5. | |
* | |
* @param bool $toggle | |
* @return void | |
*/ | |
public static function enableMd5($toggle = null) | |
{ | |
if (is_bool($toggle)) { | |
self::$useMd5 = $toggle; | |
} | |
} | |
/** | |
* Returns unique cache key for provided paramaters and key. | |
* | |
* @param mixed $parameters | |
* @param string $key | |
* @return string | |
*/ | |
protected static function _createKey($parameters,$key) | |
{ | |
$modelName = get_called_class(); | |
$masterKey = self::$masterKey; | |
if (isset($parameters['di'])) { | |
unset($parameters['di']); | |
} | |
$cacheKey = serialize($parameters); | |
if (self::$useMd5) { | |
$cacheKey = md5($cacheKey); | |
} | |
// replace namespace slashes if used | |
return str_replace('\\', '_', "{$masterKey}_{$modelName}_{$key}({$cacheKey})"); | |
} | |
/** | |
* Creates unique cache key and inserts it into the paramaters. | |
* | |
* @param mixed $parameters | |
* @param string $key | |
* @return array | |
*/ | |
protected static function createKey($parameters,$key) | |
{ | |
if (!self::$enableCache) { | |
return $parameters; | |
} | |
if (!is_array($parameters)) { | |
$parameters = [$parameters]; | |
} | |
if (!isset($parameters["cache"])) { | |
$cacheKey = self::_createKey($parameters,$key); | |
$parameters["cache"] = ["key" => $cacheKey]; | |
} | |
return $parameters; | |
} | |
/** | |
* public alias for self::_createKey to be used in raw sql queries. | |
* | |
* @param mixed $parameters | |
* @param string $key | |
* @return void | |
*/ | |
public static function getCacheKey($parameters,$key) | |
{ | |
return self::_createKey($parameters,$key); | |
} | |
/** | |
* Creates unique cache key for provided paramaters and checks if availabe. | |
* | |
* @param mixed $parameters | |
* @return mixed | |
*/ | |
public static function find($parameters = null) | |
{ | |
$parameters = self::createKey($parameters,'find'); | |
return parent::find($parameters); | |
} | |
/** | |
* Creates unique cache key for provided paramaters and checks if availabe. | |
* | |
* @param mixed $parameters | |
* @return mixed | |
*/ | |
public static function findFirst($parameters = null) | |
{ | |
$parameters = self::createKey($parameters,'findFirst'); | |
return parent::findFirst($parameters); | |
} | |
/** | |
* Creates unique cache key for provided paramaters and checks if availabe. | |
* | |
* @param mixed $parameters | |
* @return mixed | |
*/ | |
public static function count($parameters = null) | |
{ | |
$parameters = self::createKey($parameters,'count'); | |
return parent::count($parameters); | |
} | |
/** | |
* Creates unique cache key for provided paramaters and checks if availabe. | |
* | |
* @param mixed $parameters | |
* @return mixed | |
*/ | |
public static function sum($parameters = null) | |
{ | |
$parameters = self::createKey($parameters,'sum'); | |
return parent::sum($parameters); | |
} | |
/** | |
* Creates unique cache key for provided paramaters and checks if availabe. | |
* | |
* @param mixed $parameters | |
* @return mixed | |
*/ | |
public static function maximum($parameters = null) | |
{ | |
$parameters = self::createKey($parameters,'maximum'); | |
return parent::maximum($parameters); | |
} | |
/** | |
* Creates unique cache key for provided paramaters and checks if availabe. | |
* | |
* @param mixed $parameters | |
* @return mixed | |
*/ | |
public static function minimum($parameters = null) | |
{ | |
$parameters = self::createKey($parameters,'minimum'); | |
return parent::minimum($parameters); | |
} | |
/** | |
* Creates unique cache key for provided paramaters and checks if availabe. | |
* | |
* @param mixed $parameters | |
* @return mixed | |
*/ | |
public static function average($parameters = null) | |
{ | |
$parameters = self::createKey($parameters,'average'); | |
return parent::average($parameters); | |
} | |
/** | |
* Overrides parent method to cache when getter is used ($robot->getRobotsParts()). | |
* Returns related records defined relations depending on the method name | |
* | |
* @param string modelName | |
* @param string method | |
* @param array arguments | |
* @return mixed | |
*/ | |
protected function _getRelatedRecords($modelName, $method, $arguments) | |
{ | |
$manager = $this->_modelsManager; | |
$relation = false; | |
$queryMethod = null; | |
// Calling find/findFirst if the method starts with "get" | |
if (Text::startsWith($method, "get")) { | |
$alias = substr($method, 3); | |
$relation = $manager->getRelationByAlias($modelName, $alias); | |
} | |
// Calling count if the method starts with "count" | |
elseif (Text::startsWith($method, "count")) { | |
$alias = substr($method, 5); | |
$queryMethod = "count"; | |
$relation = $manager->getRelationByAlias($modelName, $alias); | |
} | |
// If the relation was found perform the query via the models manager | |
if (!is_object($relation)) { | |
return null; | |
} | |
$extraArgs = isset($arguments[0]) ? $arguments[0] : null; | |
// check if cache is enabled and the relation type is many to many | |
// if its belongs to or has many it will invoke findFirst/find so no need for caching it twice | |
if (self::$enableCache && $relation->getType() > 2 && $this->di->has(self::$cacheServiceName)) { | |
if (!is_array($arguments)) { | |
$arguments = [$arguments]; | |
} | |
$extraParameters = $relation->getParams(); | |
if (!is_array($extraParameters)) { | |
$extraParameters = [$extraParameters]; | |
} | |
// making sure that if the conditions are the same | |
// the key would match everytime using getters or properties | |
if (empty($arguments)) { | |
$arguments = [null]; | |
} | |
$extraParameters['method'] = $queryMethod ?: 'get'; | |
$keyCaption = array_merge($arguments, $extraParameters); | |
//getting fields used in relationship | |
$fields = $relation->getFields(); | |
if (!is_array($fields)) { | |
$fields = [$fields]; | |
} | |
//making a unique key based on field's value | |
foreach ($fields as $key => $value) { | |
$keyCaption['APR' . $key] = $this->readAttribute($value); | |
} | |
//create key | |
$cacheKey = self::_createKey($keyCaption,strtolower($alias)); | |
$cacheService = $this->di->get(self::$cacheServiceName); | |
if ($cacheService->exists($cacheKey)) { | |
return $cacheService->get($cacheKey); | |
} else { | |
$result = $manager->getRelationRecords($relation,$queryMethod,$this,$extraArgs); | |
$cacheService->save($cacheKey, $result); | |
return $result; | |
} | |
} | |
return $manager->getRelationRecords($relation,$queryMethod,$this,$extraArgs); | |
} | |
/** | |
* Overrides parent method to cache when getter is used ($robot->getRelated('robotsParts')). | |
* Returns related records based on defined relations | |
* | |
* @param string alias | |
* @param array arguments | |
* @return \Phalcon\Mvc\Model\ResultsetInterface | |
*/ | |
public function getRelated($alias, $arguments = null) | |
{ | |
// Query the relation by alias | |
$className = get_class($this); | |
$manager = $this->_modelsManager; | |
$relation = $manager->getRelationByAlias($className, $alias); | |
if (!is_object($relation)) { | |
throw new \Exception("There is no defined relations for the model '" . $className . "' using alias '" . $alias . "'"); | |
} | |
// check if cache is enabled and the relation type is many to many | |
// if its belongs to or has many it will invoke findFirst/find so no need for caching it twice | |
if (self::$enableCache && $relation->getType() > 2 && $this->di->has(self::$cacheServiceName)) { | |
if (!is_array($arguments)) { | |
$arguments = [$arguments]; | |
} | |
$extraParameters = $relation->getParams(); | |
if (!is_array($extraParameters)) { | |
$extraParameters = [$extraParameters]; | |
} | |
// making sure that if the conditions are the same | |
// the key would match everytime using getters or properties | |
$extraParameters['method'] = 'get'; | |
$keyCaption = array_merge($arguments, $extraParameters); | |
// getting fields used in relationship | |
$fields = $relation->getFields(); | |
if (!is_array($fields)) { | |
$fields = [$fields]; | |
} | |
// making a unique key based on field's value | |
foreach ($fields as $key => $value) { | |
$keyCaption['APR' . $key] = $this->readAttribute($value); | |
} | |
// create key | |
$cacheKey = self::_createKey($keyCaption,strtolower($alias)); | |
$cacheService = $this->di->get(self::$cacheServiceName); | |
if ($cacheService->exists($cacheKey)) { | |
return $cacheService->get($cacheKey); | |
} else { | |
$result = $manager->getRelationRecords($relation, null, $this, $arguments); | |
$cacheService->save($cacheKey, $result); | |
return $result; | |
} | |
} | |
// Call the 'getRelationRecords' in the models manager | |
return $manager->getRelationRecords($relation, null, $this, $arguments); | |
} | |
/** | |
* Overrides parent method to cache when magic property is used ($robot->robotsParts). | |
* Magic method to get related records using the relation alias as a property | |
* | |
* @param string property | |
* @return \Phalcon\Mvc\Model\Resultset|Phalcon\Mvc\Model | |
*/ | |
public function __get($property) | |
{ | |
$modelName = get_class($this); | |
$manager = $this->getModelsManager(); | |
$lowerProperty = strtolower($property); | |
// Check if the property is a relationship | |
$relation = $manager->getRelationByAlias($modelName, $lowerProperty); | |
if (is_object($relation)) { | |
// Not fetch a relation if it is on CamelCase | |
if (property_exists($this,$lowerProperty) && is_object($this->{$lowerProperty})) { | |
return $this->{$lowerProperty}; | |
} | |
// check if cache is enabled and the relation type is many to many | |
// if its belongs to or has many it will invoke findFirst/find so no need for caching it twice | |
if (self::$enableCache && $relation->getType() > 2 && $this->di->has(self::$cacheServiceName)) { | |
// keep in mind if conditions set in relationship may change | |
// and check if cache has been set | |
$extraParameters = $relation->getParams(); | |
if (!is_array($extraParameters)) { | |
$extraParameters = [$extraParameters]; | |
} | |
// making sure that if the conditions are the same | |
// the key would match everytime using getters or properties | |
$extraParameters['method'] = 'get'; | |
$extraParameters = array_merge([0 => null],$extraParameters); | |
// getting fields used in relationship | |
$fields = $relation->getFields(); | |
if (!is_array($fields)) { | |
$fields = [$fields]; | |
} | |
// making a unique key based on field's value | |
foreach ($fields as $key => $value) { | |
$extraParameters['APR' . $key] = $this->readAttribute($value); | |
} | |
//create key | |
$cacheKey = self::_createKey($extraParameters,$lowerProperty); | |
// Get the related records using cache | |
$cacheService = $this->di->get(self::$cacheServiceName); | |
if ($cacheService->exists($cacheKey)) { | |
$result = $cacheService->get($cacheKey); | |
} else { | |
$result = $manager->getRelationRecords($relation,null,$this,null); | |
$cacheService->save($cacheKey, $result); | |
} | |
} else { | |
// Get the related records without cahe | |
$result = $manager->getRelationRecords($relation,null,$this,null); | |
} | |
// Assign the result to the object | |
if (is_object($result)) { | |
// We assign the result to the instance avoiding future queries | |
$this->{$lowerProperty} = $result; | |
// For belongs-to relations we store the object in the related bag | |
if ($result instanceof \Phalcon\Mvc\ModelInterface) { | |
$this->_related[$lowerProperty] = $result; | |
} | |
} | |
return $result; | |
} | |
// in source code (method_exists) has been used | |
// so i decided to escape the autoload crap | |
return parent::__get($property); | |
} | |
/** | |
* Deletes all cache for model with the option to delete other related models | |
* | |
* @param bool $resetRelated delete all related models | |
* @param null|string|array $otherModels other optional models to delete | |
* @return void | |
*/ | |
public function resetCache($resetRelated = false, $otherModels = null) | |
{ | |
if (!self::$enableCache) { | |
return; | |
} | |
$modelName = get_called_class(); | |
$masterKey = self::$masterKey; | |
$models = [$modelName]; | |
// check for provided other models to remove cache for | |
if (!is_null($otherModels)) { | |
if (!is_array($otherModels)) { | |
$otherModels = [$otherModels]; | |
} | |
$models = array_merge($models,$otherModels); | |
} | |
// get all related models when its set to true | |
if ($resetRelated === true) { | |
$manager = $this->_modelsManager; | |
$relations = array_merge( | |
$manager->getHasMany($this), | |
$manager->getHasOne($this), | |
$manager->getHasManyToMany($this), | |
$manager->getHasOneAndHasMany($this) | |
); | |
foreach ($relations as $relation) { | |
$models[] = $relation->getReferencedModel(); | |
$intermediateModel = $relation->getIntermediateModel(); | |
if ($intermediateModel) { | |
$models[] = $intermediateModel; | |
} | |
} | |
} | |
// remove duplicate models | |
$models = array_unique($models); | |
// replace namespace slashes if used | |
$models = array_map(function($model) { | |
return str_replace('\\', '_', $model); | |
},$models); | |
$modelsCache = $this->getDi()->get(self::$cacheServiceName); | |
$keys = $modelsCache->queryKeys($masterKey); | |
foreach ($keys as $key) { | |
if ($resetRelated === 'ALLCACHE') { | |
$modelsCache->delete($key); | |
continue; | |
} | |
foreach ($models as $model) { | |
if (strpos($key,$model) !== false) { | |
$modelsCache->delete($key); | |
break; | |
} | |
} | |
} | |
} | |
/** | |
* Deletes all cache for model after Insert/Update queries | |
* | |
* @return void | |
*/ | |
public function afterSave() | |
{ | |
$this->resetCache(); | |
} | |
/** | |
* Deletes all cache for model after Delete queries | |
* | |
* @return void | |
*/ | |
public function afterDelete() | |
{ | |
$this->resetCache(); | |
} | |
} |
https://gist.github.com/talal424/5fdaf1e4613e20c23069c095cf506168
i just wrote this and did some tests and it should work just fine
it could be tricky if you use namespaces ( when clearing related models cache )
Perfect!!! Thank you for interest.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://gist.github.com/talal424/5fdaf1e4613e20c23069c095cf506168
i just wrote this and did some tests and it should work just fine
it could be tricky if you use namespaces ( when clearing related models cache )