›
byrcsc/laravel-dev-login · 1.x
Change how a profile finds its user when an email lookup on the guard's provider is not enough.
A resolver turns a profile into the user it names. The package ships one,
FindUserByEmail, and it is what runs unless you say otherwise.
Reach for a resolver of your own when your users are not found by email address, when a profile should pick a user by role or by recency, or when a development environment should create the user rather than look it up.
FindUserByEmail asks the profile's guard for its user provider, then calls:
$provider->retrieveByCredentials(['email' => $profile->email]);Asking the guard rather than a model class or a config key is what makes this work without configuration. Custom user models, several providers behind several guards, and providers that are not Eloquent all resolve through the same call, because it is the provider your application already trusts to find users at login.
A profile on the admin guard is looked up through whatever provider admin
names. Nothing about the package needs to know which model that is.
Implement ByRcsc\LaravelDevLogin\Contracts\UserResolver:
namespace ByRcsc\LaravelDevLogin\Contracts;
use ByRcsc\LaravelDevLogin\Profile;
use Illuminate\Contracts\Auth\Authenticatable;
interface UserResolver
{
public function resolve(Profile $profile): ?Authenticatable;
}One method. Return the user, or null to say it does not exist. The caller
turns null into ProfileUserNotFound, naming the profile and its email.
Resolvers are built through the container, so constructor injection works:
namespace App\DevLogin;
use App\Models\User;
use ByRcsc\LaravelDevLogin\Contracts\UserResolver;
use ByRcsc\LaravelDevLogin\Profile;
use Illuminate\Contracts\Auth\Authenticatable;
final class FindUserByUsername implements UserResolver
{
public function resolve(Profile $profile): ?Authenticatable
{
return User::query()
->where('username', $profile->email)
->first();
}
}The email key is a plain string on the profile. Nothing checks that it looks
like an address, so a resolver of your own can read it as a username, an
employee number, or anything else.
The first of these that is set wins:
| Where | Applies to |
|---|---|
The profile's resolver key | That one profile |
The resolver key in config/dev-login.php | Every other profile |
| Nothing set | FindUserByEmail |
Set the default for every profile:
// config/dev-login.php
'resolver' => App\DevLogin\FindUserByUsername::class,Or name one on a single profile:
'oldest' => [
'label' => 'Whoever was seeded first',
'email' => 'ignored@example.com',
'resolver' => App\DevLogin\TheOldestAccount::class,
],Both places take a class-string. The config file contains no closures anywhere,
which is what lets it survive config:cache.
A class-string that does not implement UserResolver throws before the
container is asked for it, so the message names the config key or the profile
rather than failing further down with an unresolvable dependency.
Nothing obliges a resolver to use email. This one takes whoever was seeded
first:
namespace App\DevLogin;
use App\Models\User;
use ByRcsc\LaravelDevLogin\Contracts\UserResolver;
use ByRcsc\LaravelDevLogin\Profile;
use Illuminate\Contracts\Auth\Authenticatable;
final class TheOldestAccount implements UserResolver
{
public function resolve(Profile $profile): ?Authenticatable
{
return User::query()->oldest('id')->first();
}
}The profile still needs an email, because it is a required key, and this
resolver never reads it.
The package never writes to your users table. A resolver of yours can:
public function resolve(Profile $profile): ?Authenticatable
{
return User::firstOrCreate(
['email' => $profile->email],
['name' => $profile->label, 'password' => bcrypt(Str::random(32))],
);
}The trade-off is that you lose the drift detector. ProfileUserNotFound is
what tells you a profile and a seeder have gone out of step, and a resolver
that creates the user never raises it.
A resolver runs after the tenant has been made current, so a tenant-scoped query needs nothing extra. The profile also carries the tenant if you want to branch on it:
public function resolve(Profile $profile): ?Authenticatable
{
if ($profile->hasTenant()) {
return User::query()
->where('tenant_id', $profile->tenant)
->where('email', $profile->email)
->first();
}
return User::query()->where('email', $profile->email)->first();
}