spatie / laravel-multitenancy Goto Github PK
View Code? Open in Web Editor NEWMake your Laravel app usable by multiple tenants
Home Page: https://spatie.be/docs/laravel-multitenancy
License: MIT License
Make your Laravel app usable by multiple tenants
Home Page: https://spatie.be/docs/laravel-multitenancy
License: MIT License
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?
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
is this package compatible to use for lumen project?
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.
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`)
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:
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:
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?
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
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.
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
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?
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?
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?
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?
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!
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);
Is it possible to use this package only in a REST-API setup (no sessions... etc) ?
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?
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
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?
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?
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?
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.
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?
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.
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:
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 ?
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:
TestCase.php
filepublic function setUp(): void
{
parent::setUp();
Tenant::find(1)->makeCurrent();
}
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),
.env.testing
file defines these variables with information for the databases and the same connections are defined in the database.php
config fileCurrently, 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 :-)
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?
i am getting unexcepted issue from \vendor\spatie\laravel-multitenancy\src\Actions\MakeTenantCurrentAction.php:13
Laravel Framework 7.12.0
PHP 7.4.7
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'.
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.
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?
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.
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.
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 !
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",
jobs
and jobs_failed
tables are in each tenant databaseQUEUE_CONNECTION=database
'queues_are_tenant_aware_by_default' => true
\Spatie\Multitenancy\Tasks\SwitchTenantDatabaseTask::class
is registered in multitenancy file configThe 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?
Is it me? or the MultitenancyServiceProvider always returns true on the following statement. :\
if (! class_exists('CreateLandlordTenantsTable')) {
echo 'It doesn´t exists';
}
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.
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!
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?
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
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,
],
],
there's two database for tenant
and for landlord
public function __construct(TestModel $model) { $this->model = $model; }
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
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: ...
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.
I start with 3 empty databases: landlord, one and two.
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)
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.
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?
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
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!
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.