Installation:
composer require commitm/multi-tenant
Ensure MultiTenantBundle is enabled in config/bundles.php (Symfony) or config/app.php (Laravel via bridge if applicable).
Configuration:
Create config/multi_tenant.yaml (or merge into existing config):
multi_tenant:
tenants:
company_id:
entity: App\Entity\Company
aware: App\Entity\Interfaces\TenantAware
property: company
App\Entity\Company with your tenant model.aware must implement CommitM\MultiTenant\Multitenancy\TenantAware.First Use Case:
Inject TenantsProvider into a service/controller:
use CommitM\MultiTenant\Multitenancy\Provider\TenantsProvider;
public function __construct(private TenantsProvider $tenantsProvider) {}
// Set tenant for the current request (e.g., from middleware)
$this->tenantsProvider->getTenantByClass(Company::class)->setEntity($company);
Middleware Integration: Extract tenant from request (e.g., subdomain, header, or route param) and set it:
public function handle(Request $request, Closure $next) {
$company = Company::find($request->get('company_id'));
$this->tenantsProvider->getTenantByClass(Company::class)->setEntity($company);
return $next($request);
}
Automatic Tenant Propagation:
For entities implementing TenantAware, the tenant is auto-set via the property (e.g., company):
$product = new Product(); // Automatically sets $product->company if tenant is active
Query Scoping:
Use the TenantAware trait in repositories to scope queries:
public function findByTenant() {
return $this->createQueryBuilder()
->where('p.company = :tenant')
->setParameter('tenant', $this->getTenant())
->getQuery()
->getResult();
}
Dynamic Tenant Switching: Override tenant mid-request (e.g., for admin actions):
$this->tenantsProvider->getTenantByClass(Company::class)->setEntity($newCompany);
spatie/laravel-symfony-bundle to integrate Symfony bundles into Laravel.prePersist/preUpdate to auto-set tenant fields if missing.TenantsProvider to isolate tenant logic:
$this->tenantsProvider->shouldReceive('getTenantByClass')->andReturn($mockTenant);
Interface Implementation:
Forgetting to implement TenantAware on scoped entities will break auto-propagation.
Fix: Add the interface and trait to all tenant-scoped models.
Circular Dependencies: Setting the tenant entity too early (e.g., in a service constructor) may cause issues if the tenant itself depends on other services. Fix: Defer tenant setup until the request lifecycle (e.g., middleware).
Query Performance:
Unscoped queries (e.g., Product::all()) will return all tenants’ data.
Fix: Always scope queries or use the TenantAware trait.
Configuration Overrides:
Hardcoding tenant IDs in config or services bypasses the provider.
Fix: Centralize tenant logic via TenantsProvider.
$tenant = $this->tenantsProvider->getTenantByClass(Company::class);
dump($tenant->getEntity()); // Should return the current tenant entity
$this->tenantsProvider->getTenantByClass(Company::class)->setEntity($company);
\Log::debug("Switched tenant to: {$company->id}");
Custom Tenant Providers:
Extend TenantsProvider to add logic (e.g., tenant fallback or validation):
class CustomTenantsProvider extends TenantsProvider {
public function validateTenant($tenant) {
if (!$tenant->isActive()) {
throw new \RuntimeException("Tenant is inactive");
}
}
}
Dynamic Tenant Resolution:
Override getTenantByClass to resolve tenants from context (e.g., API tokens):
$this->tenantsProvider->extend(function ($class) {
return $this->resolveFromAuth()->getTenant();
});
Multi-Tenant Migrations: Use Doctrine extensions to apply tenant-specific migrations:
// In a custom migration service
$this->tenantsProvider->getAllTenants()->each(function ($tenant) {
$this->runMigrationForTenant($tenant);
});
Property Naming:
The property in config must match the entity’s field name (e.g., company → $entity->company).
Fix: Use snake_case for consistency with Laravel conventions.
Entity Autowiring:
Ensure the entity in config is fully qualified (e.g., App\Entity\Company), not a short class name.
How can I help you explore Laravel packages today?