Tenant awrare cache note - gruz/multi-tenancy-voyager-tries GitHub Wiki

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 or database 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 in config/app.php providers section before AppServiceProvider. Depending on the way you use hyt/tenancy you have have some code like $env = app(Environment::class); in AppServiceProvider::boot which will instantiate the Cache object. So better place CacheServiceProvider 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 or database 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:
        }
    }
}