-
Notifications
You must be signed in to change notification settings - Fork 6
Tenant awrare cache note
Here are my thoughts why the current documentation is not working. At least sometimes. Making it extremely hard to find out the the solution.
I use Laravel 5.8 and hyn/tenancy
5.4.*
Tenant aware cache will not work with
file
ordatabase
driver in general case. At least it will not work if you try to use 'Telescope`. More on this below.
When any package calls cache for the first time in the app lifecycle, the Cache
object is instantiated. And the directory/prefix of the cache is passed to the cache object.
E.g.
./vendor/laravel/framework/src/Illuminate/Cache/FileStore.php
public function __construct(Filesystem $files, $directory)
./vendor/laravel/framework/src/Illuminate/Cache/RedisStore.php
public function __construct(Redis $redis, $prefix = '', $connection = 'default')
./vendor/laravel/framework/src/Illuminate/Cache/DatabaseStore.php
public function __construct(ConnectionInterface $connection, $table, $prefix = '')
For all (or at least many) auto-discovered packages Cache
is instantiated before the application calls our App\Providers\CacheServiceProvider::boot
to get our Cache::extend
rules. So Cache::extend
is ignored and we stay at the cache root for all tenants, we are not able to namespace cache dynamically.
Anything calling Cache::remeber
instantiates the Cache
object as it's configured in config/cache.php
and freezes the prefix/path values inside the Cache
object.
Here we can try to run our App\Providers\CacheServiceProvider::boot
and \Cache::extend()
before anything instantiates a Cache
object. We may try to disable auto-discover for extensions which instantiate Cache
, manually add all needed service providers into config/app.php
.
And this may work until you try to run telescope debug tool, which must be run as early as possible to catch all possible activity. But telescope instantiates Cache
object before anything else happens, before we change the instantiate rules in App\Providers\CacheServiceProvider
.
Fortunately Illuminate\Cache\RedisStore
class (but not Illuminate\Cache\FileStore
or Illuminate\Cache\DatabaseStore
) has setPrefix
methods. So we can redefine the prefix any time, after telescope
has already instantiated the cache object.
So finally to get cache tetant aware we must:
- Switch to
redis
cache driver in.env
CACHE_DRIVER=redis
- Use @hakanersu approach, create own
CacheServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class CacheServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
if (PHP_SAPI === 'cli') {
$namespace = str_slug(env('APP_NAME', 'laravel'), '_').'_cache';
} else {
$fqdn = request()->getHost();
$namespace = \DB::table('hostnames')
->select('websites.uuid')
->join('websites', 'hostnames.website_id', '=', 'websites.id')
->where('fqdn', $fqdn)
->value('uuid');
}
\Cache::setPrefix($namespace);
}
}
- Register the
CacheServiceProvider
inconfig/app.php
providers
section beforeAppServiceProvider
. Depending on the way you usehyt/tenancy
you have have some code like$env = app(Environment::class);
inAppServiceProvider::boot
which will instantiate theCache
object. So better placeCacheServiceProvider
before.
/*
* Application Service Providers...
*/
App\Providers\CacheServiceProvider::class,
App\Providers\AppServiceProvider::class,
After that cache should become tenant aware.
=====
If you are pretty sure you are not going to use anything like telescope
instantiating Cache
object early, you may try to use file
or database
cache driver.
Here the steps are different:
- Choose
file
ordatabase
cache driver in.env
CACHE_DRIVER=file
or CACHE_DRIVER=database
- Disable
hyn/tenancy
auto-discovery
Update composer.json
like this
"extra": {
"laravel": {
"dont-discover": [
"hyn/multi-tenant"
]
}
},
and run composer install
to apply changes.
- In
config/app.php
register providers likes this:
/*
* Application Service Providers...
*/
App\Providers\CacheServiceProvider::class,
Hyn\Tenancy\Providers\TenancyProvider::class,
Hyn\Tenancy\Providers\WebserverProvider::class,
App\Providers\AppServiceProvider::class,
- And finally add our
CacheServiceProvider
<?php
namespace App\Providers;
use Illuminate\Cache\FileStore;
use Illuminate\Cache\DatabaseStore;
use Illuminate\Support\ServiceProvider;
class CacheServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
$namespace = function($app) {
if (PHP_SAPI === 'cli') {
return $app['config']['cache.default'];
}
$fqdn = request()->getHost();
$uuid = \DB::table('hostnames')
->select('websites.uuid')
->join('websites', 'hostnames.website_id', '=', 'websites.id')
->where('fqdn', $fqdn)
->value('uuid');
return $uuid;
};
$cacheDriver = config('cache.default');
switch ($cacheDriver) {
case 'file':
\Cache::extend($cacheDriver, function ($app) use ($namespace){
$namespace = $namespace($app);
return \Cache::repository(new FileStore(
$app['files'],
$app['config']['cache.stores.file.path'].$namespace
));
});
break;
case 'database':
\Cache::extend($cacheDriver, function ($app) use ($namespace){
$namespace = $namespace($app);
return \Cache::repository(new DatabaseStore(
$app['db.connection'],
'cache',
$namespace
));
});
break;
case 'redis':
// But if not yet instantiated, then we are able to redifine namespace (prefix). Works for Redis only
if (PHP_SAPI === 'cli') {
$namespace = str_slug(env('APP_NAME', 'laravel'), '_').'_cache';
} else {
$fqdn = request()->getHost();
$namespace = \DB::table('hostnames')
->select('websites.uuid')
->join('websites', 'hostnames.website_id', '=', 'websites.id')
->where('fqdn', $fqdn)
->value('uuid');
}
\Cache::setPrefix($namespace);
break;
default:
}
}
}