litepie/tenancy

Complete multitenancy solution for Laravel applications

v1.0.0 2025-08-22 04:11 UTC

This package is auto-updated.

Last update: 2025-08-22 04:12:04 UTC


README

Latest Version on Packagist Total Downloads GitHub Tests Action Status GitHub Code Style Action Status License

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

Contributing

We welcome contributions! Here's how you can help:

  1. ๐Ÿ› Report Bugs: Use GitHub Issues with detailed reproduction steps
  2. โœจ Feature Requests: Propose new features in GitHub Discussions
  3. ๐Ÿ“– Documentation: Help improve our documentation
  4. ๐Ÿงช Testing: Add test cases for edge cases
  5. ๐Ÿ’ป 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