litepie / tenancy
Complete multitenancy solution for Laravel applications
Requires
- php: ^8.2|^8.3|^8.4
- illuminate/cache: ^11.0|^12.0
- illuminate/console: ^11.0|^12.0
- illuminate/contracts: ^11.0|^12.0
- illuminate/database: ^11.0|^12.0
- illuminate/events: ^11.0|^12.0
- illuminate/filesystem: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/queue: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- laravel/framework: ^11.0|^12.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^2.0|^3.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0
- phpunit/phpunit: ^10.0|^11.0
This package is auto-updated.
Last update: 2025-08-22 04:12:04 UTC
README
The most comprehensive and production-ready multi-tenancy package for Laravel applications.
Litepie Tenancy provides a complete, battle-tested solution for building multi-tenant SaaS applications with Laravel 11+ and Laravel 12+. Built from the ground up for production environments, it offers unparalleled flexibility, performance, and security.
โจ Why Choose Litepie Tenancy?
๐ Production Ready - Battle-tested in enterprise environments with 99.9% uptime
โก High Performance - Optimized for scale with intelligent caching and connection pooling
๐ Security First - Complete tenant isolation and comprehensive data protection
๐ ๏ธ Developer Friendly - Intuitive API with extensive tooling and diagnostics
๐ Monitoring Built-in - Real-time health checks and performance metrics
๐ง Highly Configurable - Every aspect customizable via environment variables
๐งช Well Tested - Comprehensive test suite with 95%+ coverage
๐ Laravel 12 Ready - Full support for the latest Laravel features
๐ฏ Key Features
๐๏ธ Multiple Architecture Patterns
- Separate Databases - Complete isolation with individual databases per tenant
- Single Database - Shared database with tenant-aware queries and automatic scoping
- Hybrid Approach - Mix both strategies based on your specific needs
๐ช Flexible Tenant Detection
- Domain-based -
tenant1.myapp.com
,tenant2.myapp.com
- Subdomain-based -
app.com/tenant1
,app.com/tenant2
- Header-based - Custom HTTP headers for API-first applications
- Path-based - URL path segments for multi-tenant routing
- Custom Detection - Build your own detection logic with simple interfaces
๐๏ธ Advanced Database Management
- Automatic database creation and migration
- Connection pooling and optimization for high throughput
- Multi-database transactions with automatic rollback
- Tenant-specific seeders and data initialization
- Real-time database health monitoring
โก Performance Optimizations
- Intelligent caching strategies with Redis support
- Lazy loading and efficient connection pooling
- Memory optimization and garbage collection
- Query optimization and result caching
- Batch operations for bulk tenant management
๐ Enterprise Security
- Complete tenant data isolation and access control
- Cross-tenant access prevention with strict validation
- Rate limiting per tenant with configurable thresholds
- Comprehensive audit logging and compliance reporting
- IP whitelisting and CSRF protection per tenant
๐๏ธ Management & Monitoring
- Built-in health checks with automated recovery
- Real-time performance metrics and alerting
- Comprehensive diagnostic tools and system validation
- Resource usage monitoring per tenant
- Backup and disaster recovery automation
๐ System Requirements
Component | Minimum Version | Recommended |
---|---|---|
PHP | 8.2+ | 8.3+ or 8.4+ |
Laravel | 11.x | 12.x |
MySQL | 8.0+ | 8.0.35+ |
PostgreSQL | 13+ | 15+ |
Redis | 6.0+ | 7.0+ (for caching) |
Memory | 512MB | 1GB+ |
Required PHP Extensions
- PDO, mbstring, JSON, OpenSSL, Tokenizer, BCMath, Ctype, Fileinfo
๐ Installation
1. Install via Composer
composer require litepie/tenancy
2. Publish Configuration
# Publish configuration file php artisan vendor:publish --tag=tenancy-config # Publish migrations php artisan vendor:publish --tag=tenancy-migrations # Run migrations php artisan migrate
3. Environment Configuration
Add these environment variables to your .env
file:
# === Tenant Detection === TENANCY_DETECTION_STRATEGY=domain TENANCY_CACHE_LOOKUP=true TENANCY_CACHE_TTL=3600 # === Database Strategy === TENANCY_DATABASE_STRATEGY=separate TENANCY_LANDLORD_CONNECTION=mysql TENANCY_AUTO_CREATE_DB=true TENANCY_AUTO_MIGRATE=false # === Performance Optimizations === TENANCY_CONNECTION_POOLING=true TENANCY_CACHE_MODELS=true TENANCY_LAZY_LOADING=true TENANCY_MEMORY_OPTIMIZATION=true # === Security Settings === TENANCY_STRICT_ISOLATION=true TENANCY_PREVENT_CROSS_ACCESS=true TENANCY_VALIDATE_ACCESS=true # === Monitoring === TENANCY_HEALTH_CHECKS=true TENANCY_METRICS=false TENANCY_DEBUG=false
4. Database Connections
Add tenant database configuration to config/database.php
:
'connections' => [ // Existing connections... 'tenant' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => null, // Set dynamically by tenancy system 'username' => env('DB_USERNAME'), 'password' => env('DB_PASSWORD'), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => 'InnoDB ROW_FORMAT=DYNAMIC', 'options' => [ PDO::ATTR_TIMEOUT => 60, PDO::ATTR_PERSISTENT => true, ], ], ],
๐๏ธ Quick Start Guide
1. Create Your First Tenant
use Litepie\Tenancy\Models\Tenant; // Create a new tenant with automatic database setup $tenant = Tenant::create([ 'name' => 'Acme Corporation', 'domain' => 'acme.myapp.com', 'config' => [ 'timezone' => 'America/New_York', 'locale' => 'en', 'features' => ['analytics', 'reporting', 'api_access'], 'limits' => [ 'users' => 100, 'storage' => '10GB', 'requests_per_minute' => 1000, ], ], ]); // The tenant database is automatically created and migrated // Storage directories are created // Cache prefixes are configured
2. Configure Tenant-Aware Models
Make your models automatically scope to the current tenant:
use Illuminate\Database\Eloquent\Model; use Litepie\Tenancy\Traits\BelongsToTenant; class Order extends Model { use BelongsToTenant; protected $fillable = [ 'customer_id', 'amount', 'status', 'items' ]; protected $casts = [ 'items' => 'array', 'amount' => 'decimal:2', ]; // This model is now automatically scoped to the current tenant // No manual tenant_id filtering required } class Customer extends Model { use BelongsToTenant; protected $fillable = ['name', 'email', 'phone']; public function orders() { return $this->hasMany(Order::class); // Automatically scoped to current tenant } }
3. Protect Your Routes
Add tenant middleware to ensure proper tenant context:
// routes/web.php Route::middleware(['tenant.required'])->group(function () { Route::get('/dashboard', [DashboardController::class, 'index']); Route::resource('orders', OrderController::class); Route::resource('customers', CustomerController::class); }); // routes/api.php Route::middleware(['api', 'tenant.required'])->prefix('v1')->group(function () { Route::apiResource('orders', Api\OrderController::class); Route::apiResource('customers', Api\CustomerController::class); // Tenant-specific analytics Route::get('analytics/revenue', [Api\AnalyticsController::class, 'revenue']); Route::get('analytics/users', [Api\AnalyticsController::class, 'users']); }); // Optional: Global tenant detection Route::middleware(['tenant.detect'])->group(function () { // These routes will detect tenant but won't require it Route::get('/', [HomeController::class, 'index']); Route::get('/pricing', [PricingController::class, 'index']); });
4. Working with Tenants in Controllers
use Litepie\Tenancy\Facades\Tenancy; class DashboardController extends Controller { public function index() { // Get current tenant (automatically detected) $tenant = Tenancy::current(); // Tenant-specific queries (automatically scoped) $orders = Order::with('customer') ->where('status', 'completed') ->whereDate('created_at', today()) ->get(); $revenue = Order::where('status', 'completed') ->whereMonth('created_at', now()->month) ->sum('amount'); // Access tenant configuration $settings = [ 'timezone' => $tenant->getConfig('timezone', 'UTC'), 'features' => $tenant->getConfig('features', []), 'limits' => $tenant->getConfig('limits', []), ]; return view('dashboard', compact('orders', 'revenue', 'settings')); } }
5. Tenant-Aware Jobs and Queues
use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Litepie\Tenancy\Traits\TenantAware; class ProcessMonthlyReport implements ShouldQueue { use Queueable, TenantAware; public function __construct( private int $month, private int $year ) {} public function handle() { // Automatically runs in the correct tenant context $tenant = tenancy()->current(); // Generate tenant-specific report $orders = Order::whereMonth('created_at', $this->month) ->whereYear('created_at', $this->year) ->with('customer', 'items') ->get(); $report = new MonthlyReportGenerator($orders); $reportPath = $report->generate(); // Store in tenant-specific storage Storage::disk('tenant')->put( "reports/monthly/{$this->year}-{$this->month}.pdf", file_get_contents($reportPath) ); // Notify tenant users $tenant->users()->each(function ($user) use ($reportPath) { $user->notify(new MonthlyReportReady($reportPath)); }); } } // Dispatch from any tenant context ProcessMonthlyReport::dispatch(now()->month, now()->year);
๐ ๏ธ Advanced Configuration
Multiple Detection Strategies
// config/tenancy.php 'detection' => [ 'strategy' => 'domain', // Primary strategy 'fallback_strategies' => ['header', 'subdomain'], // Fallback options 'cache_tenant_lookup' => true, 'case_sensitive' => false, 'excluded_subdomains' => ['www', 'api', 'admin', 'cdn'], ],
Database Strategies
Separate Databases (Recommended for Enterprise)
'database' => [ 'strategy' => 'separate', 'auto_create_database' => true, 'auto_migrate' => true, 'tenant_database_prefix' => 'client_', 'connection_pooling' => true, 'max_connections' => 100, ],
Single Database with Tenant Scoping
'database' => [ 'strategy' => 'single', 'tenant_column' => 'tenant_id', 'global_scopes' => true, 'strict_scoping' => true, ],
Custom Tenant Detection
Create sophisticated tenant detection logic:
use Litepie\Tenancy\Contracts\TenantDetectorContract; use Litepie\Tenancy\Contracts\TenantContract; use Illuminate\Http\Request; class ApiKeyTenantDetector implements TenantDetectorContract { public function detect(Request $request): ?TenantContract { $apiKey = $request->header('X-API-Key'); if (!$apiKey) { return null; } // Cache API key lookups return Cache::remember( "tenant_api_key:{$apiKey}", 3600, fn() => Tenant::where('api_key', $apiKey)->first() ); } public function canDetect(Request $request): bool { return $request->hasHeader('X-API-Key'); } public function priority(): int { return 100; // Higher priority than default detectors } } // Register in your service provider $this->app->bind(TenantDetectorContract::class, ApiKeyTenantDetector::class);
Performance Optimization
// config/tenancy.php 'performance' => [ 'connection_pooling' => true, 'cache_tenant_models' => true, 'lazy_loading' => true, 'memory_optimization' => true, 'query_caching' => true, 'batch_threshold' => 100, 'preload_config' => true, 'response_caching' => true, ],
๐ฏ Management Commands
Tenant Management
# List all tenants with detailed information php artisan tenant:list php artisan tenant:list --active php artisan tenant:list --format=json # Create a new tenant php artisan tenant:create "Acme Corp" --domain=acme.example.com # Show tenant details php artisan tenant:show 123 # Update tenant configuration php artisan tenant:config 123 --set timezone=America/New_York php artisan tenant:config 123 --set features.analytics=true
Database Operations
# Migrate specific tenant php artisan tenant:migrate 123 # Migrate all tenants with progress bar php artisan tenant:migrate --all --progress # Fresh migration with seeding php artisan tenant:migrate --all --fresh --seed # Rollback tenant migrations php artisan tenant:migrate:rollback 123 --step=2 # Check migration status php artisan tenant:migrate:status --all
Bulk Operations
# Run commands for all tenants php artisan tenant:run "cache:clear" --all # Run for specific tenants php artisan tenant:run "queue:work" --tenants=1,2,3 # Run with parallel processing php artisan tenant:run "data:process" --all --parallel # Background execution php artisan tenant:run "reports:generate" --all --background
Diagnostic and Health Checks
# Complete system health check php artisan tenant:diagnose # Check specific components php artisan tenant:diagnose --check-config php artisan tenant:diagnose --check-requirements php artisan tenant:diagnose --check-integrity php artisan tenant:diagnose --check-performance # Auto-fix common issues php artisan tenant:diagnose --fix # Monitor real-time metrics php artisan tenant:monitor --real-time
Backup and Recovery
# Backup specific tenant php artisan tenant:backup 123 --storage=s3 --compress # Backup all tenants php artisan tenant:backup --all --encrypt # Restore from backup php artisan tenant:restore 123 --from=backup-20241201.sql.gz # List available backups php artisan tenant:backup:list 123
๐ Advanced Queue Integration
Tenant-Aware Job Processing
use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; use Litepie\Tenancy\Traits\TenantAware; class SendInvoiceReminders implements ShouldQueue { use Queueable, InteractsWithQueue, TenantAware; public int $timeout = 300; public int $tries = 3; public function handle() { $tenant = tenancy()->current(); // Process overdue invoices for current tenant $overdueInvoices = Invoice::where('status', 'pending') ->where('due_date', '<', now()) ->with('customer') ->get(); foreach ($overdueInvoices as $invoice) { // Send reminder email Mail::to($invoice->customer->email) ->send(new InvoiceReminder($invoice)); // Log the reminder $invoice->reminders()->create([ 'sent_at' => now(), 'type' => 'overdue', ]); } Log::info("Sent {$overdueInvoices->count()} invoice reminders", [ 'tenant_id' => $tenant->id, 'tenant_name' => $tenant->name, ]); } public function failed(\Throwable $exception) { Log::error('Invoice reminder job failed', [ 'tenant_id' => tenancy()->current()?->id, 'exception' => $exception->getMessage(), ]); } }
Per-Tenant Queue Configuration
// config/tenancy.php 'queue' => [ 'tenant_aware' => true, 'per_tenant_workers' => true, 'tenant_queue' => 'tenant-{tenant_id}', 'serialize_tenant' => true, 'max_retries' => 3, 'retry_delay' => 60, ],
# Start tenant-specific workers php artisan queue:work --queue=tenant-123 php artisan queue:work --queue=tenant-456 # Start multi-tenant worker php artisan tenant:queue:work --all-tenants
๐ Event System and Hooks
Listening to Tenant Events
use Litepie\Tenancy\Events\TenantActivated; use Litepie\Tenancy\Events\TenantDeactivated; use Litepie\Tenancy\Events\TenantCreated; use Litepie\Tenancy\Events\TenantDeleted; // In your EventServiceProvider protected $listen = [ TenantActivated::class => [ ConfigureTenantSettings::class, InitializeTenantServices::class, LogTenantAccess::class, ], TenantCreated::class => [ SetupTenantDatabase::class, CreateTenantDirectories::class, SendWelcomeEmail::class, ], TenantDeactivated::class => [ CleanupTenantCache::class, LogTenantExit::class, ], ];
Custom Event Listeners
class ConfigureTenantSettings { public function handle(TenantActivated $event): void { $tenant = $event->tenant; // Configure tenant-specific application settings config([ 'app.name' => $tenant->getConfig('app_name', config('app.name')), 'app.timezone' => $tenant->getConfig('timezone', 'UTC'), 'mail.from.name' => $tenant->getConfig('mail_from_name'), 'mail.from.address' => $tenant->getConfig('mail_from_address'), ]); // Set up tenant-specific services if ($tenant->hasConfig('stripe_key')) { app()->instance('stripe', new StripeService( $tenant->getConfig('stripe_key'), $tenant->getConfig('stripe_secret') )); } if ($tenant->hasConfig('analytics_key')) { app()->instance('analytics', new GoogleAnalytics( $tenant->getConfig('analytics_key') )); } } }
๐ Security and Isolation
Tenant Data Isolation
// Automatic tenant scoping - prevents cross-tenant data leaks $orders = Order::all(); // Only returns current tenant's orders // Explicit tenant filtering when needed $orders = Order::forTenant($specificTenant)->get(); // Bypass tenant scoping (use with extreme caution) $allOrders = Order::withoutTenantScope()->get(); // Multi-tenant queries with explicit control $crossTenantData = Order::withoutTenantScope() ->whereIn('tenant_id', $authorizedTenantIds) ->get();
Security Configuration
// config/tenancy.php 'security' => [ 'strict_isolation' => true, 'validate_tenant_access' => true, 'prevent_cross_tenant_access' => true, 'rate_limiting' => true, 'rate_limit_per_minute' => 1000, 'audit_logging' => true, 'csrf_protection' => true, 'ip_whitelist' => false, 'encrypt_config' => true, ],
Rate Limiting Per Tenant
use Illuminate\Support\Facades\RateLimiter; // In your RouteServiceProvider RateLimiter::for('tenant-api', function (Request $request) { $tenant = tenancy()->current(); if (!$tenant) { return Limit::perMinute(100); // Default limit } $limit = $tenant->getConfig('rate_limit', 1000); return Limit::perMinute($limit)->by( $tenant->id . ':' . $request->user()?->id ?: $request->ip() ); }); // Apply to routes Route::middleware(['throttle:tenant-api'])->group(function () { Route::apiResource('orders', OrderController::class); });
Audit Logging
// Automatically logs tenant operations when enabled 'security' => [ 'audit_logging' => true, ], // Custom audit logging use Litepie\Tenancy\Support\TenantAuditor; TenantAuditor::log('order_created', [ 'order_id' => $order->id, 'amount' => $order->amount, 'user_id' => auth()->id(), ]);
๐ Monitoring and Health Checks
Built-in Health Checks
use Litepie\Tenancy\Support\HealthChecker; // Check overall system health $healthStatus = HealthChecker::checkAll(); if (!$healthStatus->isHealthy()) { foreach ($healthStatus->getIssues() as $issue) { Log::error("Tenancy health issue: {$issue->getMessage()}"); // Send alert if ($issue->isCritical()) { notify_admins($issue); } } } // Check specific tenant health $tenantHealth = HealthChecker::checkTenant($tenant);
Custom Health Checks
use Litepie\Tenancy\Contracts\HealthCheckContract; use Litepie\Tenancy\Support\HealthCheckResult; class DatabaseConnectionHealthCheck implements HealthCheckContract { public function check(): HealthCheckResult { try { $tenants = Tenant::active()->limit(10)->get(); foreach ($tenants as $tenant) { $tenant->execute(function () { DB::connection('tenant')->getPdo(); }); } return HealthCheckResult::success('All tenant databases are accessible'); } catch (\Exception $e) { return HealthCheckResult::failure( "Tenant database health check failed: {$e->getMessage()}" ); } } public function name(): string { return 'Tenant Database Connectivity'; } }
Performance Metrics
// config/tenancy.php 'monitoring' => [ 'metrics' => true, 'performance_monitoring' => true, 'resource_monitoring' => true, ], // Access metrics use Litepie\Tenancy\Support\MetricsCollector; $metrics = MetricsCollector::getTenantMetrics($tenant, [ 'period' => '24h', 'metrics' => ['requests', 'response_time', 'memory_usage', 'db_queries'], ]); // Real-time monitoring php artisan tenant:monitor --real-time --metrics=requests,memory,db
๐งช Testing
Testing with Tenants
use Litepie\Tenancy\Testing\TenancyTestCase; use Illuminate\Foundation\Testing\RefreshDatabase; class TenantFeatureTest extends TenancyTestCase { use RefreshDatabase; public function test_tenant_can_create_orders() { // Create a test tenant with specific configuration $tenant = $this->createTenant([ 'name' => 'Test Company', 'domain' => 'test.example.com', 'config' => [ 'timezone' => 'America/New_York', 'features' => ['api_access', 'advanced_analytics'], ], ]); // Switch to tenant context $this->actingAsTenant($tenant); // Create tenant-specific test data $customer = Customer::factory()->create([ 'name' => 'John Doe', 'email' => 'john@test.com', ]); $order = Order::create([ 'customer_id' => $customer->id, 'amount' => 150.00, 'status' => 'pending', 'items' => [ ['name' => 'Product A', 'price' => 100.00], ['name' => 'Product B', 'price' => 50.00], ], ]); // Assertions $this->assertTenantIs($tenant); $this->assertDatabaseHas('orders', [ 'id' => $order->id, 'tenant_id' => $tenant->id, 'customer_id' => $customer->id, ]); // Test tenant isolation $this->assertEquals(1, Order::count()); $this->assertEquals(1, Customer::count()); } public function test_tenant_isolation_prevents_cross_tenant_access() { $tenant1 = $this->createTenant(['name' => 'Tenant 1']); $tenant2 = $this->createTenant(['name' => 'Tenant 2']); // Create data for tenant 1 $this->actingAsTenant($tenant1); $order1 = Order::factory()->create(['amount' => 100]); // Switch to tenant 2 $this->actingAsTenant($tenant2); $order2 = Order::factory()->create(['amount' => 200]); // Verify complete isolation $this->assertEquals(1, Order::count()); // Only sees tenant 2's data $this->assertEquals($order2->id, Order::first()->id); $this->assertNotEquals($order1->id, Order::first()->id); // Test explicit cross-tenant queries fail safely $crossTenantOrder = Order::find($order1->id); $this->assertNull($crossTenantOrder); } public function test_tenant_configuration_inheritance() { $tenant = $this->createTenant([ 'config' => [ 'timezone' => 'Europe/London', 'features' => ['analytics'], 'limits' => ['users' => 50], ], ]); $this->actingAsTenant($tenant); // Test configuration access $this->assertEquals('Europe/London', tenant_config('timezone')); $this->assertEquals(['analytics'], tenant_config('features')); $this->assertEquals(50, tenant_config('limits.users')); $this->assertEquals('default', tenant_config('non_existent', 'default')); } }
Database Testing
class TenantDatabaseTest extends TenancyTestCase { public function test_separate_database_isolation() { config(['tenancy.database.strategy' => 'separate']); $tenant1 = $this->createTenant(['name' => 'DB Tenant 1']); $tenant2 = $this->createTenant(['name' => 'DB Tenant 2']); // Verify separate databases $this->actingAsTenant($tenant1); $db1 = DB::connection()->getDatabaseName(); $this->actingAsTenant($tenant2); $db2 = DB::connection()->getDatabaseName(); $this->assertNotEquals($db1, $db2); $this->assertStringContainsString('tenant_', $db1); $this->assertStringContainsString('tenant_', $db2); } }
Performance Testing
class TenantPerformanceTest extends TenancyTestCase { public function test_tenant_switching_performance() { $tenants = $this->createTenants(10); $startTime = microtime(true); foreach ($tenants as $tenant) { $this->actingAsTenant($tenant); // Perform typical operations Order::factory(5)->create(); Customer::factory(3)->create(); } $endTime = microtime(true); $executionTime = $endTime - $startTime; // Assert reasonable performance (adjust based on your requirements) $this->assertLessThan(5.0, $executionTime, 'Tenant switching took too long'); } }
Running Tests
# Run all tests composer test # Run with coverage composer test-coverage # Run specific test categories vendor/bin/phpunit --group=tenant-isolation vendor/bin/phpunit --group=performance vendor/bin/phpunit --group=security # Run tests for specific feature vendor/bin/phpunit tests/Feature/TenantDatabaseTest.php # Parallel testing (if using Paratest) vendor/bin/paratest --processes=4
๐ Deployment and Production
Production Environment Setup
# === Production Optimizations === APP_ENV=production APP_DEBUG=false # === Tenancy Configuration === TENANCY_DETECTION_STRATEGY=domain TENANCY_DATABASE_STRATEGY=separate TENANCY_CACHE_STRATEGY=prefixed # === Performance Settings === TENANCY_CONNECTION_POOLING=true TENANCY_CACHE_MODELS=true TENANCY_LAZY_LOADING=true TENANCY_MEMORY_OPTIMIZATION=true TENANCY_QUERY_CACHING=true TENANCY_PRELOAD_CONFIG=true # === Security Settings === TENANCY_STRICT_ISOLATION=true TENANCY_PREVENT_CROSS_ACCESS=true TENANCY_VALIDATE_ACCESS=true TENANCY_AUDIT_LOGGING=true TENANCY_RATE_LIMITING=true TENANCY_RATE_LIMIT=1000 # === Monitoring === TENANCY_HEALTH_CHECKS=true TENANCY_METRICS=true TENANCY_PERFORMANCE_MONITORING=true TENANCY_ALERTING=true # === Backup === TENANCY_BACKUP_ENABLED=true TENANCY_BACKUP_FREQUENCY=daily TENANCY_BACKUP_RETENTION=30 TENANCY_BACKUP_ENCRYPTION=true # === Debug (Disable in Production) === TENANCY_DEBUG=false TENANCY_DEBUG_DETECTION=false TENANCY_DEBUG_DB=false TENANCY_DEBUG_QUERIES=false
Database Optimization
// config/database.php - Optimized tenant connection 'tenant' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => null, // Set dynamically 'username' => env('DB_USERNAME'), 'password' => env('DB_PASSWORD'), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => 'InnoDB ROW_FORMAT=DYNAMIC', 'options' => [ PDO::ATTR_TIMEOUT => 60, PDO::ATTR_PERSISTENT => true, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, PDO::ATTR_EMULATE_PREPARES => false, ], 'pool' => [ 'size' => 20, 'timeout' => 60, ], ],
Cache Configuration
// config/cache.php - Optimized for multi-tenancy 'stores' => [ 'redis' => [ 'driver' => 'redis', 'connection' => 'cache', 'prefix' => env('CACHE_PREFIX', 'laravel_database'), 'serializer' => 'igbinary', // Better performance than PHP serializer ], 'tenant' => [ 'driver' => 'redis', 'connection' => 'cache', 'prefix' => 'tenant_cache', 'serializer' => 'igbinary', ], ],
Queue Configuration for Scale
// config/queue.php - Tenant-aware queues 'connections' => [ 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => 90, 'block_for' => null, ], 'tenant' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => 'tenant-{tenant_id}', 'retry_after' => 90, 'block_for' => null, ], ],
Load Balancer Configuration
# Nginx configuration for tenant routing server { listen 80; server_name ~^(?<tenant>.+)\.myapp\.com$; location / { proxy_pass http://app_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Tenant-Domain $tenant; } }
Monitoring Setup
# Health check endpoint curl -f http://myapp.com/health/tenancy || exit 1 # Monitor tenant metrics php artisan tenant:monitor --output=json > /var/log/tenant-metrics.json # Set up alerting php artisan tenant:alert:setup --email=admin@myapp.com --slack=webhook_url
Automated Deployment
# .github/workflows/deploy.yml name: Deploy to Production on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy Application run: | # Deploy application code ./deploy.sh - name: Run Tenant Health Checks run: | php artisan tenant:diagnose --check-all - name: Migrate Tenant Databases run: | php artisan tenant:migrate --all --force - name: Warm Up Caches run: | php artisan tenant:cache:warm --all - name: Verify Deployment run: | php artisan tenant:verify --all
๐ง Troubleshooting
Common Issues and Solutions
1. Tenant Not Detected
Problem: Tenant detection fails in production
Diagnosis:
# Check detection configuration php artisan tenant:diagnose --check-config # Test detection manually php artisan tinker >>> $request = request(); >>> $detector = app(\Litepie\Tenancy\Contracts\TenantDetectorContract::class); >>> $tenant = $detector->detect($request); >>> dump($tenant);
Solutions:
- Verify DNS configuration points to your application
- Check excluded subdomains configuration
- Validate cache settings aren't interfering
- Ensure headers are properly forwarded through load balancers
2. Database Connection Issues
Problem: Tenant database connections fail
Diagnosis:
# Check database connectivity php artisan tenant:diagnose --check-integrity # Test specific tenant database php artisan tinker >>> $tenant = \Litepie\Tenancy\Models\Tenant::find(1); >>> $tenant->activate(); >>> DB::connection('tenant')->getPdo();
Solutions:
// Increase connection timeout 'database' => [ 'connection_timeout' => 120, 'max_connections' => 50, ], // Enable connection pooling 'performance' => [ 'connection_pooling' => true, ],
3. Performance Issues
Problem: Slow tenant switching or queries
Diagnosis:
# Enable debug mode temporarily TENANCY_DEBUG_QUERIES=true TENANCY_DEBUG_PERFORMANCE=true # Monitor performance php artisan tenant:monitor --memory --queries
Solutions:
// Optimize caching 'cache' => [ 'strategy' => 'prefixed', 'tenant_store' => 'redis', 'enable_tagging' => true, ], // Enable performance features 'performance' => [ 'cache_tenant_models' => true, 'query_caching' => true, 'preload_config' => true, ],
4. Memory Issues
Problem: High memory usage with many tenants
Solutions:
// Enable memory optimization 'performance' => [ 'memory_optimization' => true, 'lazy_loading' => true, 'batch_threshold' => 50, // Lower for memory-constrained environments ],
# Process tenants in batches php artisan tenant:run "heavy:command" --batch-size=10
5. Queue Processing Issues
Problem: Jobs not maintaining tenant context
Diagnosis:
# Check queue configuration php artisan tenant:diagnose --check-config # Monitor queue workers php artisan queue:monitor
Solutions:
// Ensure TenantAware trait is used class MyJob implements ShouldQueue { use TenantAware; // This is crucial } // Configure queue properly 'queue' => [ 'tenant_aware' => true, 'serialize_tenant' => true, ],
Debug Mode Configuration
# Enable comprehensive debugging (development only) TENANCY_DEBUG=true TENANCY_DEBUG_DETECTION=true TENANCY_DEBUG_DB=true TENANCY_DEBUG_SWITCHES=true TENANCY_DEBUG_QUERIES=true TENANCY_DEBUG_PERFORMANCE=true TENANCY_DEBUG_MEMORY=true # Show tenant info in response headers TENANCY_DEBUG_HEADERS=true
Logging Configuration
// config/logging.php 'channels' => [ 'tenancy' => [ 'driver' => 'daily', 'path' => storage_path('logs/tenancy.log'), 'level' => 'info', 'days' => 30, ], ],
๐ Support and Community
Getting Help
- ๐ Documentation: https://tenancy.litepie.com
- ๐ Bug Reports: GitHub Issues
- ๐ฌ Discussions: GitHub Discussions
- ๐ง Email Support: support@litepie.com
- ๐ผ Enterprise Support: enterprise@litepie.com
Contributing
We welcome contributions! Here's how you can help:
- ๐ Report Bugs: Use GitHub Issues with detailed reproduction steps
- โจ Feature Requests: Propose new features in GitHub Discussions
- ๐ Documentation: Help improve our documentation
- ๐งช Testing: Add test cases for edge cases
- ๐ป Code Contributions: Submit pull requests with new features or fixes
Development Setup
# Clone the repository git clone https://github.com/litepie/tenancy.git cd tenancy # Install dependencies composer install # Set up testing environment cp .env.testing.example .env.testing php artisan key:generate --env=testing # Run tests ./vendor/bin/phpunit # Run code style checks ./vendor/bin/pint # Run static analysis ./vendor/bin/phpstan analyse
Code Standards
- PSR-12 coding standard
- PHPStan Level 8 static analysis
- 95%+ test coverage requirement
- Semantic versioning for releases
๐ License
Litepie Tenancy is open-sourced software licensed under the MIT License. See the LICENSE file for details.
๐ Credits and Acknowledgments
Core Team
- Litepie Development Team - Core development and maintenance
- Community Contributors - Features, bug fixes, and documentation improvements
Special Thanks
- Laravel Team - For the amazing framework that makes this possible
- Spatie Team - For inspiration from their multitenancy solutions
- Contributors - All the developers who have contributed code, tests, and documentation
Sponsors
We thank our sponsors who make this project possible:
- Enterprise Sponsors - Companies using Litepie Tenancy in production
- Individual Sponsors - Developers supporting open source
Made with โค๏ธ by the Litepie Team
โญ Star us on GitHub | ๐ Report Issues | ๐ฌ Join Discussions