# Laravel Multidomain Laravel Multidomain is a package that enables a single Laravel installation to serve multiple HTTP domains with domain-specific environment files, storage paths, and database configurations. This multi-tenancy solution allows different customers to share the same application codebase while maintaining completely separate configurations, storage folders, and data isolation. The package works by detecting the current HTTP domain and loading the appropriate `.env.{domain}` file and storage directory automatically. It extends Laravel's core Application class and provides artisan commands for managing domains. Both HTTP requests and CLI commands support domain-specific configurations through automatic detection or the `--domain` option. ## Installation ### Add Package to Composer Install the package using Composer to add multi-domain support to your Laravel application. ```bash composer require gecche/laravel-multidomain:13.* ``` ### Replace Laravel Application Class Modify the `bootstrap/app.php` file to use the extended Application class that provides domain detection and management capabilities. ```php withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { // }) ->withExceptions(function (Exceptions $exceptions) { // })->create(); ``` ### Configure QueueServiceProvider Override the QueueServiceProvider in `config/app.php` to enable domain-aware queue processing. ```php \Illuminate\Support\ServiceProvider::defaultProviders()->merge([ // Package Service Providers... ])->replace([ // Replace Laravel's QueueServiceProvider with Multidomain version \Illuminate\Queue\QueueServiceProvider::class => \Gecche\Multidomain\Queue\QueueServiceProvider::class, ])->merge([ // Added Service Providers... ])->toArray(), // ... rest of config ]; ``` ## Artisan Commands ### domain:add - Add a New Domain The `domain:add` command creates a new domain configuration including environment file and storage directories for tenant isolation. ```bash # Add a new domain with default configuration php artisan domain:add site1.com # Add domain with custom environment values php artisan domain:add site2.com --domain_values='{"DB_DATABASE":"site2_db","CACHE_PREFIX":"site2"}' # Add domain with force flag to recreate storage directories php artisan domain:add site1.com --force # Add domain for local development (updates .gitignore) php artisan domain:add dev.local --dev # Expected output: # Added site1.com to the application. # Creates: # - .env.site1.com (environment file) # - storage/site1_com/ (storage directory with app/, framework/, logs/ subdirectories) ``` ### domain:remove - Remove an Existing Domain The `domain:remove` command removes a domain's environment file and optionally its storage directory from the application. ```bash # Remove domain (keeps storage directory) php artisan domain:remove site2.com # Remove domain and delete storage directory php artisan domain:remove site2.com --force # Expected output: # Removed site2.com from the application. # Deletes: # - .env.site2.com (always deleted) # - storage/site2_com/ (only with --force flag) ``` ### domain:list - List Installed Domains The `domain:list` command displays all configured domains with their associated environment files and storage paths. ```bash # List domains as formatted text (default) php artisan domain:list # Expected output: # Domain: site1.com # - Storage dir: /var/www/app/storage/site1_com # - Env file: /var/www/app/.env.site1.com # # Domain: site2.com # - Storage dir: /var/www/app/storage/site2_com # - Env file: /var/www/app/.env.site2.com # List domains as JSON php artisan domain:list --output=json # Expected output: # [{"domain":"site1.com","storage_dir":"/var/www/app/storage/site1_com","env_file":"/var/www/app/.env.site1.com"}] # List domains as table php artisan domain:list --output=table # Expected output: # +------------+----------------------------------+---------------------------+ # | domain | storage_dir | env_file | # +------------+----------------------------------+---------------------------+ # | site1.com | /var/www/app/storage/site1_com | /var/www/app/.env.site1.com | # +------------+----------------------------------+---------------------------+ ``` ### domain:update_env - Update Environment Variables The `domain:update_env` command updates environment variables across one or all domain configuration files. ```bash # Update all domain .env files with new values php artisan domain:update_env --domain_values='{"MAIL_DRIVER":"smtp","MAIL_HOST":"mail.example.com"}' # Update specific domain's .env file only php artisan domain:update_env site1.com --domain_values='{"DB_DATABASE":"site1_production"}' # Expected output: # Updated env domain files # Example: Adding queue configuration to all domains php artisan domain:update_env --domain_values='{"QUEUE_CONNECTION":"redis","REDIS_HOST":"127.0.0.1"}' ``` ### Using --domain Option with Any Artisan Command All Laravel artisan commands accept the `--domain` option to execute in the context of a specific domain. ```bash # Run migrations for a specific domain php artisan migrate --domain=site1.com # Clear cache for a specific domain php artisan cache:clear --domain=site2.com # Run tinker in domain context php artisan tinker --domain=site1.com # Generate config cache for specific domain php artisan config:cache --domain=site2.com # Creates: bootstrap/cache/config-site2_com.php # Run any command with domain context php artisan queue:work --domain=site1.com --queue=default1 php artisan schedule:run --domain=site1.com ``` ## Application API Methods ### app()->domain() - Get Current Domain The `domain()` method returns the current HTTP domain name or checks if the application is running under specific domains. ```php domain(); // Returns: "site1.com" // Check if running under a specific domain if (app()->domain('site1.com')) { // Domain is site1.com } // Check if running under one of multiple domains if (app()->domain('site1.com', 'site2.com')) { // Domain is either site1.com or site2.com } // Use in controllers class TenantController extends Controller { public function dashboard() { $domain = app()->domain(); $tenantConfig = config("tenants.{$domain}"); return view('dashboard', [ 'domain' => $domain, 'config' => $tenantConfig, ]); } } ``` ### app()->fullDomain() - Get Full Domain with Scheme The `fullDomain()` method returns the complete domain including HTTP scheme and port. ```php fullDomain(); // Returns: "https://site1.com:8443" or "http://site1.com" // Check specific full domain if (app()->fullDomain('https://secure.site1.com')) { // Handle secure subdomain } // Access individual components $scheme = app('domain_scheme'); // "https" or "http" $domain = app('domain'); // "site1.com" $port = app('domain_port'); // 443, 80, or custom port ``` ### app()->domainsList() - Get All Configured Domains The `domainsList()` method returns an associative array containing all installed domains with their storage paths and environment files. ```php domainsList(); // Returns: // [ // 'site1.com' => [ // 'storage_path' => '/var/www/app/storage/site1_com', // 'env' => '.env.site1.com' // ], // 'site2.com' => [ // 'storage_path' => '/var/www/app/storage/site2_com', // 'env' => '.env.site2.com' // ] // ] // Iterate over domains for admin dashboard foreach (app()->domainsList() as $domain => $info) { echo "Domain: {$domain}\n"; echo "Storage: {$info['storage_path']}\n"; echo "Env File: {$info['env']}\n"; } ``` ### app()->domainStoragePath() - Get Domain Storage Path The `domainStoragePath()` method returns the storage directory path for the current or specified domain. ```php domainStoragePath(); // Returns: "/var/www/app/storage/site1_com" // Get storage path for specific domain $storagePath = app()->domainStoragePath('site2.com'); // Returns: "/var/www/app/storage/site2_com" // Use storage_path() helper - automatically domain-aware $logPath = storage_path('logs/laravel.log'); // Returns: "/var/www/app/storage/site1_com/logs/laravel.log" // Store domain-specific files $uploadPath = storage_path('app/uploads'); file_put_contents("{$uploadPath}/document.pdf", $content); ``` ## Helper Functions ### domain_sanitized() - Sanitize Domain Name The `domain_sanitized()` function converts dots in domain names to underscores for filesystem-safe naming. ```php domain()) . '.php'; // Returns: "config-site1_com.php" ``` ### domain_root_url() - Get Domain Root URL The `domain_root_url()` function returns the root URL of the current domain with optional path appended. ```php function() { return \Illuminate\Support\Arr::get($_SERVER, 'HTTP_HOST'); } ]; return Application::configure( basePath: dirname(__DIR__), environmentPath: $environmentPath, domainParams: $domainParams ) ->withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { // }) ->withExceptions(function (Exceptions $exceptions) { // })->create(); ``` ### Advanced Detection with Request Headers Implement domain detection based on custom headers or request attributes for proxy setups. ```php function() { // Check X-Forwarded-Host header first (for reverse proxy) $forwardedHost = \Illuminate\Support\Arr::get($_SERVER, 'HTTP_X_FORWARDED_HOST'); if ($forwardedHost) { // Take first host if multiple (comma-separated) return explode(',', $forwardedHost)[0]; } // Fall back to HTTP_HOST $httpHost = \Illuminate\Support\Arr::get($_SERVER, 'HTTP_HOST'); if ($httpHost) { // Remove port if present return explode(':', $httpHost)[0]; } // Final fallback to SERVER_NAME return \Illuminate\Support\Arr::get($_SERVER, 'SERVER_NAME', 'localhost'); } ]; ``` ## Queue Configuration ### Configure Domain-Specific Queues Set up separate queues for each domain to process jobs independently using different queue names. ```php env('QUEUE_CONNECTION', 'sync'), 'connections' => [ 'database' => [ 'driver' => 'database', 'table' => 'jobs', 'queue' => env('QUEUE_DEFAULT', 'default'), 'retry_after' => 90, ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => env('QUEUE_DEFAULT', 'default'), 'retry_after' => 90, ], ], ]; ``` ### Run Domain-Specific Queue Workers Start queue workers for specific domains using the `--domain` option. ```bash # Start worker for site1.com php artisan queue:work --domain=site1.com --queue=site1_queue # Start worker for site2.com php artisan queue:work --domain=site2.com --queue=site2_queue # Run queue listener for specific domain php artisan queue:listen --domain=site1.com --queue=site1_queue # Run with specific connection and domain php artisan queue:work redis --domain=site1.com --queue=site1_queue --tries=3 ``` ## Storage Link Configuration ### Configure Multiple Storage Links Set up domain-specific public storage links for serving files from different tenant storage directories. ```bash # Create symbolic links for each domain's public storage ln -s storage/site1_com/app/public public/storage-site1_com ln -s storage/site2_com/app/public public/storage-site2_com ``` ```php [ 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => env('APP_URL') . '/storage' . env('APP_PUBLIC_STORAGE', ''), 'visibility' => 'public', ], ], ]; // Usage in application $url = Storage::disk('public')->url('uploads/image.jpg'); // Returns: "https://site1.com/storage-site1_com/uploads/image.jpg" ``` ## Environment File Organization ### Store Environment Files in Custom Directory Configure the package to store all domain environment files in a dedicated subdirectory for better organization. ```php withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { // }) ->withExceptions(function (Exceptions $exceptions) { // })->create(); // Directory structure: // /var/www/app/ // ├── envs/ // │ ├── .env (default) // │ ├── .env.site1.com // │ └── .env.site2.com // ├── storage/ // │ ├── site1_com/ // │ └── site2_com/ // └── ... ``` ## Laravel Horizon Integration ### Configure Horizon for Multi-Domain Integrate Laravel Horizon with the multi-domain package for monitoring queue workers across all tenants. ```php