⬆️ ⬇️

Variant of working with the cache without access to the backend using the example of Yii2

I am a web developer and not so long ago (working outside the main work) I had to solve a rather non-standard task in our circles: to develop a frontend on Yii2 to the site, the entire backend of which is written in ancient Greek ASP VBScript (sorry, I already forgot how write it: just ASP, or just VBScript?).



Immediately, I’ll make a reservation that the entire project of customers currently consists of ~ 500mb of scripts (the guys are still writing on it, since the year 97).



Of course, not all of this functionality needed a front, which was very pleasing. Two things did not please: Oracle DB (but this is another story) and the impossibility of disabling the cache. But this command does not use the cache in any way whatsoever, and will not do it under any circumstances: either because of the inertia of thinking, or because of the difficulties of VBScript + Memcache, but rather simply because of that 500MB (about which I wrote above).



In general, I had to get such a funny cake: Backend on ASP, Frontend on Yii2.

')

It was instead of intro.



The problem that arose immediately: tk. need to use caching - how to know when and what data has changed in the database? There will be no invalidation from the backend, there will be no removal of the key. Nothing like this shone. Although the project was not very high-loaded, but I wanted to do it somehow flexibly and optimally.



I offer to the court my sandbox, perhaps not the most elegant, but a solution. Maybe someone will face this and this post will help him, as many excellent solutions on this site helped me.



So, what is the main idea: since we do not have information about what has changed in the database, then we will collect it ourselves.



First we create the interface:



<?php /** *         . *       ,   ,    *      ..      . */ interface InvalidateModels { public function getInvalidateTime(); public function getInvalidateField(); } 


Usually I diligently fill in phpDoc - therefore, where it is more or less complete - I will not comment in particular.



Each model that implements this interface tells the future invalidation procedure that it is ready to disable its cache:



 class Airport extends ActiveRecord implements InvalidateModels { /** *     * @return int */ public function getInvalidateTime() { return 60 * 60 * 24; } /** *        * @return string */ public function getInvalidateField() { return 'update_stamp'; } 


Here it is worth noting that if the table does not have a field for which you can catch, then, for example, for Oracle you can use the banal ROWNUM , since (if the DB architects are not quite bad people), the data in such a table will never change, but will be added.



Next thing is small. We write the standard console command Yii2, which we will call with the crown once in a certain time (I had 5 minutes). And we do something like this in it:



 /** *        */ class InvalidateCacheController extends \yii\console\Controller { /** * ,      *       \common\interfaces\InvalidateModels * ,    * * @return array */ private function _getInvalidateModels() { return [ Airport::class, ]; } /** * Action    * 1.           * 2.              *        .       , *       . */ public function actionInvalidateCache() { $models = $this->_getInvalidateModels(); $reflectionObjects = []; foreach ($models as $modelName) { $reflectionObjects[] = new \ReflectionClass($modelName); } /** @var \ReflectionClass $refObject */ foreach ($reflectionObjects as $refObject) { //      InvalidateModels if (!$refObject->implementsInterface('\common\interfaces\InvalidateModels')) { continue; } $modelName = $refObject->getName(); /** @var \common\interfaces\InvalidateModels $model */ $model = new $modelName; $invalidateTime = $model->getInvalidateTime(); $cacheKey = 'LastInvalidateTime-' . $refObject->getName(); $lastInvalidateTime = \Yii::$app->memcache->get($cacheKey); if (false === $lastInvalidateTime) { $this->_invalidateCache($refObject); \Yii::$app->memcache->set($cacheKey, 1, $invalidateTime); } } } /** *    * * @param \ReflectionClass $refObject */ private function _invalidateCache(\ReflectionClass $refObject) { $modelName = $refObject->getName(); /** @var \common\interfaces\InvalidateModels $model */ $model = new $modelName; $invalidateField = $model->getInvalidateField(); /** @var \yii\db\ActiveRecord $model */ $lastChangedTime = $model::find()->max($invalidateField); $lastDataInCache = \Yii::$app->memcache->get('last-set-time.' . $model::tableName()); //                    if ($lastDataInCache < strtotime($lastChangedTime)) { \Yii::$app->memcache->invalidateTags([$model::tableName()]); } } } 


Unfortunately, I didn’t quite understand whether it was possible to go through reflexes in certain folders and extract classes from files, so I made, perhaps, an extra _getInvalidateModels () method in which you need to repeat invalid models. If so, I will be very grateful for the hint.



Conclusion



Of course, using this method, we will have a time lag, and this will never guarantee 100% coincidence of data in the cache and database. But this approach (in projects that are not critical to this) probably has the right to life.



Thank you for attention!



I am very positive about criticism.

Source: https://habr.com/ru/post/307092/



All Articles