From 898bb67bed846aed64164cbe93d556f1a07b5337 Mon Sep 17 00:00:00 2001 From: Lucian DRAGOMIR Date: Mon, 19 Feb 2018 00:26:10 +0200 Subject: [PATCH 1/4] - replaced $eagerLoad = $this->eagerLoad ?? []; $model = $this->model ?? $this; $query = $this->query ?? app(Builder::class); with $eagerLoad = $this->retrieveEagerLoad(); $model = $this->retrieveCacheModel(); $query = $this->retrieveCacheQuery(); to avoid name collisions in case the model contains one of the 'eagerLoad', 'mode' or 'query' attributes - added makeCachePrefix and used it to prefix cache tags and keyDifferentiator and class name tag with the value returned by the 'getCachePrefix' method of the model This method can be used in a multitenant implementation where an object with the same key and tags is cached for each tenant database connection. - added CacheGlobal class to be a placeholder for the global static cache disable flag. The cached value using $cache might generate unpredictable behaviour in case multiple requests are made to the server in the same time. --- src/Traits/Cachable.php | 89 +++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 12 deletions(-) diff --git a/src/Traits/Cachable.php b/src/Traits/Cachable.php index 6ca1146..7bec4df 100644 --- a/src/Traits/Cachable.php +++ b/src/Traits/Cachable.php @@ -7,11 +7,13 @@ use Illuminate\Database\Query\Builder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use GeneaLabs\LaravelModelCaching\CachedBuilder; +use Illuminate\Database\Eloquent\Model; + +use GeneaLabs\LaravelModelCaching\CacheGlobal; trait Cachable { protected $isCachable = true; - protected static $isCachableKey = 'genealabs:model-caching:is-disabled'; protected function cache(array $tags = []) { @@ -22,8 +24,8 @@ protected function cache(array $tags = []) } if (is_subclass_of($cache->getStore(), TaggableStore::class)) { - if (is_a($this, CachedModel::class)) { - array_push($tags, str_slug(get_called_class())); + if (is_a($this, Model::class)) { + array_push($tags, $this->makeCachePrefix(str_slug(get_called_class()))); } $cache = $cache->tags($tags); @@ -34,7 +36,8 @@ protected function cache(array $tags = []) public function disableCache() { - cache()->forever(self::$isCachableKey, true); + + CacheGlobal::disableCache(); $this->isCachable = false; @@ -50,24 +53,86 @@ public function flushCache(array $tags = []) $this->cache($tags)->flush(); } + protected function retrieveEagerLoad() + { + if (is_a($this, Model::class)) { + return []; + } + if (is_a($this, EloquentBuilder::class)) { + return $this->eagerLoad ?? []; + } + return null; + } + + protected function retrieveCacheModel() + { + if (is_a($this, Model::class)) { + return $this; + } + if (is_a($this, EloquentBuilder::class)) { + return $this->model; + } + return null; + } + + protected function retrieveCacheQuery() + { + if (is_a($this, Model::class)) { + return app(Builder::class); + } + if (is_a($this, EloquentBuilder::class)) { + return $this->query; + } + return null; + } + + protected function makeCachePrefix($elementMix) + { + $model = $this->retrieveCacheModel(); + if (!method_exists($model, "getCachePrefix")) { + return $elementMix; + } + + $result = null; + $cachePrefix = $model->getCachePrefix(); + if ($cachePrefix == null) { + return $elementMix; + } + if (is_array($elementMix)) { + $result = []; + foreach ($elementMix as $value) { + array_push($result, $cachePrefix . '-' . $value); + } + return $result; + } else { + $result = $cachePrefix . '-' . $elementMix; + } + return $result; + } + protected function makeCacheKey( array $columns = ['*'], $idColumn = null, string $keyDifferentiator = '' ) : string { - $eagerLoad = $this->eagerLoad ?? []; - $model = $this->model ?? $this; - $query = $this->query ?? app(Builder::class); + $eagerLoad = $this->retrieveEagerLoad(); + $model = $this->retrieveCacheModel(); + $query = $this->retrieveCacheQuery(); return (new CacheKey($eagerLoad, $model, $query)) - ->make($columns, $idColumn, $keyDifferentiator); + ->make($columns, $idColumn, $this->makeCachePrefix($keyDifferentiator)); } protected function makeCacheTags() : array { - $tags = (new CacheTags($this->eagerLoad ?? [], $this->model ?? $this)) + $eagerLoad = $this->retrieveEagerLoad(); + $model = $this->retrieveCacheModel(); + + $tags = (new CacheTags($eagerLoad, $model)) ->make(); + $tags = $this->makeCachePrefix($tags); + return $tags; } @@ -80,7 +145,7 @@ public static function bootCachable() public static function all($columns = ['*']) { - if (cache()->get(self::$isCachableKey)) { + if (CacheGlobal::isDisabled()) { return parent::all($columns); } @@ -97,8 +162,8 @@ public static function all($columns = ['*']) public function newEloquentBuilder($query) { - if (cache()->get(self::$isCachableKey)) { - cache()->forget(self::$isCachableKey); + if (CacheGlobal::isDisabled()) { + CacheGlobal::enableCache(); return new EloquentBuilder($query); } From ab87f49f1edfa609442e5f1541c40f24844e1ad8 Mon Sep 17 00:00:00 2001 From: Lucian DRAGOMIR Date: Mon, 19 Feb 2018 00:29:36 +0200 Subject: [PATCH 2/4] - update README.md --- README.md | 12 ++++++++++++ src/CacheGlobal.php | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/CacheGlobal.php diff --git a/README.md b/README.md index 8760a25..d53087e 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,18 @@ extends `Illuminate\Foundation\Auth\User`. Overriding that would break functiona Not only that, but it probably isn't a good idea to cache the user model anyway, since you always want to pull the most up-to-date info on it. +### Multitenant support for cached models +If you need multitenancy support the same model context (key and tags) needs to be cached for each denant with it's specific values. This requires a separations of cache that is supported by implementing the getCachePrefix method in the model class. + +I would recommend to implement in your application a TenantCachable trait containing the getCachePrefix method that for example returns a unique value corresponding to each tenant. +An example in the context of using the hyn/multi-tenant package can be: +```php +public function getCachePrefix() +{ + return $this->getConnectionName() . '-' . $this->getConnection()->getDatabaseName(); +} +``` + ### Optional Disabling Caching of Queries **Recommendation: add this to all your seeder queries to avoid pulling in cached information when reseeding multiple times.** diff --git a/src/CacheGlobal.php b/src/CacheGlobal.php new file mode 100644 index 0000000..a1d69cf --- /dev/null +++ b/src/CacheGlobal.php @@ -0,0 +1,26 @@ + Date: Mon, 19 Feb 2018 00:29:36 +0200 Subject: [PATCH 3/4] - update README.md --- README.md | 13 +++++++++++++ src/CacheGlobal.php | 26 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/CacheGlobal.php diff --git a/README.md b/README.md index 8760a25..87adec5 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ relationships. This package is an attempt to address those requirements. - automatic, self-invalidating model query caching. - automatic use of cache tags for cache providers that support them (will flush entire cache for providers that don't). +- support for multitenant implementations by implementing getCachePrefix method in the Model class ## Requirements - PHP >= 7.1.3 @@ -74,6 +75,18 @@ extends `Illuminate\Foundation\Auth\User`. Overriding that would break functiona Not only that, but it probably isn't a good idea to cache the user model anyway, since you always want to pull the most up-to-date info on it. +### Multitenant support for cached models +If you need multitenancy support the same model context (key and tags) needs to be cached for each denant with it's specific values. This requires a separations of cache that is supported by implementing the getCachePrefix method in the model class. + +I would recommend to implement in your application a TenantCachable trait containing the getCachePrefix method that for example returns a unique value corresponding to each tenant. +An example in the context of using the hyn/multi-tenant package can be: +```php +public function getCachePrefix() +{ + return $this->getConnectionName() . '-' . $this->getConnection()->getDatabaseName(); +} +``` + ### Optional Disabling Caching of Queries **Recommendation: add this to all your seeder queries to avoid pulling in cached information when reseeding multiple times.** diff --git a/src/CacheGlobal.php b/src/CacheGlobal.php new file mode 100644 index 0000000..a1d69cf --- /dev/null +++ b/src/CacheGlobal.php @@ -0,0 +1,26 @@ + Date: Mon, 19 Feb 2018 01:25:42 +0200 Subject: [PATCH 4/4] - fixed all method by using makeCachePrefix on determining tags --- src/Traits/Cachable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/Cachable.php b/src/Traits/Cachable.php index 7bec4df..ae8b641 100644 --- a/src/Traits/Cachable.php +++ b/src/Traits/Cachable.php @@ -151,7 +151,7 @@ public static function all($columns = ['*']) $class = get_called_class(); $instance = new $class; - $tags = [str_slug(get_called_class())]; + $tags = $this->makeCachePrefix([str_slug(get_called_class())]); $key = $instance->makeCacheKey(); return $instance->cache($tags)