GithubHelp home page GithubHelp logo

spatie / laravel-multitenancy Goto Github PK

View Code? Open in Web Editor NEW
1.0K 27.0 145.0 490 KB

Make your Laravel app usable by multiple tenants

Home Page: https://spatie.be/docs/laravel-multitenancy

License: MIT License

PHP 99.79% Blade 0.21%
laravel php multitenancy

laravel-multitenancy's Introduction

An unopinionated multitenancy package for Laravel apps

Latest Version on Packagist GitHub Tests Action Status Total Downloads

This package can make a Laravel app tenant aware. The philosophy of this package is that it should only provide the bare essentials to enable multitenancy.

The package can determine which tenant should be the current tenant for the request. It also allows you to define what should happen when switching the current tenant to another one. It works for multitenancy projects that need to use one or multiple databases.

Before starting with the package, we highly recommend first watching this talk by Tom Schlick on multitenancy strategies.

The package contains a lot of niceties such as making queued jobs tenant aware, making an artisan command run for each tenant, an easy way to set a connection on a model, and much more.

Are you a visual learner? Then watch this video that covers how you can use laravel-multitenancy and how it works under the hood.

Support us

We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.

Documentation

You can find the entire documentation for this package on our documentation site.

Testing

You'll need to create the following 3 local MySql databases to be able to run the test suite:

  • laravel_mt_landlord
  • laravel_mt_tenant_1
  • laravel_mt_tenant_2

You can run the package's tests:

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you've found a bug regarding security please mail [email protected] instead of using the issue tracker.

Credits

The code of this package is based on the code shown in the Multitenancy in Laravel series by Mohamed Said

Alternatives

License

The MIT License (MIT). Please see License File for more information.

laravel-multitenancy's People

Contributors

adrianmrn avatar alexrififi avatar alexvanderbist avatar anthonypeck avatar bimsh avatar binaryk avatar claudsonm avatar drasko95 avatar erikn69 avatar freekmurze avatar igorbabko avatar inmanturbo avatar jacobmllr95 avatar juukie avatar laravel-shift avatar lasseeee avatar masterix21 avatar miclf avatar moisish avatar mpbarlow avatar nielsvanpach avatar raymadrona avatar riasvdv avatar ricardov03 avatar rigby90 avatar santiagazo avatar sevannerse avatar stancl avatar telkins avatar timoladoyinbo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-multitenancy's Issues

Check current tenant on EnsureValidTenantSession middleware

This is not an issue but maybe a feature request.

It seems that EnsureValidaTenant middleware session is executed before than NeedsTenant middleware (I have EnsureValidTenantSession in the web middleware group).

The following exception error is raised when a tenant doesn't exists:

[previous exception] [object] (ReflectionException(code: -1): Class currentTenant does not exist at /home/vagran
t/Foobar/Code/vendor/laravel/framework/src/Illuminate/Container/Container.php:805)

The error is raised because the tenant returned by the TenantFinder is null (It doesn't exists), and it's logic to receive such exception, however in my case I am using the database in order to save the tenant sessions, and I think that it could be a good idea to check the tenant before initialize the session because the session is not available when the tenant doesn't exists and the database is used in order to save the sessions.

My proposal is to modify the "EnsureValidTenantSession" middleware with something like that:

use Closure;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Spatie\Multitenancy\Concerns\UsesMultitenancyConfig;
use Spatie\Multitenancy\Models\Tenant;
use Symfony\Component\HttpFoundation\Response;

/**
 * Class EnsureValidTenantSession.
 *
 * @package App\Tenancy\Middleware
 */
class EnsureValidTenantSession
{
    use UsesMultitenancyConfig;

    /**
     * @param  \Illuminate\Http\Request  $request $request
     * @param Closure $next
     * @return mixed|void
     */
    public function handle(Request $request, Closure $next)
    {
        $sessionKey = 'ensure_valid_tenant_session_tenant_id';

        $currentTenant = $this->resolveCurrentTenant($request);

        if (! $currentTenant)
            return $this->handleInvalidTenantSession($request);

        if (! $request->session()->has($sessionKey)) {
            $request->session()->put($sessionKey, $currentTenant->id);

            return $next($request);
        }

        if ($request->session()->get($sessionKey) !== $currentTenant->id) {
            return $this->handleInvalidTenantSession($request);
        }

        return $next($request);
    }


    /**
     * Attempt to resolve a tenant.
     *
     * @param Request $request
     * @return Tenant|null
     */
    protected function resolveCurrentTenant(Request $request) : ?Tenant
    {
        $tenant = null;

        try {
            $tenant = app($this->currentTenantContainerKey());
        } catch (BindingResolutionException $e) {
            Log::warning('Unable to resolve tenant', ['url' => $request->getUri()]);
        }

        return $tenant;
    }


    /**
     * Abort request.
     *
     * @param Request $request
     */
    protected function handleInvalidTenantSession(Request $request) : void
    {
        abort(Response::HTTP_UNAUTHORIZED);
    }
}

If you agree with that, I can submit a push request or maybe you can can suggest a better way to manage this case.

In case that you think that is not necessary to check the current tenant then please proceed to close this issue.

tenant migration adds to landlord database

Hi,

Thanks for the great package!

I just started testing the package and created some records directly into the landlord.tenants table and ran php artisan tenants:artisan migrate but it adds them to my landlord database (only once).

here is my files and the settings I am using:

// config/database.php

return [

    'default' => env('DB_CONNECTION', 'landlord'),


    'connections' => [

        'tenant' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => null,
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => false,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

        'landlord' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => 'landlord',
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => false,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],
]
// config/multitenancy.php

return [

    'tenant_finder' => DomainTenantFinder::class,

    'tenant_artisan_search_fields' => [
        'id',
    ],

    'switch_tenant_tasks' => [
        Spatie\Multitenancy\Tasks\SwitchTenantDatabaseTask::class,
    ],


    'tenant_model' => Tenant::class,

    'queues_are_tenant_aware_by_default' => true,

    'tenant_database_connection_name' => 'tenant',

    'landlord_database_connection_name' => 'landlord',

    'current_tenant_container_key' => 'currentTenant',

    'actions' => [
        'make_tenant_current_action' => MakeTenantCurrentAction::class,
        'forget_current_tenant_action' => ForgetCurrentTenantAction::class,
        'make_queue_tenant_aware_action' => MakeQueueTenantAwareAction::class,
        'migrate_tenant' => MigrateTenantAction::class,
    ],
];
# .env
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=homestead
DB_PASSWORD=secret

Using Laravel Scheduler with Multitenancy

Running php artisan tenants:artisan schedule:run loops through the tenants and runs the scheduled commands but Tenant::current() is null. I think I've narrowed this down to the scheduler calling an artisan command in a forked process which recalls php artisan XXX with whatever the command is (which bypasses the multi-tenancy call).

Is there a way to make the scheduler tenant aware like the package currently does with queues?

Thanks!

How to configure for testing?

I'm trying to test in the standard Laravel way with PHPSTORM, PHPUnit and sqlite in memory database (RefreshDatabase trait).

I am trying to assert that a record has been created in the landlord database (actually I am trying to TDD a command which creates a tenant). I assumed that the connected database would be the landlord one but it does not contain a tenants table.

\DB::select('SHOW TABLES')

result = {array} [6]
 0 = {stdClass} [1]
  Tables_in_landlord = "contacts"
 1 = {stdClass} [1]
  Tables_in_landlord = "failed_jobs"
 2 = {stdClass} [1]
  Tables_in_landlord = "messages"
 3 = {stdClass} [1]
  Tables_in_landlord = "migrations"
 4 = {stdClass} [1]
  Tables_in_landlord = "password_resets"
 5 = {stdClass} [1]
  Tables_in_landlord = "users"

env('DB_CONNECTION')
sqlite

\DB::connection()
landlord

PHPUnit.xml

 <server name="DB_CONNECTION" value="sqlite"/>
 <server name="DB_DATABASE" value=":memory:"/>

This is showing the structure for a tenant database. How should I configure so that I am testing against a landlord database?

EnsureValidTenantSession not needed for sites using subdomain as identification

Am I right to assume that the EnsureValidTenantSession is not needed when the tenant is identified through subdomains, and when the login page is at the subdomain (subdomain.domain.com/login)?

This due to the nature of how browsers handle cookie domains when the SESSION_DOMAIN in Laravel is left at the default value null. I.e. the value defaults to the domain of the resource that was requested: subdomain.domain.com, essentially "preventing users from a tenant abusing their session to access another tenant", given they all identify through the subdomain.

Decouple domain from logic

Thanks for your work on the package.

The base package seems to be slightly coupled to a domain based tenant e.g. tenant.domain.com in that the initial landlord migration creates a column called domain. It is easy enough for the user to change the tenant identification logic from getHost() and change the migration to rename the landlord table identification column to tenant_identifier (or something similar), but wonder if the change to the column name should be made in this base package?

Thanks

TenantAware and NotTenantAware are not detected in MakeQueueTenantAwareAction

There's a problem I noticed when I was working on a project is that there's some unexpected behavior happens in queues, at which I try to implement NotTenantAware or TenantAware and switch the queues_are_tenant_aware_by_default key, and then I realize that there's a problem happens on the other side, meaning that if the tenant side is fixed, the landlord side crashes in queues and vice versa.

Env:

  • PHP 7.4.8
  • Laravel 7.0
  • OS: Windows
  • Local Server Client: Laragon
namespace App\Listeners\Landlord;

use App\Events\Landlord\TenantCreated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Artisan;
use Spatie\Multitenancy\Jobs\NotTenantAware;

class OnTenantCreated implements ShouldQueue, NotTenantAware
{
    public $queue = 'tenants_db';
    public function __construct()
    {
        //
    }
    public function handle(TenantCreated $event)
    {
        Artisan::call(
          "tenants:artisan \"migrate --database=tenant --seed\" --tenant={$event->tenant->id}"
        );
    }
}

this code is Landlord Specific Event Listener

namespace App\Listeners\Tenant;

use App\Events\Tenant\UserCreated;
use App\Mail\UserCreatedMail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Mail;
use Spatie\Multitenancy\Jobs\TenantAware;

class OnUserCreated implements ShouldQueue, TenantAware
{
    public $queue = 'tenants_mail';
    public function __construct()
    {
        //
    }
    public function handle(UserCreated $event)
    {
        Mail::to($event->user)->send(new UserCreatedMail($event->user));
    }
}

and this one is tenant specific.

However, when I switch the key in config one works and one crashes, but I noticed something happened also.
When I tried to use Tinker or Web to inspect those Listeners Implementations I got the right implementation Like following:

Tinker:

class_implements(\App\Listeners\Landlord\OnTenantCreated::class)

//Output

[
     "Illuminate\Contracts\Queue\ShouldQueue" => "Illuminate\Contracts\Queue\ShouldQueue",
     "Spatie\Multitenancy\Jobs\NotTenantAware" => "Spatie\Multitenancy\Jobs\NotTenantAware",
]

Web:

use App\Listeners\Landlord\OnTenantCreated;

Route::get('/reflect', function (){
    return class_implements(OnTenantCreated::class);
});

Output: 

{
"Illuminate\\Contracts\\Queue\\ShouldQueue": "Illuminate\\Contracts\\Queue\\ShouldQueue",
"Spatie\\Multitenancy\\Jobs\\NotTenantAware": "Spatie\\Multitenancy\\Jobs\\NotTenantAware"
}

And then I tried to Log Listener implementations to errors log file and I noticed something very very weird:

[2020-07-14 13:08:49] local.INFO: array (
  'Illuminate\\Contracts\\Queue\\ShouldQueue' => 'Illuminate\\Contracts\\Queue\\ShouldQueue',
)  
[2020-07-14 13:08:52] local.ERROR: The current tenant could not be determined in a job named `Illuminate\Queue\CallQueuedHandler@call`. No `tenantId` was set in the payload. {"exception":"[object] (Spatie\\Multitenancy\\Exceptions\\CurrentTenantCouldNotBeDeterminedInTenantAwareJob(code: 0): The current tenant could not be determined in a job named `Illuminate\\Queue\\CallQueuedHandler@call`. No `tenantId` was set in the payload. at C:\\laragon\\www\\test-mt\\vendor\\spatie\\laravel-multitenancy\\src\\Exceptions\\CurrentTenantCouldNotBeDeterminedInTenantAwareJob.php:12)
[stacktrace]
#0 C:\\laragon\\www\\test-mt\\vendor\\spatie\\laravel-multitenancy\\src\\Actions\\MakeQueueTenantAwareAction.php(83): Spatie\\Multitenancy\\Exceptions\\CurrentTenantCouldNotBeDeterminedInTenantAwareJob::noIdSet(Object(Illuminate\\Queue\\Events\\JobProcessing))
#1 C:\\laragon\\www\\test-mt\\vendor\\spatie\\laravel-multitenancy\\src\\Actions\\MakeQueueTenantAwareAction.php(48): Spatie\\Multitenancy\\Actions\\MakeQueueTenantAwareAction->findTenant(Object(Illuminate\\Queue\\Events\\JobProcessing))
#2 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Events\\Dispatcher.php(381): Spatie\\Multitenancy\\Actions\\MakeQueueTenantAwareAction->Spatie\\Multitenancy\\Actions\\{closure}(Object(Illuminate\\Queue\\Events\\JobProcessing))
#3 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Events\\Dispatcher.php(226): Illuminate\\Events\\Dispatcher->Illuminate\\Events\\{closure}('Illuminate\\\\Queu...', Array)
#4 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Queue\\Worker.php(512): Illuminate\\Events\\Dispatcher->dispatch('Illuminate\\\\Queu...')
#5 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Queue\\Worker.php(343): Illuminate\\Queue\\Worker->raiseBeforeJobEvent('database', Object(Illuminate\\Queue\\Jobs\\DatabaseJob))
#6 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Queue\\Worker.php(306): Illuminate\\Queue\\Worker->process('database', Object(Illuminate\\Queue\\Jobs\\DatabaseJob), Object(Illuminate\\Queue\\WorkerOptions))
#7 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Queue\\Worker.php(132): Illuminate\\Queue\\Worker->runJob(Object(Illuminate\\Queue\\Jobs\\DatabaseJob), 'database', Object(Illuminate\\Queue\\WorkerOptions))
#8 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Queue\\Console\\WorkCommand.php(112): Illuminate\\Queue\\Worker->daemon('database', 'tenants_db', Object(Illuminate\\Queue\\WorkerOptions))
#9 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Queue\\Console\\WorkCommand.php(96): Illuminate\\Queue\\Console\\WorkCommand->runWorker('database', 'tenants_db')
#10 [internal function]: Illuminate\\Queue\\Console\\WorkCommand->handle()
#11 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Container\\BoundMethod.php(33): call_user_func_array(Array, Array)
#12 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Container\\Util.php(37): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
#13 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Container\\BoundMethod.php(91): Illuminate\\Container\\Util::unwrapIfClosure(Object(Closure))
#14 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Container\\BoundMethod.php(35): Illuminate\\Container\\BoundMethod::callBoundMethod(Object(Illuminate\\Foundation\\Application), Array, Object(Closure))
#15 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Container\\Container.php(592): Illuminate\\Container\\BoundMethod::call(Object(Illuminate\\Foundation\\Application), Array, Array, NULL)
#16 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Console\\Command.php(134): Illuminate\\Container\\Container->call(Array)
#17 C:\\laragon\\www\\test-mt\\vendor\\symfony\\console\\Command\\Command.php(258): Illuminate\\Console\\Command->execute(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
#18 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Console\\Command.php(121): Symfony\\Component\\Console\\Command\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
#19 C:\\laragon\\www\\test-mt\\vendor\\symfony\\console\\Application.php(911): Illuminate\\Console\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#20 C:\\laragon\\www\\test-mt\\vendor\\symfony\\console\\Application.php(264): Symfony\\Component\\Console\\Application->doRunCommand(Object(Illuminate\\Queue\\Console\\WorkCommand), Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#21 C:\\laragon\\www\\test-mt\\vendor\\symfony\\console\\Application.php(140): Symfony\\Component\\Console\\Application->doRun(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#22 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Console\\Application.php(93): Symfony\\Component\\Console\\Application->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#23 C:\\laragon\\www\\test-mt\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Console\\Kernel.php(129): Illuminate\\Console\\Application->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#24 C:\\laragon\\www\\test-mt\\artisan(37): Illuminate\\Foundation\\Console\\Kernel->handle(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#25 {main}
"} 

As the result shows, it does not detect TenantAware or NotTenantAware interfaces from inside MakeQueueTenantAwareAction

protected function listenForJobsBeingQueued(): self
    {
        app('queue')->createPayloadUsing(function ($connectionName, $queue, $payload) {
            $job = $payload['data']['command'];

            Log::info(class_implements($job));

            if (! $this->isTenantAware($job)) {
                return [];
            }

            return ['tenantId' => optional(Tenant::current())->id];
        });

        return $this;
    }

Solutions I tried:

  • composer dump-autoload
  • php artisan optimize
  • php artisan config:clear
  • php artisan cache:clear
  • php artisan queue:restart
  • Restarted local web server
  • Restarted tinker
  • Restarted the whole computer

Testing Issue

Hi!

Thanks for the wonderful work on this package! The integration with the app in progress went quite well and things are working well on the actual app (I think!).

One snag that I seem to have hit is with testing. All the tests were working correctly before integration with this package. I made the following changes to get things working:

  • Added the following method to the TestCase.php file
public function setUp(): void
{
    parent::setUp();

    Tenant::find(1)->makeCurrent();
}
  • Updated the multitenancy.php config file so it would have the test database information for landlord and tenant
'tenant_database_connection_name' => env('DB_CONNECTION_TENANT', null),
'landlord_database_connection_name' => env('DB_CONNECTION_LANDLORD', null),
  • The .env.testing file defines these variables with information for the databases and the same connections are defined in the database.php config file

Currently, most of the tests are working, but some are throwing up random errors. The thing that is common in all tests where failures are occurring is the use of DatabaseTransactions trait.

Checking further, I found that the trait is infact not working. If some entries are created / deleted in the tests, they do not get reverted.

Any help would be great to have :-)

Confusion about EnsureValidTenantSession

Hi. First off this is a great package, and apologies if I'm just misunderstanding something here. I notice that there is another open issue about this middleware, but I think it's talking about a different issue, sorry if not.

The documentation says to register EnsureValidTenantSession as a global middleware.

However if you do this, and your TenantFinder returns null, you get a BindingResolutionException, because currentTenant is never bound.

You can throw in your TenantFinder, but this won't work if you're using the provided DomainTenantFinder. This global middleware approach also prevents having any routes that are not tenant aware.

Providing I've not just made a stupid mistake somewhere, I've thought of a few potential solutions, and if the maintainers are on board with any I'd be happy to submit a PR:

  • Modify MultiTenantServiceProvider::determineCurrentTenant to always bind currentTenant, even if it's null. I don't really like this approach, because the act of "making current" is all handled nicely by the Tenant model currently, and this would break that.

  • Modify EnsureValidTenantSession such that it simply continues with the next request if no tenant is set. I don't think this could cause any issues, as if there is no tenant I don't think there could be any tenant abuse? But it would be a breaking change -- same applies to updating EnsureValidTenantSession to throw NoCurrentTenant if the tenant is not bound.

  • Update the documentation to instead recommend either:

  • Applying both NeedsTenant and EnsureValidTenantSession as global middleware, if all routes are tenant aware, or;

  • Creating a new middleware group containing both NeedsTenant and EnsureValidTenantSession, if only some routes are tenant aware. This behaves as you would expect -- it first requires a tenant, throwing a nice easy-to-catch NoCurrentTenant, then validates the session to protect against abuse, giving a 401 if that check fails.

I think the documentation option is probably the best as it requires no breaking changes (or any code changes at all)?

Please let me know what you think, like I say I'd be happy to open a PR for any of these.

Database alternative

Rather than using this code to lookup a row from a database table:

return $this->getTenantModel()::whereDomain($host)->first();

...is it possible to populate the model manually? My use case is that I have multiple tenant config files which I would prefer to use. Ie: 'config/tenants/nike.php', 'config/tenants/apple.php'.

For example:

return config('tenants.' . $host);

How to run migrations on database created when tenant is created ?

Hi

I'm struggling a bit with this !
I got my database created automatically when a tenant is created, with an Event listener on my Tenant model :

        static::created(function (UserTenant $model) {
            $model->createDatabase();
        });

       // ....
    public function createDatabase()
    {
      DB::connection()->statement("CREATE DATABASE " . $this->getDatabaseName() );

     Artisan::call('tenants:artisan "migrate --database=tenant" --tenant=' . $this->id);

and I tried to add some call to the Artisan command from this function, so the table are migrated, but I get an error :
'There are no commands defined in the "migrate" namespace.'

Thanks for your help !

Executing custom artisan commands for each tenant

I have a command called app:setup that creates the default values for the settings and drop-downs. This command also takes some parameters to just add specific defaults.
When I run php artisan tenants:artisan "app:setup" as mentioned in the docs, it tries to do the setup against my landlord database and fails because I don't have those tables in the landlord.
I also tried to add the database param to the command and it says that the The "--database" option does not exist.

Do I need to just use php artisan app:setup and then run a loop for each tenant or there is a way to make the docs suggestion work.

New tenant registration + Passport

As discussed in #29 I have likely a simple question. When registering a new tenant, should we be calling php artisan passport:install each time to generate keys and clients as part of the initilization?

Static functions and DB Facade

Hey team,

Loving the package so far.

Have you created anything for automatically setting the connection when using the DB:: facade? Would you just add that to the "switch_tenant_tasks" or override the original Database Provider?

Scheduled Job issue

Hi --
I have the package set up and things are working as expected....with the exception of my scheduled jobs.

I have queues enabled by default to be tenant aware:
'queues_are_tenant_aware_by_default' => true

I have the middleware added to app/http/kernel:

// middlewaregroups / web:
\Spatie\Multitenancy\Http\Middleware\NeedsTenant::class,
\Spatie\Multitenancy\Http\Middleware\EnsureValidTenantSession::class

In App\Console\Kernel, I have a scheduled job:
$schedule->job(new UpdateMatrix())->everyFiveMinutes();

The job runs twice but only the main database updates.

In my cronjob (which executes the Scheduler) I have applied the tenants:artisan prefix.

For the UpdateMatrix job above, I have added TenantAware
class UpdateMatrix implements ShouldQueue, TenantAware..

What am I missing?

Multiple database setup issue

A new issue has come up after introduction of #92

I have a mysql database setup which has tenant and landlord databases and another set on sqlite (used for testing). Before introduction of the above PR I was able to migrate and seed these databases by just specifying the connections on the artisan commands.

I.e. the below commands all worked without issues:

php artisan migrate:fresh --path=database/migrations/landlord
php artisan tenants:artisan "migrate:fresh --seed --database=tenant"

for mysql. And:

php artisan migrate:fresh --path=database/migrations/landlord --database=testing_landlord
php artisan tenants:artisan "migrate:fresh --seed --database=testing_tenant"

for sqlite setup. However, since the connections must be specified in the config file now this setup no longer works without having to change the config file before working on each database.

If this is the intended behavior going forward, it would be great to know the correct way to work through this issue.

Failed to install the package

Hi, I'm not sure if the package is ready or not yet, but I'm trying to install it and it failed, here are some screenshots. Thanks.

Screen Shot 2020-05-13 at 5 56 59 PM

Screen Shot 2020-05-13 at 5 57 49 PM

TenantAware with custom Tenant Finder in queues

Hi, In my case, I'm using a middlewaare to define the middleware (as pointed at #53 ).

The problem is that When I queue a simple Send mail, it always throw a Spatie\Multitenancy\Exceptions\CurrentTenantCouldNotBeDeterminedInTenantAwareJob.

How can I set a custon Tenant Finder for queue?

Migrating Tenant Databases - Multi Database

I've been follow all steps in the documentation, from creating landlord and tenant db connection then create a couple of rows in the tenants table with correct database name. After that i ran this command php artisan tenants:artisan migrate but the result always give me error: Invalid catalog name: 1046 No database selected

Full error message:
Screen Shot 2020-05-29 at 11 59 14

My database config:
Screen Shot 2020-05-29 at 12 00 45

My tenants table in landlord database:
Screen Shot 2020-05-29 at 12 02 44

My local databases:
Screen Shot 2020-05-29 at 12 03 53

My multitenancy.php config file:
carbon

Tenant migration command adds tables to landlord database.

command: php artisan tenants:artisan "migrate" --tenant=1
output: Running command for tenant tenant1 (id: 1)...
But all the tables are added to landlord database.
Here's my landlord connection.
image
Here's my tenant connection.
image

I am new to the package and cannot figure out it on my own. Thank you

[Question] Tenants with custom domain

Hi,

I looked into your package and it's amazing by the way. I was wondering how you would suggest handling clients with their own domain name? How can we figure out the current tenant?

when there's a `Failed tenant job` it will be inserted in landlord database

there's two database for tenant and for landlord

Event:

public function __construct(TestModel $model) { $this->model = $model; }

Listener function:

public function handle(TestEvent $event): void { throw new \Exception("Test record failed jobs"); }

-> database of landlord has failed_jobs table
-> database of tenant has failed_jobs table

result:

in the tenant failed_jobs table is empty
in the landlord failed_jobs table is:
Exception: Test record failed jobs in D:\fullpath\ListenerFile.php:53 Stack trace: ...

Passport migrations executed against tenants.

Hi, just installed Passport and I am getting this error when I create (and migrate) a new tenant:

Base table or view already exists: 1050 Table 'oauth_auth_codes' already exists (surely this just means in the landlord, this is a new tenant db).

Can I just move the Passport migrations into the landlord directory?

Migration !class_exists always true

Is it me? or the MultitenancyServiceProvider always returns true on the following statement. :\

if (! class_exists('CreateLandlordTenantsTable')) {
  echo 'It doesn´t exists';
}

Best practices for Passport and MultiTenancy

First off, I hope I have not become an annoyance in here! I have created a Chrome extension that originally leveraged password grant authentication and a standard URL to authenticate. Now, with subdomain-based tenants, I'm not clear on what the best approach might be.

I am trying to require a user to only have to provide email and password when logging in via the extension; however, without knowing the subdomain or manually entering it, I'm hitting a wall on how to determine which tenant this user belongs to and, indeed, switching the db connection to that tenant as well.

Again, apologies if this is noise in here, just wondering if someone else is dealing with this. I realize it's out of scope for the package itself.

Queuing Tenant Database Migration Event To Landlord Jobs Table

First of all, thanks for great work in this package and the simplicity of implementation, I hope this package keep developing with same philosophy of simplicity.

Preconditions:

  • php version: 7.4.8
  • laravel version: 7.0
  • os: windows

I am trying to fire queuable event in the tenant model (created) observer method to run the migration of each created tenant in the background and simultaneously, but then I got an error like this when the event is fired:

Illuminate/Database/QueryException with message 'SQLSTATE[3D000]: Invalid catalog name: 1046 No database selected (SQL: insert into jobs (queue, attempts, reserved_at, available_at, created_at, payload) values (tenants_db, 0, ?, 1594402919, 1594402919, {"uuid":"77b0d6aa-6313-4276-82fd-0538c5eb7ee7","displayName":"App//Listeners//OnTenantCreated","job":"Illuminate//Queue//CallQueuedHandler@call","maxTries":null,"maxExceptions":null,"delay":null,"timeout":null,"timeoutAt":null,"data":{"commandName":"Illuminate//Events//CallQueuedListener","command":"O:36:/"Illuminate//Events//CallQueuedListener/":8:{s:5:/"class/";s:29:/"App//Listeners//OnTenantCreated/";s:6:/"method/";s:6:/"handle/";s:4:/"data/";a:1:{i:0;O:24:/"App//Events//TenantCreated/":2:{s:6:/"tenant/";O:45:/"Illuminate//Contracts//Database//ModelIdentifier/":4:{s:5:/"class/";s:26:/"App//Models//Landlord//Tenant/";s:2:/"id/";i:55;s:9:/"relations/";a:0:{}s:10:/"connection/";s:8:/"landlord/";}s:6:/"socket/";N;}}s:5:/"tries/";N;s:10:/"retryAfter/";N;s:9:/"timeoutAt/";N;s:7:/"timeout/";N;s:3:/"job/";N;}"}}))'

I think this problem happens because the database default connection is set to tenant which has database value set to null by default, so I cannot be able to access landlord database jobs table, and to be able to distribute the migrations queue tasks to each tenant it must migrate the jobs table first, so I feel that I am lost in an egg or chicken dilemma, so anyone can help with solution or suggest another workflow ?

Error dispatching queued jobs from artisan commands

Thanks for the great work, I have been using it for a short time and it has worked quite well except for putting a job in the queue when it is dispatched from an artisan command

Preconditions:

  • "php": "^7.4.0"
  • "laravel/framework": "^7.0",
  • Database for each tenant
  • jobs and jobs_failed tables are in each tenant database
  • Queue connection environment var QUEUE_CONNECTION=database
  • multitenancy config file has this value for queues 'queues_are_tenant_aware_by_default' => true
  • \Spatie\Multitenancy\Tasks\SwitchTenantDatabaseTask::class is registered in multitenancy file config

The command handler looks like:

class ImportUsers extends Command
{
    ... //removed to keep it short...
    public function handle(PeopleImporter $import)
    {
         ... //removed to keep it short
        try {
              DB::beginTransaction();
               user = User::updateOrCreate($data);
               if ($access->wasRecentlyCreated) {
		   // Push a queue job for publish a notification
		   dispatch(new PublishNewUser($access))->delay($this->delay);
		} else {
		  // Push a queue job for publish a notification
		   dispatch(new PublishUserChanges($access))->delay($this->delay);
		}
              DB::commit();
         } catch (\Throwable $exception) {
             DB::rollBack();
             Log::error($exception->getTraceAsString());
         }
         ... //removed to keep it short
    }
}

Using the tenants:artisan command wrapper, my command signature looks like:

php artisan tenants:artisan "app:import-users --file=files/imports/users.csv" --tenant=1

I have verified that the users are correctly registered in the correct database schema for the specified tenant, but I always get an error when trying to push the job to the queue.

The exception trace is:

Running command for tenant `Sandbox` (id: 1)...
---------------------------------------------------------

Starting import
===============

  0/10 [░░░░░░░░░░░░░░░░░░░░░░░░░░░░]   0%

Error: Call to a member function prepare() on null in /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php:458
Stack trace:
#0 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php(664): Illuminate\Database\Connection->Illuminate\Database\{closure}('insert into `jo...', Array)
#1 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php(631): Illuminate\Database\Connection->runQueryCallback('insert into `jo...', Array, Object(Closure))
#2 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php(465): Illuminate\Database\Connection->run('insert into `jo...', Array, Object(Closure))
#3 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php(417): Illuminate\Database\Connection->statement('insert into `jo...', Array)
#4 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/Processor.php(32): Illuminate\Database\Connection->insert('insert into `jo...', Array)
#5 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(2771): Illuminate\Database\Query\Processors\Processor->processInsertGetId(Object(Illuminate\Database\Query\Builder), 'insert into `jo...', Array, NULL)
#6 /var/www/html/vendor/laravel/framework/src/Illuminate/Queue/DatabaseQueue.php(162): Illuminate\Database\Query\Builder->insertGetId(Array)
#7 /var/www/html/vendor/laravel/framework/src/Illuminate/Queue/DatabaseQueue.php(111): Illuminate\Queue\DatabaseQueue->pushToDatabase('publish-access-...', '{"uuid":"a7cd02...', Object(Carbon\Carbon))
#8 /var/www/html/vendor/laravel/framework/src/Illuminate/Queue/Queue.php(60): Illuminate\Queue\DatabaseQueue->later(Object(Carbon\Carbon), Object(App\Jobs\PublishAccessChanges), '', 'publish-access-...')
#9 /var/www/html/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(171): Illuminate\Queue\Queue->laterOn('publish-access-...', Object(Carbon\Carbon), Object(App\Jobs\PublishAccessChanges))
#10 /var/www/html/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(158): Illuminate\Bus\Dispatcher->pushCommandToQueue(Object(Illuminate\Queue\DatabaseQueue), Object(App\Jobs\PublishAccessChanges))
#11 /var/www/html/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(73): Illuminate\Bus\Dispatcher->dispatchToQueue(Object(App\Jobs\PublishAccessChanges))
#12 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Bus/PendingDispatch.php(134): Illuminate\Bus\Dispatcher->dispatch(Object(App\Jobs\PublishAccessChanges))
#13 /var/www/html/app/Imports/PeopleImport.php(104): Illuminate\Foundation\Bus\PendingDispatch->__destruct()
#14 /var/www/html/vendor/maatwebsite/excel/src/Sheet.php(275): App\Imports\PeopleImport->onRow(Object(Maatwebsite\Excel\Row))
#15 /var/www/html/vendor/maatwebsite/excel/src/Reader.php(111): Maatwebsite\Excel\Sheet->import(Object(App\Imports\PeopleImport), 2)
#16 /var/www/html/vendor/maatwebsite/excel/src/Transactions/NullTransactionHandler.php(14): Maatwebsite\Excel\Reader->Maatwebsite\Excel\{closure}()
#17 /var/www/html/vendor/maatwebsite/excel/src/Reader.php(115): Maatwebsite\Excel\Transactions\NullTransactionHandler->__invoke(Object(Closure))
#18 /var/www/html/vendor/maatwebsite/excel/src/Excel.php(146): Maatwebsite\Excel\Reader->read(Object(App\Imports\PeopleImport), 'files/imports/p...', 'Csv', 's3')
#19 /var/www/html/vendor/maatwebsite/excel/src/Concerns/Importable.php(37): Maatwebsite\Excel\Excel->import(Object(App\Imports\PeopleImport), 'files/imports/p...', 's3', 'Csv')
#20 /var/www/html/app/Console/Commands/ImportPeople.php(66): App\Imports\PeopleImport->import('files/imports/p...', 's3', 'Csv')
#21 [internal function]: App\Console\Commands\ImportPeople->handle(Object(App\Imports\PeopleImport))
#22 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(33): call_user_func_array(Array, Array)
#23 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/Util.php(36): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}()
#24 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(91): Illuminate\Container\Util::unwrapIfClosure(Object(Closure))
#25 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))
#26 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php(592): Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), Array, Array, NULL)
#27 /var/www/html/vendor/laravel/framework/src/Illuminate/Console/Command.php(134): Illuminate\Container\Container->call(Array)
#28 /var/www/html/vendor/symfony/console/Command/Command.php(258): Illuminate\Console\Command->execute(Object(Symfony\Component\Console\Input\StringInput), Object(Illuminate\Console\OutputStyle))
#29 /var/www/html/vendor/laravel/framework/src/Illuminate/Console/Command.php(121): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\StringInput), Object(Illuminate\Console\OutputStyle))
#30 /var/www/html/vendor/symfony/console/Application.php(911): Illuminate\Console\Command->run(Object(Symfony\Component\Console\Input\StringInput), Object(Illuminate\Console\OutputStyle))
#31 /var/www/html/vendor/symfony/console/Application.php(264): Symfony\Component\Console\Application->doRunCommand(Object(App\Console\Commands\ImportPeople), Object(Symfony\Component\Console\Input\StringInput), Object(Illuminate\Console\OutputStyle))
#32 /var/www/html/vendor/symfony/console/Application.php(140): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\StringInput), Object(Illuminate\Console\OutputStyle))
#33 /var/www/html/vendor/laravel/framework/src/Illuminate/Console/Application.php(93): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\StringInput), Object(Illuminate\Console\OutputStyle))
#34 /var/www/html/vendor/laravel/framework/src/Illuminate/Console/Application.php(185): Illuminate\Console\Application->run(Object(Symfony\Component\Console\Input\StringInput), Object(Illuminate\Console\OutputStyle))
#35 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(263): Illuminate\Console\Application->call('zintech:import:...', Array, Object(Illuminate\Console\OutputStyle))
#36 /var/www/html/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(261): Illuminate\Foundation\Console\Kernel->call('zintech:import:...', Array, Object(Illuminate\Console\OutputStyle))
#37 /var/www/html/vendor/spatie/laravel-multitenancy/src/Commands/TenantsArtisanCommand.php(56): Illuminate\Support\Facades\Facade::__callStatic('call', Array)
#38 /var/www/html/vendor/spatie/laravel-multitenancy/src/Commands/TenantsArtisanCommand.php(42): Spatie\Multitenancy\Commands\TenantsArtisanCommand->runArtisanCommandForTenant(Object(App\Models\Tenancy\Tenant), 'zintech:import:...')
#39 /var/www/html/vendor/laravel/framework/src/Illuminate/Support/Traits/EnumeratesValues.php(202): Spatie\Multitenancy\Commands\TenantsArtisanCommand->Spatie\Multitenancy\Commands\{closure}(Object(App\Models\Tenancy\Tenant), 0)
#40 /var/www/html/vendor/spatie/laravel-multitenancy/src/Commands/TenantsArtisanCommand.php(43): Illuminate\Support\LazyCollection->each(Object(Closure))
#41 [internal function]: Spatie\Multitenancy\Commands\TenantsArtisanCommand->handle()
#42 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(33): call_user_func_array(Array, Array)
#43 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/Util.php(36): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}()
#44 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(91): Illuminate\Container\Util::unwrapIfClosure(Object(Closure))
#45 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))
#46 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php(592): Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), Array, Array, NULL)
#47 /var/www/html/vendor/laravel/framework/src/Illuminate/Console/Command.php(134): Illuminate\Container\Container->call(Array)
#48 /var/www/html/vendor/symfony/console/Command/Command.php(258): Illuminate\Console\Command->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Illuminate\Console\OutputStyle))
#49 /var/www/html/vendor/laravel/framework/src/Illuminate/Console/Command.php(121): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Illuminate\Console\OutputStyle))
#50 /var/www/html/vendor/symfony/console/Application.php(911): Illuminate\Console\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#51 /var/www/html/vendor/symfony/console/Application.php(264): Symfony\Component\Console\Application->doRunCommand(Object(Spatie\Multitenancy\Commands\TenantsArtisanCommand), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#52 /var/www/html/vendor/symfony/console/Application.php(140): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#53 /var/www/html/vendor/laravel/framework/src/Illuminate/Console/Application.php(93): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#54 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(129): Illuminate\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#55 /var/www/html/artisan(37): Illuminate\Foundation\Console\Kernel->handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#56 {main}# php artisan tenants:artisan "app:import-users --file=files/imports/users.csv" --tenant=1

The result is the same if exec the command without database transactions

Do you have any idea how I can solve it?

How to: phpunit tests

How to I run my phpunit tests with laravel-multitenancy
So far I had them running with a sqlite database just for testing puposes. How should i setup testing now?

Sanctum package not switching the database connection

I am using sanctum package for authorizing users in both tenant database connection and landlord database connection. In my case I'm using example.test domain for authorizing landlord users, and subdomains ex. subdomain.example.test for authorizing tenant users. I am using landlord connection as default and works good when trying to login as landlord user, but when trying to login in subdomain.example.test it looks like sanctum cannot change database connection to 'tenant'.

introduction.md needs editing

Im just reading through the documentation and on the introduction page in the last paragraph it links to a video that cant be found and talks about laravel-responsecache instead of laravel-multitenancy

How to make telescope tenant aware?

How can I make telescope tenant aware? here is the part that throws error because 'mysql' is no longer available but instead the name is dynamic:

'driver' => env('TELESCOPE_DRIVER', 'database'),

    'storage' => [
        'database' => [
            'connection' => env('DB_CONNECTION', 'mysql'),
            'chunk' => 1000,
        ],
    ],

Generating route using route()

When generating routes using route() helper inside a queued job or mail, it will generate the non scoped route. For example: tenant is acme with the domain acme.localhost and I will send a user a password recovery email implementing ShouldQueue with a link inside the mail/notification using the route helper function. The route helper will actually return the route using the root domain (localhost) instead of acme.localhost because of how the routes are setup.

So my question is if there is any elegant way to handle this problem without having to write my own UrlGenerator implementation and instead use a switch task?

Tenant multi domain testing

Hi team,
First of all thanks for this amazing package!
I'm fairly new to Laravel and now trying to rewrite an old project in Laravel with this multitenancy package.
Part of rewriting the project is trying to do things better, which for one involves adding tests.
I've got Laravel and multitenancy up and running, and I've got my first green tests. But now I'm stuck at testing for a subdomain returning the right status code.
For example, I create one tenant with domain one.tenancytest.test and I assert for a status 200 on one.tenancytest.test and a status 500 on doesnotexist.tenancytest.test
The first one works, but the second assertion fails as it also returns status 200.
My tests and setup are here: https://github.com/CasperBE/tenancytest
\Spatie\Multitenancy\Http\Middleware\NeedsTenant::class has been added to the App/Http/Kernel.php file
All the steps I've made to get to this point are in the README.md file.
I hope that someone can help me out here!
Thanks in advance,
Jasper

Accessing top domain page

Hi,

So when I try to access my top domain home page: main.test , I get this:

Target class [currentTenant] does not exist.```

But I would like to be able to access this page so I can have a home page.

where can i store my configuration ?

i got stuck in a suiation where i need set different configuration to different tenant
so where can i keep those configuration . should i add colum on tenants table or make new table on landlord db.

Tables not created when running migrations for new tenant.

I start with 3 empty databases: landlord, one and two.

  1. Migrate the landlord db
php artisan migrate --path=database/migrations/landlord --database=landlord 

Migration table created successfully.
Migrating: 2018_08_08_100000_create_telescope_entries_table
Migrated:  2018_08_08_100000_create_telescope_entries_table (0.63 seconds)
Migrating: 2020_07_24_114830_create_landlord_tenants_table
Migrated:  2020_07_24_114830_create_landlord_tenants_table (0.15 seconds)
  1. Add tenants to the table.
  2. Run tenant migrations:
php artisan tenants:artisan "migrate --database=tenant"

Running command for tenant `one` (id: 1)...
---------------------------------------------------------
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.14 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.07 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0.05 seconds)
Migrating: 2020_07_20_084618_create_table1_table
Migrated:  2020_07_20_084618_create_table2_table (0.14 seconds)
Migrating: 2020_07_20_155442_create_table2_table
Migrated:  2020_07_20_155442_create_table2_table (0.22 seconds)

Running command for tenant `two` (id: 2)...
---------------------------------------------------------
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.14 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.1 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0.07 seconds)
Migrating: 2020_07_20_084618_create_table1_table
Migrated:  2020_07_20_084618_create_table1_table (0.15 seconds)
Migrating: 2020_07_20_155442_create_table2_table
Migrated:  2020_07_20_155442_create_table2_table (0.23 seconds)
All done!

Everything is good at this point, landlord and tenants contain correct tables.

  1. Now I add a new tenant database and tenant record.
  2. Run migrations for new tenant (yes, the id is correct):
php artisan tenants:artisan "migrate" --tenant=3

Running command for tenant `three` (id: 3)...
---------------------------------------------------------
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.13 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.09 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0.05 seconds)
Migrating: 2020_07_20_084618_create_contacts_table
Migrated:  2020_07_20_084618_create_contacts_table (0.14 seconds)
Migrating: 2020_07_20_155442_create_messages_table
Migrated:  2020_07_20_155442_create_messages_table (0.24 seconds)
All done!

When I inspect, database three does not contain any tables and the landlord now contains table1 and table2 (which are tenant tables).

Am I doing something wrong?

[Question] SaSS multi-tenant

Hi guys,

I have another question. Let's say I have a multi-tenant SaSS app. I am also using Laravel Nova to have the admin panel. I have a few questions about best practices and general questions:

1- Where do I store the users table? In each tenant? If so how can I access the tenants?
2- As I am using Laravel Nova, I created a resource called Tenant so I can manage the tenants but from where do I access it?
3- Where would you guys suggest that I store the cashier tables?

I am thinking that I need to put the users and cashier tables in the landlord connection, am I missing something?

Undefined variable: host

Hi, I am following the instructions from https://docs.spatie.be/laravel-multitenancy/v1/installation/using-multiple-databases/

and am at the step where I am supposed to run
php artisan migrate --path=database/migrations/landlord --database=landlord

However, I am getting this error:
Undefined variable: host (SQL: select * from information_schema.tables where table_schema = landlord and table_name = migrations and table_type = 'BASE TABLE')

I've checked my mysql, it is running, and I have created the database called landlord.

Just wondering why this might be occurring and if there is a fix?

"Session store not set on request." error after adding global EnsureValidTenantSession middleware

If I remove that middleware the app runs fine.

I'm guessing that when I add the EnsureValidTenantSession middleware to the global middleware stack it's making sure that there's a valid tenant all the time, is that correct?
The problem is I don't want to have a tenant all the time.

Instead of loading tenants by domain, I want to find them by wildcard, which I'm already achieving by using a custom TenantFinder and that's working fine.
So, for example HTTP://myapp.test/myrestaurant will load the My Restaurant tenant.

If there's no wildcard on the request then I'm assuming it's a request for the landlord's application to be running.
So HTTP://myapp.test/clients would be where the landlord would see all the clients for the SaaS, not for the My Restaurant tenant.

Right now there are no middlewares for the routes.
My plan is to make all the landlord's routes checked by something like IsLandlordAdmin auth-like middleware group and then Route::get('/{tenant}', 'TenantController@someMethod'); would take care of the tenant's logic.

Can't migrate when using Laravel Telescope.

I get this error:

Base table or view already exists: 1050 Table 'telescope_entries'

Presumably this is because the telescope migration is running against the tenants. Is there a way to get this to run against the landlord?

Multitenancy + Activity Log

Can't seem to get the connection working with Activity Log. I've tried both setting 'database_connection' => 'tenant', in config.activitylog, as well as config(['activitylog.database_connection' => 'tenant']); in a tenant middleware. I continue to get this error:

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'tenant_d62db5ba-bcaa-498f-a8eb-b8af00b8e119.activity_log' doesn't exist (SQL: select count(*) as aggregate from `activity_log`) 

Registration flow question

Looks great, and so far so good. Cheers!

I've got an initial setup in which a user registers, a tenant is created and its db created. Wondering your thoughts on best approach to complete the process.

I'm thinking of dispatching a job for seeding the tenant database (using Artisan::call) like so:

Artisan::call('tenants:artisan migrate --seed', [
            '--tenant' => [$this->tenant->id],
        ]);

and inserting the User. I understand I can make jobs tenant-aware, but my auth routes are outside the tenant subdomain. Can I still use makeCurrent() here somehow? Or do I need to manually pass the new Tenant Id directly to the job at this stage?

Multitenancy with Passport, Scheduling, Filesystem

Hi,

Great work! I am really very excited to use this package at my running project.

I have read DOCs. I could not find any settings regarding API Passport and scheduling. Doesn't have any specific Settings for managing API requests and managing scheduling jobs?

If I want to use a different File storage directory for each Tenant managing File storage, Does there have any method/settings to maintain this?

Anwar

Custom Tenant Finder using authenticated user

Hi.
In my case, The Tenant is linked to User and not to domain (All tenants will access the app from the same domain).
But when I run Auth::user() in TenantFinder@findForRequest I always get null.

How can I access The authenticated user inside TenantFinder?

Switch the tenant on the same domain

Hello!

I'm building an app that will be accessible for users from a single domain. A user can access multiple tenants.

How would you switch the tenant on the session level?

I've tried to manually store the tenant id in the session, when the MadeTenantCurrentEvent is fired. I've created a custom TenantFinder that would read this id from the session and detect the tenant. However, the session is not yet initialized when my custom TenantFinder is being executed, and I am not able to read the id from the session.

I believe that it's linked to #53.

Thank you very much!

[QUESTION] about managing tenants via "landlord" admin panel

I've managed to migrate and seed landlord and two test tenants (tenant1.laravel.test, tenant2.laravel.test).
I would like to have a couple of admin pages for "landlord" but I can't figure out how to do that on my own.
These "landlord" pages would mainly be used for tenant management (simple admin panel for tenant CRUD operations).
I think that because of "NeedsTennant" middleware, I can't serve anything on 'laravel.test' domain. When I try to open 'laravel.test', I get:

Spatie\Multitenancy\Exceptions\NoCurrentTenant
The request expected a current tenant but none was set.

I'm aware that this is expected behavior and can't even imagine if what I'm trying to do is possible or even if it should be done this way, mainly because I'm still a beginner.
What would be a proper way to implement "landlord" portion of application that would be accessible via 'laravel.test'.
Thanks in advance!

SwitchTenantDatabaseTask Issue with UsesLandlordConnection

When using SwitchTenantDatabaseTask, UsesLandlordConnection models are trying to mistakenly connect to tenant instead of landlord database.

If I remove SwitchTenantDatabaseTask, UsesLandlordConnection models seems to be making valid connection to the landlord database.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.