Sprout uses a driver-based factory pattern across its core components. This pattern enables third-party packages to extend Sprout with custom implementations without modifying core code.
Sprout needs to support multiple implementations of key concepts:
Rather than hardcoding these, Sprout uses configuration-driven factories. You specify a driver name in config, and the factory creates the appropriate implementation. This keeps the core flexible and allows packages like Bud, Seedling, and Canopy to register their own drivers.
Three of Sprout’s managers extend a common BaseFactory class. This isn’t just code reuse — it’s a deliberate design
decision that ensures consistency across the system.
The base factory provides:
Subclasses only need to implement two methods:
getFactoryName() — Returns the type name (e.g., 'provider', 'resolver')getConfigKey() — Maps a name to its config pathThis means adding a new manager (as extension packages do) follows a predictable pattern. You extend BaseFactory,
implement the two methods, add your driver creation methods, and the rest works automatically.
When you request an instance (e.g., $sprout->providers()->get('tenants')), the factory resolves it through a priority
chain:
driver valuecreateEloquentProvider() for driver eloquentcreateDefaultProvider()This priority order is deliberate:
The convention for built-in methods is create{Driver}{Type}() — so createEloquentProvider(),
createSubdomainResolver(), etc. This makes it easy to find how a driver is created and to add new built-in drivers.
| Manager | Creates | Config Key |
|---|---|---|
| TenancyManager | Tenancy instances | multitenancy.tenancies |
| TenantProviderManager | TenantProvider implementations | multitenancy.providers |
| IdentityResolverManager | IdentityResolver implementations | multitenancy.resolvers |
| ServiceOverrideManager | ServiceOverride implementations | multitenancy.overrides |
The first three extend BaseFactory. ServiceOverrideManager is different — overrides have lifecycle requirements
(setup/cleanup phases, bootable overrides) that don’t fit the simple factory pattern. See
Service Overrides for details on why.
The primary extension point is register(). Third-party packages call this to add new drivers:
TenantProviderManager::register('api', function (Application $app, array $config, string $name) {
return new ApiTenantProvider($name, $config['endpoint'], $config['api_key']);
});
The closure receives:
$app — The Laravel application container$config — The full configuration array for this instance$name — The configuration name (e.g., 'tenants')Once registered, the driver can be used in configuration:
'providers' => [
'external' => [
'driver' => 'api',
'endpoint' => 'https://api.example.com/tenants',
'api_key' => env('TENANT_API_KEY'),
],
],
Custom creators take priority over built-in drivers, allowing you to override default behaviour if needed.
For built-in drivers, the factory uses a naming convention. When you specify 'driver' => 'eloquent' for a provider,
the factory looks for a method called createEloquentProvider(). This convention makes it easy to add new built-in
drivers — just add a method following the pattern.
| Manager | Driver | Method |
|---|---|---|
| TenantProviderManager | eloquent |
createEloquentProvider() |
| TenantProviderManager | database |
createDatabaseProvider() |
| IdentityResolverManager | subdomain |
createSubdomainResolver() |
| IdentityResolverManager | path |
createPathResolver() |
The Sprout class provides access to all managers:
$sprout = app(Sprout::class);
$sprout->providers(); // TenantProviderManager
$sprout->resolvers(); // IdentityResolverManager
$sprout->tenancies(); // TenancyManager
$sprout->overrides(); // ServiceOverrideManager
From a manager, you can get specific instances:
$provider = $sprout->providers()->get('tenants');
$resolver = $sprout->resolvers()->get('subdomain');
This factory pattern is how Sprout’s extension packages integrate:
Each package calls the appropriate Manager::register() method in its service provider, making its drivers available
through standard configuration.