Skip to content

Add multitenant support to allow keeping tenant specific data separated for the cached models. #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 multitenant support the same model context (key and tags) needs to be cached for each tenant 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 for your models 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.**
Expand Down
26 changes: 26 additions & 0 deletions src/CacheGlobal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php namespace GeneaLabs\LaravelModelCaching;

class CacheGlobal
{
protected static $enabled = true;

public static function disableCache()
{
static::$enabled = false;
}

public static function enableCache()
{
static::$enabled = true;
}

public static function isDisabled()
{
return !static::$enabled;
}

public static function isEnabled()
{
return static::$enabled;
}
}
91 changes: 78 additions & 13 deletions src/Traits/Cachable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [])
{
Expand All @@ -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);
Expand All @@ -34,7 +36,8 @@ protected function cache(array $tags = [])

public function disableCache()
{
cache()->forever(self::$isCachableKey, true);

CacheGlobal::disableCache();

$this->isCachable = false;

Expand All @@ -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;
}

Expand All @@ -80,13 +145,13 @@ public static function bootCachable()

public static function all($columns = ['*'])
{
if (cache()->get(self::$isCachableKey)) {
if (CacheGlobal::isDisabled()) {
return parent::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)
Expand All @@ -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);
}
Expand Down