Last active
July 29, 2021 20:42
-
-
Save talal424/5fdaf1e4613e20c23069c095cf506168 to your computer and use it in GitHub Desktop.
Phalcon auto cache model ( with many to many relationships ) for Phalcon 4.1.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 | |
declare(strict_types=1); | |
use Phalcon\Di; | |
use Phalcon\Mvc\Model; | |
use Phalcon\Mvc\ModelInterface; | |
use Phalcon\Mvc\Model\ResultsetInterface; | |
use Phalcon\Mvc\Model\RelationInterface; | |
/** | |
* Phalcon auto cache model ( with many to many relationships ) for Phalcon 4.1.x | |
* | |
* @version 2.0.1 | |
* @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\Cache service in your services file. | |
* | |
* >> You must make a seperate service for the adapter since you can't get cache keys from Phalcon\Cache | |
* | |
* <code> | |
* use Phalcon\Cache; | |
* use Phalcon\Cache\AdapterFactory; | |
* use Phalcon\Di\FactoryDefault; | |
* use Phalcon\Storage\SerializerFactory; | |
* | |
* $di->set( | |
* 'cacheAdapter', | |
* function () { | |
* $config = $this->getConfig(); | |
* $serializerFactory = new SerializerFactory(); | |
* $adapterFactory = new AdapterFactory($serializerFactory); | |
* | |
* $options = [ | |
* 'defaultSerializer' => 'php', | |
* 'lifetime' => 7200, | |
* 'storageDir' => $config->application->cacheDir, | |
* 'prefix' => 'some_prefix' | |
* ]; | |
* | |
* $adapter = $adapterFactory->newInstance('stream', $options); | |
* | |
* return $adapter; | |
* } | |
* ); | |
* | |
* $di->set( | |
* 'modelsCache', | |
* function () { | |
* $adapter = $this->getCacheAdapter(); | |
* return new Cache($adapter); | |
* } | |
* ); | |
* </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 `$prefix` to your App name or call `Cache::setPrefix('SomeApp')` | |
* | |
* all queries goes throw Phalcon\Model should be cached automatically | |
* | |
* # PHQL queries: | |
* | |
* <code> | |
* // 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 = RobotsParts::getPHQL($phql, ['Parts'], ['robots_id' => '1']); | |
* </code> | |
* | |
* include used models in PHQL query (as the second paramater) to ensure the clearance of the cache keys when `Cache::clearCache(true)` is called | |
* | |
* if you call `Cache::clearCache()` it remove cache made using PHQL queries if the models' names used in key | |
* | |
* call `Cache::clearAllCache()` to clear all cache keys | |
* | |
* # Notice: | |
* if you override afterSave/afterDelete methods in models don't forget to call `parent::afterSave`/`parent::afterDelete` | |
* | |
* # Tested on: | |
* * Php 7.4.11 | |
* * Phalcon 4.1.0 | |
*/ | |
abstract class Cache extends Model | |
{ | |
/** | |
*the status of cache model. | |
* | |
* @var bool | |
*/ | |
protected static $enableCache = true; | |
/** | |
* a prefix that will be prepended to every key created. | |
* | |
* @var string | |
*/ | |
protected static $prefix = 'SomeApp'; | |
/** | |
* 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(bool $toggle): void | |
{ | |
self::$enableCache = $toggle; | |
} | |
/** | |
* Returns the status of the cache model. | |
* | |
* @return bool | |
*/ | |
public static function cacheEnabled(): bool | |
{ | |
return self::$enableCache; | |
} | |
/** | |
* Sets the master key. | |
* | |
* @param string $key | |
* @return void | |
*/ | |
public static function setPrefix(string $prefix): void | |
{ | |
self::$prefix = $prefix; | |
} | |
/** | |
* enables/disables using md5. | |
* | |
* @param bool $toggle | |
* @return void | |
*/ | |
public static function enableMd5(bool $toggle): void | |
{ | |
self::$useMd5 = $toggle; | |
} | |
/** | |
* Returns unique cache key for provided paramaters and key. | |
* | |
* @param array $parameters | |
* @param string $key | |
* @return string | |
*/ | |
protected static function generateCacheKey(array $parameters, string $key): string | |
{ | |
$model = get_called_class(); | |
$prefix = self::$prefix; | |
if (isset($parameters['di'])) { | |
unset($parameters['di']); | |
} | |
$cacheKey = serialize($parameters); | |
if (self::$useMd5) { | |
$cacheKey = md5($cacheKey); | |
} | |
// replace namespace slashes if used | |
return str_replace('\\', '_', "{$prefix}_{$model}_{$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, string $key) | |
{ | |
if (!self::$enableCache) { | |
return $parameters; | |
} | |
if (!is_array($parameters)) { | |
$parameters = [$parameters]; | |
} | |
if (!isset($parameters['cache'])) { | |
$cacheKey = self::generateCacheKey($parameters,$key); | |
$parameters['cache'] = ['key' => $cacheKey]; | |
} | |
return $parameters; | |
} | |
/** | |
* public alias for self::_createKey to be used in raw sql queries. | |
* | |
* @param array $parameters | |
* @param string $key | |
* @return string | |
*/ | |
public static function getCacheKey(array $parameters, string $key): string | |
{ | |
return self::generateCacheKey($parameters, $key); | |
} | |
/** | |
* Creates unique cache key for provided paramaters and checks if availabe. | |
* | |
* @param mixed $parameters | |
* @return mixed | |
*/ | |
public static function find($parameters = null): ResultsetInterface | |
{ | |
$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): ?ModelInterface | |
{ | |
$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 relations | |
* Returns related records based on defined relations | |
* | |
* @param string $alias | |
* @param mixed $arguments | |
* @return \Phalcon\Mvc\Model\Resultset\Simple|Phalcon\Mvc\Model\Resultset\Simple|false | |
*/ | |
public function getRelated(string $alias, $arguments = null) | |
{ | |
$className = get_class($this); | |
$manager = $this->modelsManager; | |
$lowerAlias = strtolower($alias); | |
/** | |
* Query the relation by alias | |
*/ | |
$relation = $manager->getRelationByAlias($className, $lowerAlias); | |
if (!is_object($relation)) { | |
throw new Exception("There is no defined relations for the model '" . $className . "' using alias '" . $alias . "'"); | |
} | |
/** | |
* If there are any arguments, Manager with handle the caching of the records | |
*/ | |
if (is_null($arguments)) { | |
/** | |
* If the related records are already in cache and the relation is reusable, | |
* we return the cached records. | |
*/ | |
if ($relation->isReusable() && $this->isRelationshipLoaded($lowerAlias)) { | |
$result = $this->related[$lowerAlias]; | |
} else { | |
/** | |
* get cached records or Call the 'getRelationRecords' in the models manager. | |
*/ | |
$result = $this->_getRelationRecords($alias, $relation, $arguments); | |
/** | |
* We store relationship objects in the related cache if there were no arguments. | |
*/ | |
$this->related[$lowerAlias] = $result; | |
} | |
} else { | |
/** | |
* get cached records or | |
* Individually queried related records are handled by Manager. | |
* The Manager also checks and stores reusable records. | |
*/ | |
$result = $this->_getRelationRecords($alias, $relation, $arguments); | |
} | |
return $result; | |
} | |
/** | |
* Helper method to query records based on a relation definition | |
* | |
* @return \Phalcon\Mvc\Model\Resultset\Simple|Phalcon\Mvc\Model\Resultset\Simple|int|false | |
*/ | |
final protected function _getRelationRecords(string $alias, RelationInterface $relation, $parameters = null) | |
{ | |
$manager = $this->modelsManager; | |
if (self::$enableCache && $relation->getType() > 2) { | |
$extraParameters = $relation->getParams(); | |
// merge provided paramaters and ones setup in relation | |
$keyCaption = $this->_mergeFindParameters($extraParameters, $parameters); | |
// 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); | |
} | |
$cacheKey = self::generateCacheKey($keyCaption, strtolower($alias)); | |
$cacheService = $this->di->get('modelsCache'); | |
if ($cacheService->has($cacheKey)) { | |
return $cacheService->get($cacheKey); | |
} else { | |
$result = $manager->getRelationRecords($relation, $this, $parameters); | |
$cacheService->set($cacheKey, $result); | |
return $result; | |
} | |
} | |
return $manager->getRelationRecords($relation, $this, $parameters); | |
} | |
/** | |
* Merge two arrays of find parameters | |
*/ | |
final protected function _mergeFindParameters($findParamsOne, $findParamsTwo): array | |
{ | |
$findParams = []; | |
if (is_string($findParamsOne)) { | |
$findParamsOne = ['conditions' => $findParamsOne]; | |
} | |
if (is_string($findParamsTwo)) { | |
$findParamsTwo = ['conditions' => $findParamsTwo]; | |
} | |
if (is_array($findParamsOne)) { | |
foreach ($findParamsOne as $key => $value) { | |
if ($key === 0 || $key === 'conditions') { | |
if (!isset($findParams[0])) { | |
$findParams[0] = $value; | |
} else { | |
$findParams[0] = "(" . $findParams[0] . ") AND (" . $value . ")"; | |
} | |
} else { | |
$findParams[$key] = $value; | |
} | |
} | |
} | |
if (is_array($findParamsTwo)) { | |
foreach ($findParamsTwo as $key => $value) { | |
if ($key === 0 || $key === 'conditions') { | |
if (!isset($findParams[0])) { | |
$findParams[0] = $value; | |
} else { | |
$findParams[0] = "(" . $findParams[0] . ") AND (" . $value . ")"; | |
} | |
} else if ($key === "bind" || $key === "bindTypes") { | |
if (is_array($value)) { | |
if (!isset($findParams[$key])) { | |
$findParams[$key] = $value; | |
} else { | |
$findParams[$key] = array_merge($findParams[$key], $value); | |
} | |
} | |
} else { | |
$findParams[$key] = $value; | |
} | |
} | |
} | |
return $findParams; | |
} | |
/** | |
* Queries the PHQL string with provided params | |
* include all model class names to ensure clearance when Cache::clearCache(true) is called | |
* | |
* @param string $phql PHQL query | |
* @param array $models models included in query | |
* @return Phalcon\Mvc\Model\ResultsetInterface|Phalcon\Mvc\Model\Query\StatusInterface | |
*/ | |
public static function getPHQL(string $phql, array $models = [], array $params = []) | |
{ | |
$di = Di::getDefault(); | |
if (static::$enableCache) { | |
$keys = implode('_', $models); | |
$modelsCache = $di->get('modelsCache'); | |
$cacheKey = static::getCacheKey($params, $keys); | |
if ($modelsCache->has($cacheKey)) { | |
return $modelsCache->get($cacheKey); | |
} | |
} | |
$modelsManager = $di->getModelsManager(); | |
$results = $modelsManager->executeQuery($phql,$params); | |
if (static::$enableCache) { | |
$modelsCache->set($cacheKey, $results); | |
} | |
return $results; | |
} | |
/** | |
* Flushes/clears the cache | |
* | |
* @return void | |
*/ | |
public static function clearAllCache(): void | |
{ | |
if (!self::$enableCache) { | |
return; | |
} | |
$di = Di::getDefault(); | |
$cacheAdapter = $di->get('cacheAdapter'); | |
$prefix = self::$prefix; | |
$keys = $cacheAdapter->getKeys($prefix); | |
foreach ($keys as $key) { | |
$cacheAdapter->delete($key); | |
} | |
} | |
/** | |
* Deletes all cache for model with the option to delete other related models | |
* | |
* @param bool $resetRelated delete all related models | |
* @return void | |
*/ | |
public function clearCache(bool $resetRelated = false): void | |
{ | |
if (!self::$enableCache) { | |
return; | |
} | |
$modelName = get_called_class(); | |
$prefix = self::$prefix; | |
$models = [$modelName]; | |
// get all related models when its set to true | |
if ($resetRelated) { | |
$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); | |
$cacheAdapter = $this->getDi()->get('cacheAdapter'); | |
$keys = $cacheAdapter->getKeys($prefix); | |
foreach ($keys as $key) { | |
foreach ($models as $model) { | |
if (strpos($key, $model) !== false) { | |
$cacheAdapter->delete($key); | |
break; | |
} | |
} | |
} | |
} | |
/** | |
* Deletes all cache for model after Insert/Update queries | |
* | |
* @return void | |
*/ | |
public function afterSave(): void | |
{ | |
$this->clearCache(); | |
} | |
/** | |
* Deletes all cache for model after Delete queries | |
* | |
* @return void | |
*/ | |
public function afterDelete(): void | |
{ | |
$this->clearCache(); | |
} | |
} |
redis storage not work
what is the issue?
i only tested on Phalcon\Cache\Adapter\Stream
if you have a solution can you post it here
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
redis storage not work