roberts/laravel-singledb-tenancy
Installation:
composer require roberts/laravel-singledb-tenancy
Publish the config and migration:
php artisan vendor:publish --provider="Roberts\SingleDbTenancy\SingleDbTenancyServiceProvider"
php artisan migrate
Configure Tenant Model:
Extend Roberts\SingleDbTenancy\Contracts\Tenant in your Tenant model:
use Roberts\SingleDbTenancy\Contracts\Tenant as TenantContract;
class Tenant extends Model implements TenantContract
{
// Your tenant fields (e.g., `domain`, `subdomain`, `id`)
}
Define Tenant Resolution:
In config/singledb-tenancy.php, set the resolution logic (e.g., subdomain):
'resolution' => [
'type' => 'subdomain',
'column' => 'subdomain',
],
First Use Case:
Deploy a tenant-aware route (e.g., tenant.{subdomain}.example.com) and test with:
php artisan route:list
Verify tenant resolution via Tinker:
use Roberts\SingleDbTenancy\Facades\Tenancy;
Tenancy::resolve('test-tenant'); // Manually trigger resolution
Tenant-Aware Routes: Use middleware to enforce tenancy:
Route::middleware(['tenant'])->group(function () {
Route::get('/', 'HomeController@index');
});
RouteServiceProvider to bind subdomains:
Route::domain('{tenant}.app.test')->group(...);
Model Scoping: Automatically scope queries to the current tenant:
class User extends Model
{
public function scopeTenant($query)
{
return $query->where('tenant_id', Tenancy::tenant()->id);
}
}
'scoping' => [
'enabled' => true,
'models' => [User::class, Post::class],
],
Tenant Context: Access the current tenant anywhere:
$tenant = Tenancy::tenant(); // Current tenant model
$tenantId = Tenancy::id(); // Current tenant ID
@tenant in Blade:
<h1>Welcome, {{ current_tenant()->name }}</h1>
Middleware Integration:
public function handle($request, Closure $next)
{
Tenancy::resolveByHeader('X-Tenant-ID');
return $next($request);
}
Tenancy::fallback() for public routes.API Tenancy: For APIs, resolve tenants via headers or tokens:
Tenancy::resolveByHeader('X-Tenant-Token');
Tenancy::runInContext() for tenant-specific jobs:
Tenancy::runInContext($tenant, function () {
SendWelcomeEmail::dispatch($user);
});
Tenancy::fake()->setTenant($fakeTenant);
Tenancy::seedForTenant() to seed data per tenant.Resolution Conflicts:
app/Providers/TenancyServiceProvider:
public function boot()
{
Tenancy::resolver(function ($request) {
return Tenant::where('custom_field', $request->input('tenant_id'))->first();
});
}
Query Performance:
tenant_id to every query.withoutGlobalScope(TenantScope::class).Middleware Order:
App\Http\Kernel above other middleware:
protected $middlewareGroups = [
'web' => [
\Roberts\SingleDbTenancy\Http\Middleware\ResolveTenancy::class,
// Other middleware...
],
];
Database Locks:
DB::transaction(function () {
Tenancy::resolve($tenant);
// Critical operations...
});
Caching:
Cache::put("tenant:{$tenantId}:key", $value);
Log Tenant Resolution: Enable debug mode in config:
'debug' => env('TENANCY_DEBUG', false),
Check logs for resolution events.
Tenant Context Leaks:
Tenancy::forget() is called in middleware termination or use Tenancy::clear().Migration Issues:
Tenancy::seedForTenant() or run migrations in a tenant context:
php artisan migrate --tenant=1
Custom Tenant Models:
Extend the Tenant model to add fields like plan_id or features:
class Tenant extends Model implements TenantContract
{
protected $casts = [
'features' => 'array',
];
}
Dynamic Tenant Fields:
Override getTenantIdentifier() to use dynamic fields:
public function getTenantIdentifier()
{
return $this->subdomain ?? $this->domain;
}
Webhook Tenancy: Resolve tenants via webhook payloads:
Tenancy::resolver(function ($request) {
return Tenant::where('webhook_secret', $request->secret)->first();
});
Multi-Factor Resolution: Combine domain + API key for resolution:
Tenancy::resolver(function ($request) {
return Tenant::where('domain', $request->domain)
->where('api_key', $request->header('X-API-KEY'))
->first();
});
Tenant-Specific Config:
Load tenant-specific config (e.g., config/tenants/{tenant_id}.php):
$tenantConfig = require config_path("tenants/{$tenantId}.php");
How can I help you explore Laravel packages today?