定制 litepie/tenancy 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

litepie/tenancy

最新稳定版本:v1.0.1

Composer 安装命令:

composer require litepie/tenancy

包简介

Complete multitenancy solution for Laravel applications

README 文档

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 Lavalite Team

⭐ Star us on GitHub | 🐛 Report Issues | 💬 Join Discussions

统计信息

  • 总下载量: 28
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 0
  • 依赖项目数: 2
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-08-22