tenantedlaravel/core
Core package for Tenanted Laravel, a multi-tenancy toolkit for Laravel. Resolve tenants by domain or subdomain, scope Eloquent queries to the active tenant, switch database connections automatically, and keep tenant data isolated—all within a single codebase. Ideal for SaaS apps.
Sprout is a Laravel package for giving a single user model multiple identities (email, username, phone, social account, etc.) and resolving the right user from any of them at login time. v1.2.0 is a compatibility/maintenance release rather than a feature release, so it is a safe entry point for new installs.
Minimal setup
composer require sprout-laravel/sprout — check the resolved constraint against your app: v1.2.0 adds Laravel 13 support alongside the previously supported versions, so verify composer.json allows the framework version you actually run before upgrading in a locked project.php artisan vendor:publish --tag="sprout-config"
php artisan vendor:publish --tag="sprout-migrations"
php artisan migrate
User model so it can own identities.config/sprout.php.Where to look first
config/sprout.php — the resolver list is the heart of the package; everything else is plumbing around it.Identity model/contract shipped by the package — this is the shape your registration and profile screens will write to.First use case
Registration writes a User plus one or more identity rows (email at minimum); login resolves the submitted credential through the resolver chain, gets back an identity, and hands you the associated user to authenticate. Start by wiring email login through a single resolver before adding phone/username, so you can isolate failures.
Keep identity creation alongside user creation. Wrap registration in a transaction: create the user, then attach identities. If you let identities be optional at registration you will hit users who cannot log in after a resolver change.
Resolver chain, ordered intentionally. Register resolvers in priority order in config — typically: exact identifier (username/id) → email → phone → external provider. Each resolver should be narrow: it answers "does this input look like my type, and what identity row matches it?" Return null rather than throwing for non-matches so the chain can continue.
// app/Sprout/EmailResolver.php
public function resolve(string $identifier): ?Identity
{
if (! filter_var($identifier, FILTER_VALIDATE_EMAIL)) {
return null; // let the next resolver try
}
return Identity::query()
->where('type', 'email')
->where('value', Str::lower($identifier))
->first();
}
Login flow. Resolve → guard against null → Auth::login($identity->user). Keep the "resolve" step separate from the "authenticate/session" step; that separation is what makes passwordless, magic-link, and social flows reuse the same resolver chain.
Write paths should normalise, not the resolver. Lowercase emails, E.164 phone numbers, and case-insensitive usernames at write time. Resolvers then do exact matches, which stay index-friendly.
Testing integration. Extend the package's Testbench/PHPUnit base if it ships one, and use RefreshDatabase plus factories for identities. Cover three cases per resolver: matches, plausible-but-missing, and not-my-type (returns null). v1.2.0 bumps PHPUnit/Testbench/Larastan, so re-run your suite after the upgrade — mostly relevant if you subclass package test classes.
Multi-guard apps. If you have admin vs. customer guards, scope resolvers per guard (or add a guard column on identities) rather than relying on a single global chain; otherwise an admin username can resolve a customer identity.
The deprecation warning for identity resolvers was updated in v1.2.0. Treat this as the headline item of the release: the current resolver mechanism is on a removal path and the warning text has been clarified. If you are starting fresh, keep your resolver implementations behind a thin interface of your own so the eventual swap is a one-file change; if you already have resolvers, budget time to migrate rather than waiting for the removal release. Check the changelog link for the specific replacement direction — this release does not describe it.
No runtime breaking changes advertised. The PHPStan fixes, baseline removal, and CI nightly job are all maintainer-side. Your real risk is transitive: bumping Testbench/PHPUnit/Larastan can conflict with pinned dev dependencies in your app if you extend the package's testing utilities.
Laravel 13 support means wider constraints, not narrower. Packages often broaden illuminate/* ranges while bumping orchestra/testbench. Run composer why-not sprout-laravel/sprout if the install fails after upgrading Laravel, and re-run Larastan after the upgrade since the package's own baseline was removed.
Nightly CI is a signal, not a guarantee. The workflow update means upstream (Laravel/PHPStan) breakage gets caught fast, but tagged releases can still lag. If you track dev-main, pin to a commit in CI.
Beware per-request resolver queries. A resolver that joins on every request will quietly tax your login and any middleware that resolves identities. Restrict resolution to auth endpoints; don't call it from a global middleware or a Blade composer.
Duplicate identities are the classic bug. Enforce a unique index on (type, value) at the database level — application-level checks lose to concurrent registrations.
Debugging. Log which resolver matched (class name + identity id) at debug level. When "login fails for one user only," it is nearly always a normalisation mismatch or a duplicate row created before the unique index existed — query by LOWER(value) to spot it.
Extension points to prefer. Resolver registration via config (not hardcoded in a service provider) and a custom Identity subclass are the two seams that survive releases; avoid extending package internals, which the static-analysis churn in this release shows are not a stability contract.
How can I help you explore Laravel packages today?