PHP code example of litepie / tenancy
1. Go to this page and download the library: Download litepie/tenancy library . Choose the download type require .
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
litepie / tenancy example snippets
'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,
],
],
],
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
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
// routes/web.php
Route::middleware(['tenant.oller::class, 'index']);
Route::resource('orders', OrderController::class);
Route::resource('customers', CustomerController::class);
});
// routes/api.php
Route::middleware(['api', 'tenant.nue']);
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
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'));
}
}
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);
// 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' => [
'strategy' => 'separate',
'auto_create_database' => true,
'auto_migrate' => true,
'tenant_database_prefix' => 'client_',
'connection_pooling' => true,
'max_connections' => 100,
],
'database' => [
'strategy' => 'single',
'tenant_column' => 'tenant_id',
'global_scopes' => true,
'strict_scoping' => true,
],
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);
// 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,
],
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(),
]);
}
}
// 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,
],
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,
],
];
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')
));
}
}
}
// 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();
// 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,
],
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);
});
// 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(),
]);
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);
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';
}
}
// 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
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' => '[email protected] ',
]);
$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'));
}
}
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);
}
}
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
// 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,
],
],
// 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',
],
],
// 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,
],
],
// Increase connection timeout
'database' => [
'connection_timeout' => 120,
'max_connections' => 50,
],
// Enable connection pooling
'performance' => [
'connection_pooling' => true,
],
// 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,
],
// Enable memory optimization
'performance' => [
'memory_optimization' => true,
'lazy_loading' => true,
'batch_threshold' => 50, // Lower for memory-constrained environments
],
// Ensure TenantAware trait is used
class MyJob implements ShouldQueue
{
use TenantAware; // This is crucial
}
// Configure queue properly
'queue' => [
'tenant_aware' => true,
'serialize_tenant' => true,
],
// config/logging.php
'channels' => [
'tenancy' => [
'driver' => 'daily',
'path' => storage_path('logs/tenancy.log'),
'level' => 'info',
'days' => 30,
],
],
bash
# Publish configuration file
php artisan vendor:publish --tag=tenancy-config
# Publish migrations
php artisan vendor:publish --tag=tenancy-migrations
# Run migrations
php artisan migrate
env
# === 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
bash
# Complete system health check
php artisan tenant:diagnose
# Check specific components
php artisan tenant:diagnose --check-config
php artisan tenant:diagnose --check-time metrics
php artisan tenant:monitor --real-time
nginx
# 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;
}
}
bash
# 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);
bash
# 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();
bash
# Check queue configuration
php artisan tenant:diagnose --check-config
# Monitor queue workers
php artisan queue:monitor