Skip to content
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

Driver [redis_tenancy] is not supported. #693

Closed
Nowi5 opened this issue Dec 16, 2018 · 25 comments
Closed

Driver [redis_tenancy] is not supported. #693

Nowi5 opened this issue Dec 16, 2018 · 25 comments
Labels
support Question or generic support.

Comments

@Nowi5
Copy link

Nowi5 commented Dec 16, 2018

Hi,

related to Make cache handling better for tenants #309 I get the issue of getting the error Driver [redis_tenancy] is not supported.
Currently I setup my own CI/CD process with local docker (base on php:7.1.8-apache), gitlab and production server. This seems to be related to some autogenerated loading of composer or so as it seems nearly to be random when it works and when not.

grafik

  • hyn/multi-tenant version: hyn/multi-tenant 5.3.1
  • laravel version: laravel/framework v5.6.39
  • webserver software and version: apache
  • php version: PHP 7.1.8 (cli) (built: Aug 4 2017 18:55:44) ( NTS )

Composer:

    "extra": {
        "laravel": {
            "dont-discover": [
				"hyn/multi-tenant",
				"spatie/laravel-permission"
            ]
        },

config/app

     /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Auth\AuthServiceProvider::class,
        ...

	// Needs to be before Hyn\Tenancy: https://github.com/hyn/multi-tenant/issues/309#issuecomment-401509408
	App\Providers\AppServiceProvider::class,
	App\Providers\CacheServiceProvider::class,		
		
        /*
         * Package Service Providers...
         */	   
        Hyn\Tenancy\Providers\TenancyProvider::class,
        Hyn\Tenancy\Providers\WebserverProvider::class,
        Spatie\Permission\PermissionServiceProvider::class,
        ...

        /*
         * Application Service Providers...
         */
        App\Providers\HelperServiceProvider::class,        
        App\Providers\AuthServiceProvider::class,
        App\Providers\ComposerServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,	    
       ...

config/cache.php

return [
    'default' => env('CACHE_DRIVER', 'file'), // in env = redis
    'stores' => [
        ...
        'redis' => [
            'driver' => 'redis_tenancy', //'redis', 'redis_tenancy'
            'connection' => 'default',
        ],
    ],
    'prefix' => env(
        'CACHE_PREFIX',
        str_slug(env('APP_NAME', 'laravel'), '_').'_cache'
    ),
];   

CacheServiceProvider:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Cache\RedisStore;

class CacheServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {	

        // https://github.com/hyn/multi-tenant/issues/309
		Cache::extend('redis_tenancy', function ($app) {
       		if (PHP_SAPI === 'cli') {
	            $uuid = $app['config']['driver'];
	        } else {
	            // ok, this is basically a hack to set the redis cache store
	            // prefix to the UUID of the current website being called
	            $fqdn = $_SERVER['SERVER_NAME'];
            
	            $uuid = DB::table('hostnames')
	                ->select('websites.uuid')
	                ->join('websites', 'hostnames.website_id', '=', 'websites.id')
	                ->where('fqdn', $fqdn)
	                ->value('uuid');
	        }

	        return Cache::repository(new RedisStore(
	            $app['redis'],
	            $uuid,
	            $app['config']['cache.stores.redis.connection']
	        ));
	    });	
    }

    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
	
    }
}

Any idea what is going wrong here? I think the boot of CacheServiceProvider is not loaded early enough, but how to change this?

While writing this, I located this: https://stackoverflow.com/a/53263914/1597218
The order in the config/app is not used as expected. In my case the order is the following:

  1. Illuminate
  2. 3rd Party (which may will use Cache as well)
  3. Rest of app/config including CacheServiceProvider, Hyn & App\

"According to the Application::registerConfiguredProviders it's hardcoded to have everything that starts with 'Illuminate' to go to the starting chunk, all others to the end, and Composer Packages go in the middle. "

Even so I add all missing 3rd Party provider into the app/config manually, the Application::registerConfiguredProviders list the providers twice.
So one option would be that this package add this provider to the default settings, as vendor files are loaded before app providers or alternativly adding a customIlluminate namespace.

@Nowi5
Copy link
Author

Nowi5 commented Dec 16, 2018

For now I just use my own Illuminate namespace:

composer.json

    "autoload": {
        "classmap": [
            "database/seeds",
            "database/factories"
        ],
        "psr-4": {
            "App\\": "app/",
			"Illuminate\\zCustom\\": "app/IlluminateOverwrite" 
        }
    },

grafik

\app\IlluminateOverwrite\Providers\CacheServiceProvider.php

<?php

namespace Illuminate\zCustom\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Cache\RedisStore;

class CacheServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {	

        // https://github.com/hyn/multi-tenant/issues/309
		Cache::extend('redis_tenancy', function ($app) {
       		if (PHP_SAPI === 'cli') {
	            $uuid = $app['config']['driver'];
	        } else {
	            // ok, this is basically a hack to set the redis cache store
	            // prefix to the UUID of the current website being called
	            $fqdn = $_SERVER['SERVER_NAME'];
            
	            $uuid = DB::table('hostnames')
	                ->select('websites.uuid')
	                ->join('websites', 'hostnames.website_id', '=', 'websites.id')
	                ->where('fqdn', $fqdn)
	                ->value('uuid');
	        }

	        return Cache::repository(new RedisStore(
	            $app['redis'],
	            $uuid,
	            $app['config']['cache.stores.redis.connection']
	        ));
	    });	
    }

    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
	
    }
}

Seems to work for now.

@JGamboa
Copy link

JGamboa commented Dec 17, 2018

Use the 5.2 version cache documentation.
I solved everything with that.

@Nowi5
Copy link
Author

Nowi5 commented Dec 17, 2018

@JGamboa please elaborate on that, any link or documentation?
Or you just mean update to latest hyn/multi-tenant version? Do we have any specific docu of caching in the new version?

@luceos
Copy link
Contributor

luceos commented Dec 17, 2018

@JGamboa can you clarify what you did that solved your issue? We'd love to improve the docs and implementation for everyone.

@luceos luceos added the support Question or generic support. label Dec 17, 2018
@JGamboa
Copy link

JGamboa commented Dec 17, 2018

@luceos Im using the hyn 5.3, but the documentation of https://laravel-tenancy.com/docs/hyn/5.3/cache doenst work. Instead i used the 5.2 documentation https://laravel-tenancy.com/docs/hyn/5.2/cache

and it works like a charm.

If u are using spattie you should add spattie to extra dont discover

"extra": {
    "laravel": {
        "dont-discover": [
            "hyn/multi-tenant",
            "spatie/laravel-permission"
        ]
    }
},

@Nowi5
Copy link
Author

Nowi5 commented Dec 17, 2018

You are right, in most cases it work, but as soon you have a 3rd party app using cache in boot, it will fail. Therefore my explanation above.

@JGamboa
Copy link

JGamboa commented Dec 17, 2018

@Nowi5 I think we should stay with the 5.2 documentation is more realistic considering that we all use 3rd party app.

@hakanersu
Copy link

hakanersu commented Feb 10, 2019

What about this, without changing provider just change prefix?

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class CacheServiceProvider extends ServiceProvider
{
    public function boot()
    {
        if (PHP_SAPI === 'cli') {
            $uuid = str_slug(env('APP_NAME', 'laravel'), '_').'_cache';
        } else {
            $fqdn = request()->getHost();
            $uuid = \DB::table('hostnames')
                ->select('websites.uuid')
                ->join('websites', 'hostnames.website_id', '=', 'websites.id')
                ->where('fqdn', $fqdn)
                ->value('uuid');
        }
        \Cache::setPrefix(str_slug($uuid, '_').'_cache');
    }
}
Illuminate\Cache\CacheServiceProvider::class,
App\Providers\CacheServiceProvider::class,

is there any downsides of this approach?

@neyosoft
Copy link

I think the manual way of setting provider priority in the config/app.php is still the best way to get it working without the error.

@luceos luceos closed this as completed Mar 7, 2019
@gruz
Copy link

gruz commented Mar 10, 2019

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:
        }
    }
}

@luceos Please, have look to this post. Maybe it's worth to update the docs.

@Miguel-Serejo
Copy link

@gruz Thanks for your detailed write-up. Thanks to you I managed to get a custom per-tenant file cache working.
I even got it to work with telescope, by adding it to dont-discover and manually registering Laravel\Telescope\TelescopeServiceProvider and App\Providers\TelescopeServiceProvider after my new CacheServiceProvider:

/*
	 * Laravel Framework Service Providers...
	 */
       [...]
        //Custom Cache Provider
	App\Providers\CacheServiceProvider::class,

	/*
	 * Package Service Providers...
	 */
	Laravel\Telescope\TelescopeServiceProvider::class,
	Laravel\Tinker\TinkerServiceProvider::class,
	Hyn\Tenancy\Providers\TenancyProvider::class,
	Hyn\Tenancy\Providers\WebserverProvider::class,
	/*
	 * Application Service Providers...
	 */
	App\Providers\AppServiceProvider::class,
	App\Providers\AuthServiceProvider::class,
	App\Providers\EventServiceProvider::class,
	App\Providers\RouteServiceProvider::class,
	App\Providers\ComposerServiceProvider::class,
	App\Providers\TelescopeServiceProvider::class,

There shouldn't be a meaningful difference between auto-discovering Telescope or manually registering it like this.

@kinsaz
Copy link

kinsaz commented Mar 27, 2019

I am getting "Call to undefined method Illuminate\Cache\FileStore::setPrefix()" trying to use \Cache::setPrefix($namespace); I added and removed combinations of the below:

use Illuminate\Cache\FileStore;
use Illuminate\Cache\RedisStore;
use Illuminate\Cache\DatabaseStore;

... but still can't access the method. In the api docs for Laravel 5.8 it says "void setPrefix(string $prefix)" for RedisStore https://laravel.com/api/5.8/Illuminate/Cache/RedisStore.html#method_setPrefix

I know it probably an easy fix but I can't get it working.

Thanks in advance for any suggestions!

@gruz
Copy link

gruz commented Mar 27, 2019

@kinsaz
Thats the main problem - FileStore doesn't have the ::setPrefix()
RedisStore has it instead.

I described it in my long post above.

@kinsaz
Copy link

kinsaz commented Mar 27, 2019

I agree I read it but I can't get it working. What do I need to do?

@gruz
Copy link

gruz commented Mar 27, 2019

@kinsaz
Do you use Redis in .env file as your caching engine?
CACHE_DRIVER=redis

@kinsaz
Copy link

kinsaz commented Mar 27, 2019

Yes, and I used the code exactly above and I never reference FileStore but I get the error

@gruz
Copy link

gruz commented Mar 27, 2019

@kinsaz artisan config:clear ?

@kinsaz
Copy link

kinsaz commented Mar 27, 2019

Thanks for your quick response! I tried config:clear earlier and it didn't work so I did it again along with artisan cache:clear, artisan clear-compiled and composer update and it worked. Now I don't get the above referenced error -so thanks - I really appreciate it!

That said, I now get a predis error "Call to a member function createCommand() on null" in the file /vendor/predis/predis/src/Client.php

"Call to a member function createCommand() on null" line 323
public function createCommand($commandID, $arguments = array())
{
return $this->profile->createCommand($commandID, $arguments);
}

Sorry to bug you with this but I appreciate your help.

@gruz
Copy link

gruz commented Mar 27, 2019

@kinsaz I didn't meet such an error. You need to debug it at your own. Sorry, cannot help with issue.

@kinsaz
Copy link

kinsaz commented Mar 27, 2019

Thanks for your help. If anyone else has any ideas. On the exception trace when I run cache:clear I get the same error as above "Call to a member function createCommand() on null"

but the stack trace give a bit more info here:

Predis\Client::createCommand("flushdb", [])

The second argument ($arguments = array()) is null.

from "Call to a member function createCommand() on null" line 323
public function createCommand($commandID, $arguments = array())

What would cause this?

@luceos
Copy link
Contributor

luceos commented Mar 28, 2019

Is predis/predis installed? @kinsaz

@zomax
Copy link

zomax commented Apr 14, 2019

@gruz @luceos @kinsaz @36864

Hi,

I did a nice trick to solve this issue. The above solution not working for me as i have many packages that instantiate the Cache object.

The problem is with this commit: laravel/framework#19646 Which make any Service Provider with namespace starting with Illuminate load first, then auto-discovered service providers, then any other providers added to config/app.php.

My solution is:

  • First change CacheServiceProvider namespace to Illuminate\UniqueAppName

  • Now just change cache.prefix value in the boot method

<?php

namespace Illuminate\UniqueAppName;

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') {
		$uuid = str_slug(env('APP_NAME', 'laravel'), '_').'_cache';
	} else {
		$fqdn = request()->getHost();
		$uuid = \DB::table('hostnames')
				->select('websites.uuid')
				->join('websites', 'hostnames.website_id', '=', 'websites.id')
				->where('fqdn', $fqdn)
				->value('uuid');
	}
		
	// Change Cache Prefix
        config( ['cache.prefix' => $uuid. '_cache'] );
   }
}
  • Then add file to composer.json autoload files object
"autoload": {
		"files": [
			"app/Providers/CacheServiceProvider.php"
		],
        "classmap": [
            "database/seeds",
            "database/factories"
        ],
        "psr-4": {
            "App\\": "app/"
        }
    },
  • Run composer dump-autoload

  • Finally add our CacheServiceProvider service provider to config/app.php before or after AppServiceProvider

/*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
	Illuminate\UniqueAppName\CacheServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,

It should work with any Cache drive

@zomax
Copy link

zomax commented Apr 15, 2019

For file Cache to work we need to change Cache storage path:

config( ['cache.stores.file.path' => storage_path('framework/cache/data/'. $uuid)] );

@froid24
Copy link

froid24 commented Jul 10, 2019

Hi @luceos ,
I just implemented the tenant cache with the file driver, following the idea of @zomax above. However, it did not work straight off the bat, and I wonder if this is not the case for the redis version also.

The problem was obtaining the uuid for the new prefix and cache file path. Since the CacheServiceProvider is added before the providers for Tenancy, the tenant database is not defined yet (but it is set in the configuration file), so an error is given.

This was solved by changing the line obtaining the uuid to use specifically the system connection (\DB::connection('system')->):

$uuid = \DB::connection('system')->table('hostnames')
->select('websites.uuid')
->join('websites', 'hostnames.website_id', '=', 'websites.id')
->where('fqdn', $fqdn)
->value('uuid');

A small detail, but maybe it will help others as well.

Cheers!

PS: Btw, the tenant-aware cache is a huge issue, and if not implemented in Tenancy and left to the user to decide, it should be at least mentioned visibly in the installation documentation. Most tenancy websites will have auth (like in my case), and having user permissions messed up between instances is a nasty security breach (and a pain to debug if not knowing what to look for).

@reggismota
Copy link

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:
        }
    }
}

@luceos Please, have look to this post. Maybe it's worth to update the docs.

I'm using another multi-tenant library and your answer was useful to me. Using version 11 of laravel I was able to create a CacheServiceProvider and do what you said. I just summarized the code by removing the switch and leaving what I would use (file), it worked perfectly. Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
support Question or generic support.
Projects
None yet
Development

No branches or pull requests