›
byrcsc/laravel-dev-login · 1.x
Let a profile carry a tenant by implementing the TenantResolver contract for your tenancy package.
A profile can name a tenant, and clicking it makes that tenant current before authenticating. One click then puts you in the right account and the right tenant.
The package ships the contract and no adapter. How a tenant becomes the current one is something only your tenancy package knows, so this page is about writing the twenty lines that connect the two.
If your application has no tenancy, nothing here applies. Profiles without a
tenant never reach a resolver.
namespace ByRcsc\LaravelDevLogin\Contracts;
use ByRcsc\LaravelDevLogin\Profile;
interface TenantResolver
{
public function makeCurrent(Profile $profile): void;
}The value to act on is $profile->tenant, exactly as it was written in
config. A string stays a string and an integer stays an integer, because the
package hands it over untouched.
Returning means the tenant is current. A tenant that cannot be made current throws.
A typical implementation looks up the tenant and hands it to whatever your tenancy package uses to switch:
namespace App\DevLogin;
use App\Models\Tenant;
use ByRcsc\LaravelDevLogin\Contracts\TenantResolver;
use ByRcsc\LaravelDevLogin\Exceptions\TenantNotResolved;
use ByRcsc\LaravelDevLogin\Profile;
final class MakeTenantCurrent implements TenantResolver
{
public function makeCurrent(Profile $profile): void
{
$tenant = Tenant::query()->find($profile->tenant);
if ($tenant === null) {
throw TenantNotResolved::make($profile);
}
// Whatever your tenancy package calls to switch: swapping a
// connection, setting a cache prefix, binding the current tenant.
$tenant->makeCurrent();
}
}Throwing TenantNotResolved::make($profile) is optional. Any exception is
wrapped in one, so a resolver that lets its own failure escape produces the
same message.
Resolvers are built through the container, so constructor injection works. The package's demo application writes the tenant into the session in nine lines, which is enough to prove the switch happened.
// config/dev-login.php
'tenant_resolver' => App\DevLogin\MakeTenantCurrent::class,This is a class-string rather than a closure, so the config file survives
config:cache. A class-string that does not implement TenantResolver throws
InvalidConfiguration naming the key to edit.
'profiles' => [
'acme-owner' => [
'label' => 'Owner',
'email' => 'owner@acme.test',
'tenant' => 'acme',
],
'globex-owner' => [
'label' => 'Owner',
'email' => 'owner@globex.test',
'tenant' => 'globex',
],
'staff' => [
'label' => 'Staff',
'email' => 'staff@example.com',
],
],The page groups buttons under a heading per tenant, in the order the tenants were configured. Profiles naming no tenant come last, under no heading. Both Owner buttons above can carry the same label, because the heading says which is which.
The tenant is made current before the user is resolved, so the lookup happens inside the tenant. A tenant-scoped user provider therefore finds the right row without the resolver doing anything special.
The order also decides what a failure means. A tenancy failure happens before anybody is authenticated, so it can never leave you logged into the wrong tenant.
Three things can go wrong, and all three stop the login:
| Situation | Exception |
|---|---|
A profile names a tenant, tenant_resolver is null | TenantNotResolved::noResolver |
| The configured class does not implement the contract | InvalidConfiguration |
| The resolver throws | TenantNotResolved::make |
Whatever your tenancy package threw is kept as the previous exception, so the stack trace still shows the real cause underneath a message naming the profile and the tenant:
Dev login profile [acme-owner] could not make the tenant [acme] current.A profile that names a tenant with no resolver configured is a button that cannot do what it says, which is why that case throws rather than logging you in without the tenant.
A tenant-bound profile ignores the session's intended URL. A URL captured in
one tenant rarely means anything in another, so the redirect falls through to
default_redirect and then to / unless the profile names its own.
Give tenant profiles an explicit redirect when the tenant has a landing page
of its own.
The contract has one method. Nothing in the login flow would call a matching teardown, so none exists.
A failure after makeCurrent() returns, such as a profile pointing at a user
who does not exist, leaves the tenant current for the rest of a request that
is about to end on an error page.