├── src
├── stubs
│ └── config.stub
├── config
│ └── domain.php
├── Foundation
│ ├── Bootstrap
│ │ └── DetectDomain.php
│ ├── Console
│ │ ├── DomainCommand.php
│ │ ├── RemoveDomainCommand.php
│ │ ├── ListDomainCommand.php
│ │ ├── Kernel.php
│ │ ├── UpdateEnvDomainCommand.php
│ │ ├── DomainCommandTrait.php
│ │ └── AddDomainCommand.php
│ ├── Configuration
│ │ └── ApplicationBuilder.php
│ ├── Http
│ │ └── Kernel.php
│ ├── helpers.php
│ ├── Providers
│ │ └── DomainConsoleServiceProvider.php
│ ├── DomainDetector.php
│ └── Application.php
├── Horizon
│ ├── WorkerCommandString.php
│ ├── SupervisorCommandString.php
│ ├── Console
│ │ ├── TimeoutCommand.php
│ │ ├── HorizonCommand.php
│ │ └── SupervisorCommand.php
│ ├── SupervisorProcess.php
│ ├── MasterSupervisorCommands
│ │ └── AddSupervisor.php
│ ├── HorizonApplicationServiceProvider.php
│ ├── QueueCommandString.php
│ ├── ProvisioningPlan.php
│ └── SupervisorOptions.php
├── Console
│ └── Application.php
└── Queue
│ ├── QueueServiceProvider.php
│ ├── Console
│ └── ListenCommand.php
│ ├── ListenerOptions.php
│ └── Listener.php
├── laravel-multidomain.png
├── .gitignore
├── tests
├── app
│ ├── Http
│ │ ├── Middleware
│ │ │ ├── EncryptCookies.php
│ │ │ ├── VerifyCsrfToken.php
│ │ │ ├── TrimStrings.php
│ │ │ ├── CheckForMaintenanceMode.php
│ │ │ └── RedirectIfAuthenticated.php
│ │ └── Kernel.php
│ ├── Console
│ │ ├── Commands
│ │ │ ├── NameCommand.php
│ │ │ └── QueuePushCommand.php
│ │ └── Kernel.php
│ ├── Jobs
│ │ └── AppNameJob.php
│ └── TestServiceProvider.php
├── routes
│ └── web.php
├── work_in_progress
│ ├── ArtisanSubfolderTestCase.php
│ └── ArtisanTestCase.php
├── .env.example
├── resources
│ └── views
│ │ └── welcome.blade.php
├── artisan
├── artisan_sub
├── database
│ └── migrations
│ │ ├── 0001_01_01_000001_create_cache_table.php
│ │ ├── 0001_01_01_000000_create_users_table.php
│ │ └── 0001_01_01_000002_create_jobs_table.php
├── src
│ ├── CommandsSubfolderTestCase.php
│ ├── HttpSubfolderTestCase.php
│ ├── CommandsTestCase.php
│ └── HttpTestCase.php
├── bootstrap
│ └── app.php
├── README.md
└── config
│ ├── queue.php
│ └── app.php
├── LICENSE
├── phpunit.xml.bak
├── phpunit.xml
├── composer.json
├── .phpunit.cache
└── test-results
├── README.md
└── CHANGELOG.md
/src/stubs/config.stub:
--------------------------------------------------------------------------------
1 | '.env',
5 | 'storage_dirs' => [
6 | 'app' => [
7 | 'public' => [
8 |
9 | ],
10 | ],
11 | 'framework' => [
12 | 'cache' => [
13 | ],
14 | 'testing' => [
15 | ],
16 | 'sessions' => [
17 | ],
18 | 'views' => [
19 | ],
20 | ],
21 | 'logs' => [
22 | ],
23 | ],
24 | 'domains' => [
25 | ],
26 | ];
--------------------------------------------------------------------------------
/tests/routes/web.php:
--------------------------------------------------------------------------------
1 | laravelAppPath = __DIR__ . '/../../vendor/orchestra/testbench-core/laravel';
18 | $this->laravelEnvPath = $this->laravelAppPath . DIRECTORY_SEPARATOR . 'envs';
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/src/Foundation/Bootstrap/DetectDomain.php:
--------------------------------------------------------------------------------
1 | detectDomain();
18 |
19 | //Overrides the storage path if the domain stoarge path exists
20 | //$app->useStoragePath($app->domainStoragePath());
21 |
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/Horizon/WorkerCommandString.php:
--------------------------------------------------------------------------------
1 | check()) {
22 | return redirect(RouteServiceProvider::HOME);
23 | }
24 |
25 | return $next($request);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Horizon/Console/TimeoutCommand.php:
--------------------------------------------------------------------------------
1 | option('domain'))->plan;
24 |
25 | $this->line(collect($plan[$this->argument('environment')] ?? [])->max('timeout') ?? 60);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Foundation/Console/DomainCommand.php:
--------------------------------------------------------------------------------
1 | line('Current application domain: '.$this->laravel['domain']. "--" . $this->laravel['domain_port'] . "--" . $this->laravel['domain_scheme'].'');
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/tests/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel
2 | APP_ENV=local
3 | APP_KEY=base64:BTJyFYeDfLauAbFziT1uwpskmFoLKk2eT2Kii5w3ksg=
4 | APP_DEBUG=true
5 | APP_LOG_LEVEL=debug
6 | APP_URL=http://localhost
7 |
8 | DB_CONNECTION=mysql
9 | DB_HOST=127.0.0.1
10 | DB_PORT=3306
11 | DB_DATABASE=homestead
12 | DB_USERNAME=homestead
13 | DB_PASSWORD=secret
14 |
15 | BROADCAST_DRIVER=log
16 | CACHE_DRIVER=file
17 | SESSION_DRIVER=file
18 | SESSION_LIFETIME=120
19 | QUEUE_CONNECTION=database
20 | QUEUE_DEFAULT=default
21 |
22 | REDIS_HOST=127.0.0.1
23 | REDIS_PASSWORD=null
24 | REDIS_PORT=6379
25 |
26 | MAIL_DRIVER=smtp
27 | MAIL_HOST=smtp.mailtrap.io
28 | MAIL_PORT=2525
29 | MAIL_USERNAME=null
30 | MAIL_PASSWORD=null
31 | MAIL_ENCRYPTION=null
32 |
33 | PUSHER_APP_ID=
34 | PUSHER_APP_KEY=
35 | PUSHER_APP_SECRET=
36 | PUSHER_APP_CLUSTER=mt1
37 |
--------------------------------------------------------------------------------
/src/Foundation/Configuration/ApplicationBuilder.php:
--------------------------------------------------------------------------------
1 | app->singleton(
17 | \Illuminate\Contracts\Http\Kernel::class,
18 | \Gecche\Multidomain\Foundation\Http\Kernel::class,
19 | );
20 |
21 | $this->app->singleton(
22 | \Illuminate\Contracts\Console\Kernel::class,
23 | \Gecche\Multidomain\Foundation\Console\Kernel::class,
24 | );
25 |
26 | return $this;
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/tests/resources/views/welcome.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
31 |
32 |
33 |
34 |
35 |
{{env("APP_NAME")}}
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/tests/app/Jobs/AppNameJob.php:
--------------------------------------------------------------------------------
1 | delete($filename);
25 | $files->append($filename,env('APP_NAME') . " --- " . $this->job->getQueue());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | handleCommand(new ArgvInput);
25 |
26 | exit($status);
27 |
--------------------------------------------------------------------------------
/tests/artisan_sub:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | handleCommand(new ArgvInput);
25 |
26 | exit($status);
27 |
--------------------------------------------------------------------------------
/src/Horizon/SupervisorProcess.php:
--------------------------------------------------------------------------------
1 | push(
25 | MasterSupervisor::commandQueue(),
26 | AddSupervisor::class,
27 | $this->options->toArray()
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/app/TestServiceProvider.php:
--------------------------------------------------------------------------------
1 | loadRoutesFrom(__DIR__ . '/../routes/web.php');
29 | $this->loadMigrationsFrom(
30 | __DIR__ . '/../database/migrations'
31 | );
32 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'multidomain');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Console/Application.php:
--------------------------------------------------------------------------------
1 | addOption($this->getDomainOption());
18 |
19 | return $definition;
20 | }
21 |
22 | /**
23 | * Get the global environment option for the definition.
24 | *
25 | * @return \Symfony\Component\Console\Input\InputOption
26 | */
27 | protected function getDomainOption()
28 | {
29 | $message = 'The domain the command should run under.';
30 |
31 | return new InputOption('--domain', null, InputOption::VALUE_OPTIONAL, $message);
32 | }
33 |
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/tests/database/migrations/0001_01_01_000001_create_cache_table.php:
--------------------------------------------------------------------------------
1 | string('key')->primary();
16 | $table->mediumText('value');
17 | $table->integer('expiration');
18 | });
19 |
20 | Schema::create('cache_locks', function (Blueprint $table) {
21 | $table->string('key')->primary();
22 | $table->string('owner');
23 | $table->integer('expiration');
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | */
30 | public function down(): void
31 | {
32 | Schema::dropIfExists('cache');
33 | Schema::dropIfExists('cache_locks');
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/src/Foundation/Http/Kernel.php:
--------------------------------------------------------------------------------
1 | supervisors[] = new SupervisorProcess(
29 | $options, $this->createProcess($master, $options), function ($type, $line) use ($master) {
30 | $master->output($type, $line);
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 gecche
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Queue/QueueServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->singleton('queue.listener', function () {
23 | return new Listener($this->app->basePath());
24 | });
25 | }
26 |
27 | /**
28 | * Extends the queue listen command
29 | *
30 | * @return void
31 | */
32 | public function register()
33 | {
34 | parent::register();
35 |
36 | $this->app->extend(ListenCommand::class, function ($command, $app) {
37 | return new QueueListenCommand($app['queue.listener']);
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/src/CommandsSubfolderTestCase.php:
--------------------------------------------------------------------------------
1 | laravelAppPath = __DIR__ . '/../../vendor/orchestra/testbench-core/laravel';
25 | $this->laravelEnvPath = $this->laravelAppPath . DIRECTORY_SEPARATOR . 'envs';
26 | }
27 |
28 | protected function resolveApplication()
29 | {
30 | return tap(new Application($_ENV['APP_BASE_PATH'],$_ENV['APP_BASE_PATH'].'/'.$this->envPath), function ($app) {
31 | $app->bind(
32 | 'Illuminate\Foundation\Bootstrap\LoadConfiguration',
33 | 'Orchestra\Testbench\Bootstrap\LoadConfiguration'
34 | );
35 | });
36 | }
37 |
38 |
39 | }
--------------------------------------------------------------------------------
/phpunit.xml.bak:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 | src/
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ./tests/src/
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/Queue/Console/ListenCommand.php:
--------------------------------------------------------------------------------
1 | hasOption('backoff')) ? $this->option('backoff') : $this->option('delay'));
23 |
24 | return new ListenerOptions(
25 | name: $this->option('name'),
26 | environment: $this->option('env'),
27 | backoff: $backoff,
28 | memory: $this->option('memory'),
29 | timeout: $this->option('timeout'),
30 | sleep: $this->option('sleep'),
31 | rest: $this->option('rest'),
32 | maxTries: $this->option('tries'),
33 | force: $this->option('force'),
34 | domain: $this->option('domain')
35 | );
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/Horizon/HorizonApplicationServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->bind(BaseSupervisorCommand::class, SupervisorCommand::class);
30 | $this->app->bind(BaseHorizonCommand::class, HorizonCommand::class);
31 | $this->app->bind(BaseTimeoutCommand::class, TimeoutCommand::class);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Queue/ListenerOptions.php:
--------------------------------------------------------------------------------
1 | domain = $domain;
39 |
40 | parent::__construct($name, $environment, $backoff, $memory, $timeout, $sleep, $maxTries, $force, $rest);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | command('inspire')
32 | // ->hourly();
33 | }
34 |
35 | /**
36 | * Register the commands for the application.
37 | *
38 | * @return void
39 | */
40 | protected function commands()
41 | {
42 | $this->load(__DIR__.'/Commands');
43 |
44 | //require base_path('routes/console.php');
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ./tests/src/
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ./tests/src/
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/Horizon/QueueCommandString.php:
--------------------------------------------------------------------------------
1 | backoff, $options->maxTime, $options->maxJobs, $options->memory,
26 | $options->queue, $options->sleep, $options->timeout, $options->maxTries,
27 | (($options instanceof SupervisorOptions) ? $options->domain : 'localhost')
28 | );
29 |
30 | if ($options->force) {
31 | $string .= ' --force';
32 | }
33 |
34 | if ($paused) {
35 | $string .= ' --paused';
36 | }
37 |
38 | return $string;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Horizon/Console/HorizonCommand.php:
--------------------------------------------------------------------------------
1 | find(MasterSupervisor::name())) {
26 | return $this->comment('A master supervisor is already running on this machine.');
27 | }
28 |
29 | $master = (new MasterSupervisor)->handleOutputUsing(function ($type, $line) {
30 | $this->output->write($line);
31 | });
32 |
33 | ProvisioningPlan::get(MasterSupervisor::name(), $this->option('domain'))->deploy(
34 | $this->option('environment') ?? config('horizon.env') ?? config('app.env')
35 | );
36 |
37 | $this->info('Horizon started successfully.');
38 |
39 | pcntl_async_signals(true);
40 |
41 | pcntl_signal(SIGINT, function () use ($master) {
42 | $this->line('Shutting down...');
43 |
44 | return $master->terminate();
45 | });
46 |
47 | $master->monitor();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Foundation/helpers.php:
--------------------------------------------------------------------------------
1 | environmentPath().($path ? DIRECTORY_SEPARATOR.$path : $path);
56 | }
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/Horizon/Console/SupervisorCommand.php:
--------------------------------------------------------------------------------
1 | hasOption('backoff')
23 | ? $this->option('backoff')
24 | : $this->option('delay');
25 |
26 | return new SupervisorOptions(
27 | $this->argument('name'),
28 | $this->argument('connection'),
29 | $this->getQueue($this->argument('connection')),
30 | $this->option('workers-name'),
31 | $this->option('balance'),
32 | $backoff,
33 | $this->option('max-time'),
34 | $this->option('max-jobs'),
35 | $this->option('max-processes'),
36 | $this->option('min-processes'),
37 | $this->option('memory'),
38 | $this->option('timeout'),
39 | $this->option('sleep'),
40 | $this->option('tries'),
41 | $this->option('force'),
42 | $this->option('nice'),
43 | $this->option('balance-cooldown'),
44 | $this->option('balance-max-shift'),
45 | $this->option('parent-id'),
46 | $this->option('domain')
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gecche/laravel-multidomain",
3 | "description": "Laravel App on a subdomains, multi-tenancy setting",
4 | "keywords": ["laravel", "subdomains", "multitenancy", "multi-tenants", "multidomain"],
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Giacomo Terreni",
9 | "email": "giacomo.terreni@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "laravel/framework": "^12.0"
14 | },
15 | "require-dev": {
16 | "mockery/mockery": "^1.6",
17 | "phpunit/phpunit": "^11.5",
18 | "orchestra/testbench": "^10.0",
19 | "orchestra/testbench-browser-kit": "^10.0",
20 | "codedungeon/phpunit-result-printer": "^0.1.0"
21 | },
22 | "autoload": {
23 | "classmap": [
24 |
25 | ],
26 | "psr-4": {
27 | "Gecche\\Multidomain\\": "src/"
28 | },
29 | "files": [
30 | "src/Foundation/helpers.php"
31 | ]
32 | },
33 | "autoload-dev": {
34 | "psr-4": {
35 | "Gecche\\Multidomain\\Tests\\": "tests/src",
36 | "Gecche\\Multidomain\\Tests\\App\\": "tests/app"
37 | }
38 | },
39 | "scripts": {
40 | "test": "phpunit"
41 | },
42 | "extra": {
43 | "laravel": {
44 | "providers": [
45 | "Gecche\\Multidomain\\Foundation\\Providers\\DomainConsoleServiceProvider"
46 | ]
47 | }
48 | },
49 | "config": {
50 | "allow-plugins": {
51 | "kylekatarnls/update-helper": true
52 | }
53 | },
54 | "minimum-stability": "dev",
55 | "prefer-stable" : true
56 | }
57 |
--------------------------------------------------------------------------------
/tests/database/migrations/0001_01_01_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('name');
17 | $table->string('email')->unique();
18 | $table->timestamp('email_verified_at')->nullable();
19 | $table->string('password');
20 | $table->rememberToken();
21 | $table->timestamps();
22 | });
23 |
24 | Schema::create('password_reset_tokens', function (Blueprint $table) {
25 | $table->string('email')->primary();
26 | $table->string('token');
27 | $table->timestamp('created_at')->nullable();
28 | });
29 |
30 | Schema::create('sessions', function (Blueprint $table) {
31 | $table->string('id')->primary();
32 | $table->foreignId('user_id')->nullable()->index();
33 | $table->string('ip_address', 45)->nullable();
34 | $table->text('user_agent')->nullable();
35 | $table->longText('payload');
36 | $table->integer('last_activity')->index();
37 | });
38 | }
39 |
40 | /**
41 | * Reverse the migrations.
42 | */
43 | public function down(): void
44 | {
45 | Schema::dropIfExists('users');
46 | Schema::dropIfExists('password_reset_tokens');
47 | Schema::dropIfExists('sessions');
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/.phpunit.cache/test-results:
--------------------------------------------------------------------------------
1 | {"version":1,"defects":[],"times":{"Gecche\\Multidomain\\Tests\\CommandsSubfolderTestCase::testDomainCommand":0.025,"Gecche\\Multidomain\\Tests\\CommandsSubfolderTestCase::testDomainAddCommand":0.019,"Gecche\\Multidomain\\Tests\\CommandsSubfolderTestCase::testDomainRemoveCommand":0.032,"Gecche\\Multidomain\\Tests\\CommandsSubfolderTestCase::testDomainRemoveForceCommand":0.026,"Gecche\\Multidomain\\Tests\\CommandsSubfolderTestCase::testDomainRemoveForceCommandSubsite":0.06,"Gecche\\Multidomain\\Tests\\CommandsSubfolderTestCase::testDomainUpdateEnvCommand":0.017,"Gecche\\Multidomain\\Tests\\CommandsSubfolderTestCase::testDomainListCommand":0.027,"Gecche\\Multidomain\\Tests\\CommandsTestCase::testDomainCommand":0.039,"Gecche\\Multidomain\\Tests\\CommandsTestCase::testDomainAddCommand":0.026,"Gecche\\Multidomain\\Tests\\CommandsTestCase::testDomainRemoveCommand":0.021,"Gecche\\Multidomain\\Tests\\CommandsTestCase::testDomainRemoveForceCommand":0.033,"Gecche\\Multidomain\\Tests\\CommandsTestCase::testDomainRemoveForceCommandSubsite":0.061,"Gecche\\Multidomain\\Tests\\CommandsTestCase::testDomainUpdateEnvCommand":0.026,"Gecche\\Multidomain\\Tests\\CommandsTestCase::testDomainListCommand":0.026,"Gecche\\Multidomain\\Tests\\HttpSubfolderTestCase::testWelcomePage":0.122,"Gecche\\Multidomain\\Tests\\HttpSubfolderTestCase::testDBConnection":0.108,"Gecche\\Multidomain\\Tests\\HttpSubfolderTestCase::testEnvFile":0.086,"Gecche\\Multidomain\\Tests\\HttpSubfolderTestCase::testStorageFolder":0.09,"Gecche\\Multidomain\\Tests\\HttpTestCase::testWelcomePage":0.149,"Gecche\\Multidomain\\Tests\\HttpTestCase::testDBConnection":0.132,"Gecche\\Multidomain\\Tests\\HttpTestCase::testEnvFile":0.093,"Gecche\\Multidomain\\Tests\\HttpTestCase::testStorageFolder":0.122}}
--------------------------------------------------------------------------------
/tests/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | $hasEnvironmentFile, 'extra' => $config->getExtraAttributes()],
28 | resolvingCallback: static function ($app) use ($config) {
29 | Workbench::startWithProviders($app, $config);
30 | Workbench::discoverRoutes($app, $config);
31 | },
32 | );
33 | };
34 |
35 | if (! defined('TESTBENCH_WORKING_PATH') && ! is_null(Env::get('TESTBENCH_WORKING_PATH'))) {
36 | define('TESTBENCH_WORKING_PATH', Env::get('TESTBENCH_WORKING_PATH'));
37 | }
38 |
39 | $app = $createApp(realpath(join_paths(__DIR__, '..')));
40 |
41 | unset($createApp);
42 |
43 | /** @var \Illuminate\Routing\Router $router */
44 | $router = $app->make('router');
45 |
46 | collect(glob(join_paths(__DIR__, '..', 'routes', 'testbench-*.php')))
47 | ->each(static function ($routeFile) use ($app, $router) {
48 | require $routeFile;
49 | });
50 |
51 | return $app;
52 |
--------------------------------------------------------------------------------
/src/Queue/Listener.php:
--------------------------------------------------------------------------------
1 | createCommand(
26 | $connection,
27 | $queue,
28 | $options
29 | );
30 |
31 | if ($options instanceof ListenerOptions && $options->domain !== null) {
32 | $command = $this->addDomain($command, $options);
33 | }
34 |
35 | // If the environment is set, we will append it to the command string so the
36 | // workers will run under the specified environment. Otherwise, they will
37 | // just run under the production environment which is not always right.
38 | if (isset($options->environment)) {
39 | $command = $this->addEnvironment($command, $options);
40 | }
41 |
42 | return new Process(
43 | $command, $this->commandPath, null, null, $options->timeout
44 | );
45 | }
46 |
47 | /**
48 | * Add the domain option to the given command.
49 | *
50 | * @param array $command
51 | * @param ListenerOptions $options
52 | * @return array
53 | */
54 | protected function addDomain(array $command, ListenerOptions $options) : array
55 | {
56 | return array_merge($command, ["--domain={$options->domain}"]);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/database/migrations/0001_01_01_000002_create_jobs_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('queue')->index();
17 | $table->longText('payload');
18 | $table->unsignedTinyInteger('attempts');
19 | $table->unsignedInteger('reserved_at')->nullable();
20 | $table->unsignedInteger('available_at');
21 | $table->unsignedInteger('created_at');
22 | });
23 |
24 | Schema::create('job_batches', function (Blueprint $table) {
25 | $table->string('id')->primary();
26 | $table->string('name');
27 | $table->integer('total_jobs');
28 | $table->integer('pending_jobs');
29 | $table->integer('failed_jobs');
30 | $table->longText('failed_job_ids');
31 | $table->mediumText('options')->nullable();
32 | $table->integer('cancelled_at')->nullable();
33 | $table->integer('created_at');
34 | $table->integer('finished_at')->nullable();
35 | });
36 |
37 | Schema::create('failed_jobs', function (Blueprint $table) {
38 | $table->id();
39 | $table->string('uuid')->unique();
40 | $table->text('connection');
41 | $table->text('queue');
42 | $table->longText('payload');
43 | $table->longText('exception');
44 | $table->timestamp('failed_at')->useCurrent();
45 | });
46 | }
47 |
48 | /**
49 | * Reverse the migrations.
50 | */
51 | public function down(): void
52 | {
53 | Schema::dropIfExists('jobs');
54 | Schema::dropIfExists('job_batches');
55 | Schema::dropIfExists('failed_jobs');
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/src/Horizon/ProvisioningPlan.php:
--------------------------------------------------------------------------------
1 | $domain] + $supervisor : $supervisor);
31 | }, $group) : $group);
32 | }, $environments) : $environments);
33 |
34 | return new static($master, $environments, config('horizon.defaults', []));
35 | }
36 |
37 | /**
38 | * Add a supervisor with the given options.
39 | *
40 | * @param BaseSupervisorOptions $options
41 | * @return void
42 | */
43 | protected function add(BaseSupervisorOptions $options)
44 | {
45 | app(HorizonCommandQueue::class)->push(
46 | MasterSupervisor::commandQueueFor($this->master),
47 | AddSupervisor::class,
48 | $options->toArray()
49 | );
50 | }
51 |
52 | /**
53 | * Convert the given array of options into a SupervisorOptions instance.
54 | *
55 | * @param string $supervisor
56 | * @param array $options
57 | * @return SupervisorOptions
58 | */
59 | protected function convert($supervisor, $options)
60 | {
61 | return SupervisorOptions::fromArray(['domain' => $options['domain']] + parent::convert($supervisor, $options)->toArray());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/README.md:
--------------------------------------------------------------------------------
1 |
2 | # laravel-multidomain (tests' notes)
3 | We provide some information about laravel-multidomain's tests.
4 |
5 | ## Organization
6 | We provide 3 test suites:
7 |
8 | 1. `CommandsTestCase` where we include a test for each of the commands
9 | provided by the `DomainConsoleServiceProvider` provider.
10 |
11 | 2. `HttpTestCase` where we include some tests for simulating HTTP requests
12 | changing the domain name. We provide two distinct settings (other than the default one)
13 | for two additional domains, namely `site1.test` and `site2.test`.
14 |
15 | 3. `ArtisanTestCase` where we include some tests for simulating the `--domain`
16 | option applied to Artisan commands.
17 |
18 | ## Requirements and setup
19 | We make use of the fantastic [Orchestra/Testbench](https://github.com/orchestral/testbench)
20 | package together with its browser-kit extension.
21 |
22 | However, in order to setup the Laravel application with the machinery provided
23 | by our package, we provide a standard `.env.example` file and some configuration files
24 | to be used by the simulated Orchestra's Laravel application.
25 |
26 | Moreover we created an adapted copy of the Laravel's `artisan` script in order to
27 | use the `--domain` option in the Artisan commands launched by shell.
28 |
29 | Note that, as pointed out in the package documentation, the `--domain` option does
30 | not work from within a Laravel application, so we needed to simulate command launches
31 | directly from shell.
32 |
33 | We perform above operations in the `setUp` sections of the suites.
34 |
35 | We provide tests under the `mysql` connection (set in `phpunit.xml`).
36 | You need to create two databases, namely (`site1` and `site2`) in your DB server
37 | in order to run tests correctly.
38 |
39 | ## Running tests
40 | As usual, we run tests from the package folder launching:
41 |
42 | ```bash
43 | ../../../vendor/bin/phpunit
44 | ```
45 |
46 | However, as we need to simulate more than one domain via `$_SERVER['SERVER_NAME']` values
47 | and as in the `phpunit.xml` file we can't put a conditional environmentt variable,
48 | in order to fullyperform all the tests, we require to launch also the following
49 | commands:
50 |
51 | ```bash
52 | SERVER_NAME=site1.test ../../../vendor/bin/phpunit
53 | ```
54 |
55 | ```bash
56 | SERVER_NAME=site2.test ../../../vendor/bin/phpunit
57 | ```
58 |
--------------------------------------------------------------------------------
/tests/app/Http/Kernel.php:
--------------------------------------------------------------------------------
1 | [
36 | EncryptCookies::class,
37 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
38 | \Illuminate\Session\Middleware\StartSession::class,
39 | // \Illuminate\Session\Middleware\AuthenticateSession::class,
40 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
41 | VerifyCsrfToken::class,
42 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
43 | ],
44 |
45 | 'api' => [
46 | 'throttle:60,1',
47 | 'bindings',
48 | ],
49 | ];
50 |
51 | /**
52 | * The application's route middleware.
53 | *
54 | * These middleware may be assigned to groups or used individually.
55 | *
56 | * @var array
57 | */
58 | protected $routeMiddleware = [
59 | 'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
60 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
61 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
62 | 'can' => \Illuminate\Auth\Middleware\Authorize::class,
63 | 'guest' => RedirectIfAuthenticated::class,
64 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
65 | ];
66 | }
67 |
--------------------------------------------------------------------------------
/src/Foundation/Console/RemoveDomainCommand.php:
--------------------------------------------------------------------------------
1 | domain = $this->argument('domain');
28 |
29 | /*
30 | * CREATE ENV FILE FOR DOMAIN
31 | */
32 | $this->deleteDomainEnvFile();
33 |
34 |
35 | /*
36 | * Setting domain storage directories
37 | */
38 |
39 | if ($this->option('force')) {
40 | $this->deleteDomainStorageDirs();
41 | }
42 |
43 | /*
44 | * Update config file
45 | */
46 |
47 | $this->updateConfigFile('remove');
48 |
49 | $this->line("Removed " . $this->domain . " from the application.");
50 | }
51 |
52 |
53 | /**
54 | * Get the stub file for the generator.
55 | *
56 | * @return string
57 | */
58 | protected function getStub()
59 | {
60 |
61 | }
62 |
63 | protected function deleteDomainEnvFile()
64 | {
65 | $domainEnvFilePath = $this->getDomainEnvFilePath();
66 | if ($this->files->exists($domainEnvFilePath)) {
67 | $this->files->delete($domainEnvFilePath);
68 | }
69 | }
70 |
71 | public function deleteDomainStorageDirs() {
72 | $path = $this->getDomainStoragePath($this->domain);
73 | if ($this->files->exists($path)) {
74 | $this->files->deleteDirectory($path);
75 | }
76 | }
77 |
78 | protected function removeDomainToConfigFile($config) {
79 | $domains = Arr::get($config, 'domains', []);
80 | if (array_key_exists($this->domain, $domains)) {
81 | unset($domains[$this->domain]);
82 | }
83 | $config['domains'] = $domains;
84 | return $config;
85 | }
86 |
87 |
88 |
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/Foundation/Console/ListDomainCommand.php:
--------------------------------------------------------------------------------
1 | configFile . '.php');
32 |
33 | $config = include $filename;
34 |
35 | /*
36 | * GET DOMAINS BASED ON domains KEY IN THE CONFIG FILE
37 | */
38 | $domains = Arr::get($config, 'domains', []);
39 |
40 |
41 | /*
42 | * Simply returns the info for each domain found in config.
43 | */
44 | $outputType = $this->option('output');
45 | $domains = $this->buildResult($domains);
46 | switch (strtolower(trim($outputType ?? 'txt'))) {
47 | default:
48 | case 'txt':
49 | $this->outputAsText($domains);
50 | break;
51 | case 'table':
52 | $this->outputAsTable($domains);
53 | break;
54 | case 'json':
55 | $this->outputAsJson($domains);
56 | break;
57 | }
58 | }
59 |
60 | protected function outputAsText(array $domains)
61 | {
62 | foreach ($domains as $domain) {
63 | $this->line("Domain: " . Arr::get($domain,'domain') . "");
64 |
65 | $this->line(" - Storage dir: " . Arr::get($domain,'storage_dir') . "");
66 | $this->line(" - Env file: " . Arr::get($domain,'env_file') . "");
67 |
68 | $this->line("");
69 |
70 | }
71 | }
72 |
73 | protected function outputAsJson(array $domains)
74 | {
75 | $this->output->writeln(json_encode($domains));
76 | }
77 |
78 | protected function outputAsTable(array $domains)
79 | {
80 | $this->output->table(array_keys(head($domains)), $domains);
81 | }
82 |
83 | protected function buildResult(array $domains): array
84 | {
85 | $result = [];
86 | foreach ($domains as $domain) {
87 | $result [] = [
88 | 'domain' => $domain,
89 | 'storage_dir' => $this->getDomainStoragePath($domain),
90 | 'env_file' => $this->getDomainEnvFilePath($domain),
91 | ];
92 | }
93 |
94 | return $result;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/tests/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_CONNECTION', 'sync'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Queue Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure the connection information for each server that
24 | | is used by your application. A default configuration has been added
25 | | for each back-end shipped with Laravel. You are free to add more.
26 | |
27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'sync' => [
34 | 'driver' => 'sync',
35 | ],
36 |
37 | 'database' => [
38 | 'driver' => 'database',
39 | 'table' => 'jobs',
40 | 'queue' => env('QUEUE_DEFAULT'),
41 | 'retry_after' => 90,
42 | ],
43 |
44 | 'beanstalkd' => [
45 | 'driver' => 'beanstalkd',
46 | 'host' => 'localhost',
47 | 'queue' => 'default',
48 | 'retry_after' => 90,
49 | 'block_for' => 0,
50 | ],
51 |
52 | 'sqs' => [
53 | 'driver' => 'sqs',
54 | 'key' => env('AWS_ACCESS_KEY_ID'),
55 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
56 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
57 | 'queue' => env('SQS_QUEUE', 'your-queue-name'),
58 | 'suffix' => env('SQS_SUFFIX'),
59 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
60 | ],
61 |
62 | 'redis' => [
63 | 'driver' => 'redis',
64 | 'connection' => 'default',
65 | 'queue' => env('REDIS_QUEUE', 'default'),
66 | 'retry_after' => 90,
67 | 'block_for' => null,
68 | ],
69 |
70 | ],
71 |
72 | /*
73 | |--------------------------------------------------------------------------
74 | | Failed Queue Jobs
75 | |--------------------------------------------------------------------------
76 | |
77 | | These options configure the behavior of failed queue job logging so you
78 | | can control which database and table are used to store the jobs that
79 | | have failed. You may change them to any database / table you wish.
80 | |
81 | */
82 |
83 | 'failed' => [
84 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
85 | 'database' => env('DB_CONNECTION', 'mysql'),
86 | 'table' => 'failed_jobs',
87 | ],
88 |
89 | ];
90 |
--------------------------------------------------------------------------------
/src/Foundation/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | artisan))
36 | {
37 | $this->artisan = (new Artisan($this->app, $this->events, $this->app->version()))
38 | ->resolveCommands($this->commands)
39 | ->setContainerCommandLoader();
40 |
41 | if ($this->symfonyDispatcher instanceof EventDispatcher) {
42 | $this->artisan->setDispatcher($this->symfonyDispatcher);
43 | }
44 | }
45 |
46 | return $this->artisan;
47 | }
48 |
49 |
50 | /**
51 | * Run an Artisan console command by name.
52 | *
53 | * @param string $command
54 | * @param array $parameters
55 | * @param \Symfony\Component\Console\Output\OutputInterface $outputBuffer
56 | * @return int
57 | */
58 | public function call($command, array $parameters = [], $outputBuffer = null, $forceBootstrap = false)
59 | {
60 | if ($forceBootstrap) {
61 | $argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : [];
62 | $argvDomain = Arr::first($argv, function ($value) {
63 | return Str::startsWith($value, '--domain');
64 | });
65 | if (!$argvDomain) {
66 | $paramDomain = Arr::get($parameters,'--domain');
67 | if ($paramDomain) {
68 | $_SERVER['argv'][] = $paramDomain;
69 | }
70 | }
71 | $this->app->bootstrapWith($this->bootstrappers());
72 | }
73 |
74 | $this->bootstrap();
75 |
76 | return $this->getArtisan()->call($command, $parameters, $outputBuffer);
77 | }
78 |
79 | protected function shouldDiscoverCommands()
80 | {
81 | if (get_class($this) === __CLASS__) {
82 | return true;
83 | }
84 | return parent::shouldDiscoverCommands(); // TODO: Change the autogenerated stub
85 | }
86 |
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/Foundation/Console/UpdateEnvDomainCommand.php:
--------------------------------------------------------------------------------
1 | domain = $this->argument('domain');
33 |
34 | $this->envFiles = $this->getDomainEnvFiles();
35 |
36 | /*
37 | * CREATE ENV FILE FOR DOMAIN
38 | */
39 | $this->updateDomainEnvFiles();
40 |
41 | $this->line("Updated env domain files");
42 | }
43 |
44 |
45 | protected function getDomainEnvFiles()
46 | {
47 |
48 | if ($this->domain) {
49 | if ($this->files->exists($this->getDomainEnvFilePath())) {
50 | return [$this->getDomainEnvFilePath()];
51 | }
52 | }
53 |
54 | $envFiles = [
55 | env_path('.env'),
56 | ];
57 | $domainList = Config::get('domain.domains',[]);
58 |
59 |
60 | foreach ($domainList as $domain) {
61 | $domainFile = $this->getDomainEnvFilePath($domain);
62 | if ($this->files->exists($domainFile)) {
63 | $envFiles[] = $domainFile;
64 | }
65 | }
66 |
67 | return $envFiles;
68 |
69 | }
70 |
71 |
72 | protected function updateDomainEnvFiles()
73 | {
74 | $domainValues = json_decode($this->option("domain_values"), true);
75 | // $this->line("".var_dump($this->option("domain_values"))."");
76 | // $this->line("".var_dump($domainValues)."");
77 |
78 | if (!is_array($domainValues)) {
79 | $domainValues = array();
80 | }
81 |
82 |
83 |
84 |
85 |
86 |
87 | foreach ($this->envFiles as $envFilePath) {
88 | $envArray = $this->getVarsArray($envFilePath);
89 |
90 | $domainEnvArray = array_merge($envArray, $domainValues);
91 | $domainEnvFileContents = $this->makeDomainEnvFileContents($domainEnvArray);
92 |
93 | $this->files->put($envFilePath, $domainEnvFileContents);
94 | }
95 | }
96 |
97 | /**
98 | * Get the stub file for the generator.
99 | *
100 | * @return string
101 | */
102 | protected function getStub()
103 | {
104 |
105 | }
106 |
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/src/Foundation/Providers/DomainConsoleServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->alias('artisan',\Gecche\Multidomain\Console\Application::class);
38 |
39 |
40 | foreach ($this->commands as $command)
41 | {
42 | $this->{"register{$command}Command"}();
43 | }
44 |
45 | $this->commands(
46 | "command.domain",
47 | "command.domain.add",
48 | "command.domain.remove",
49 | "command.domain.update_env",
50 | "command.domain.list"
51 | );
52 |
53 | }
54 |
55 |
56 | public function boot() {
57 | $this->publishes([
58 | __DIR__.'/../../config/domain.php' => config_path('domain.php'),
59 | ]);
60 | }
61 |
62 |
63 | /**
64 | * Register the command.
65 | *
66 | * @return void
67 | */
68 | protected function registerDomainCommand()
69 | {
70 | $this->app->singleton('command.domain', function()
71 | {
72 | return new DomainCommand;
73 | });
74 | }
75 |
76 | /**
77 | * Register the command.
78 | *
79 | * @return void
80 | */
81 | protected function registerAddDomainCommand()
82 | {
83 | $this->app->singleton('command.domain.add', function($app)
84 | {
85 | return new AddDomainCommand($app['files']);
86 | });
87 | }
88 |
89 | /**
90 | * Register the command.
91 | *
92 | * @return void
93 | */
94 | protected function registerRemoveDomainCommand()
95 | {
96 | $this->app->singleton('command.domain.remove', function($app)
97 | {
98 | return new RemoveDomainCommand($app['files']);
99 | });
100 | }
101 |
102 | /**
103 | * Register the command.
104 | *
105 | * @return void
106 | */
107 | protected function registerUpdateEnvDomainCommand()
108 | {
109 | $this->app->singleton('command.domain.update_env', function($app)
110 | {
111 | return new UpdateEnvDomainCommand($app['files']);
112 | });
113 | }
114 |
115 | /**
116 | * Register the command.
117 | *
118 | * @return void
119 | */
120 | protected function registerListDomainCommand()
121 | {
122 | $this->app->singleton('command.domain.list', function($app)
123 | {
124 | return new ListDomainCommand($app['files']);
125 | });
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Foundation/DomainDetector.php:
--------------------------------------------------------------------------------
1 | domainDetectionFunctionWeb = $domainDetectionFunctionWeb;
24 |
25 | }
26 |
27 |
28 | /**
29 | * Detect the application's current environment.
30 | *
31 | * @param array|string $environments
32 | * @param array|null $consoleArgs
33 | * @return string
34 | */
35 | public function detect($consoleArgs = null)
36 | {
37 | if ($consoleArgs)
38 | {
39 | return $this->detectConsoleDomain($consoleArgs);
40 | }
41 | else
42 | {
43 | return $this->detectWebDomain();
44 | }
45 | }
46 |
47 | /**
48 | * Set the application environment for a web request.
49 | *
50 | * @param array|string $environments
51 | * @return string
52 | */
53 | protected function detectWebDomain()
54 | {
55 | if ($this->domainDetectionFunctionWeb instanceof Closure) {
56 | return ($this->domainDetectionFunctionWeb)();
57 | }
58 | //return filter_input(INPUT_SERVER,'SERVER_NAME');
59 | return Arr::get($_SERVER,'SERVER_NAME');
60 |
61 | }
62 |
63 | /**
64 | * Set the application environment from command-line arguments.
65 | *
66 | * @param mixed $environments
67 | * @param array $args
68 | * @return string
69 | */
70 | protected function detectConsoleDomain(array $args)
71 | {
72 | // First we will check if an environment argument was passed via console arguments
73 | // and if it was that automatically overrides as the environment. Otherwise, we
74 | // will check the environment as a "web" request like a typical HTTP request.
75 | if ( ! is_null($value = $this->getDomainArgument($args)))
76 | {
77 | return head(array_slice(explode('=', $value), 1));
78 | }
79 | else
80 | {
81 | return $this->detectWebDomain();
82 | }
83 | }
84 |
85 | /**
86 | * Get the environment argument from the console.
87 | *
88 | * @param array $args
89 | * @return string|null
90 | */
91 | protected function getDomainArgument(array $args)
92 | {
93 | return Arr::first($args, function ($value) {
94 | return Str::startsWith($value, '--domain');
95 | });
96 | }
97 |
98 | /*
99 | * Split the domain name into scheme, name and port
100 | */
101 | public function split($domain) {
102 | $domain = $domain ?? '';
103 |
104 | if (Str::startsWith($domain,'https://')) {
105 | $scheme = 'https';
106 | $domain = substr($domain,8);
107 | } elseif (Str::startsWith($domain,'http://')) {
108 | $scheme = 'http';
109 | $domain = substr($domain,7);
110 | } else {
111 | $scheme = 'http';
112 | }
113 |
114 | $semicolon = strpos($domain,':');
115 | if ($semicolon === false) {
116 | $port = ($scheme == 'http') ? 80 : 443;
117 | } else {
118 | $port = substr($domain,$semicolon+1);
119 | $domain = substr($domain,0,-(strlen($port)+1));
120 | }
121 |
122 | return array($scheme,$domain,$port);
123 |
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/Horizon/SupervisorOptions.php:
--------------------------------------------------------------------------------
1 | domain = $domain;
66 | }
67 |
68 | /**
69 | * Get the command-line representation of the options for a supervisor.
70 | *
71 | * @return string
72 | */
73 | public function toSupervisorCommand()
74 | {
75 | return SupervisorCommandString::fromOptions($this);
76 | }
77 |
78 | /**
79 | * Get the command-line representation of the options for a worker.
80 | *
81 | * @return string
82 | */
83 | public function toWorkerCommand()
84 | {
85 | return WorkerCommandString::fromOptions($this);
86 | }
87 |
88 | /**
89 | * Convert the options to a raw array.
90 | *
91 | * @return array
92 | */
93 | public function toArray()
94 | {
95 | return [
96 | 'balance' => $this->balance,
97 | 'connection' => $this->connection,
98 | 'queue' => $this->queue,
99 | 'backoff' => $this->backoff,
100 | 'force' => $this->force,
101 | 'maxProcesses' => $this->maxProcesses,
102 | 'minProcesses' => $this->minProcesses,
103 | 'maxTries' => $this->maxTries,
104 | 'maxTime' => $this->maxTime,
105 | 'maxJobs' => $this->maxJobs,
106 | 'memory' => $this->memory,
107 | 'nice' => $this->nice,
108 | 'name' => $this->name,
109 | 'workersName' => $this->workersName,
110 | 'sleep' => $this->sleep,
111 | 'timeout' => $this->timeout,
112 | 'balanceCooldown' => $this->balanceCooldown,
113 | 'balanceMaxShift' => $this->balanceMaxShift,
114 | 'domain' => $this->domain
115 | ];
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Foundation/Console/DomainCommandTrait.php:
--------------------------------------------------------------------------------
1 | domain;
26 | }
27 |
28 | return rtrim(env_path('.env.' . $domain),'.');
29 | }
30 |
31 | /**
32 | * Returns the path of the storage folder for the specified domain
33 | * @param null $domain
34 | * @return string
35 | */
36 | protected function getDomainStoragePath($domain = null)
37 | {
38 | $path = app()->exactDomainStoragePath($domain);
39 | return $path;
40 | }
41 |
42 | /**
43 | * Returns the contents of the stub of the package's configuration file
44 | * @return mixed
45 | */
46 | protected function getConfigStub()
47 | {
48 | $filename = base_path('stubs/domain/config.stub');
49 |
50 | if (!$this->files->exists($filename)) {
51 | $filename = __DIR__ . '/../../stubs/config.stub';
52 | }
53 |
54 | return $this->files->get($filename);
55 | }
56 |
57 | /**
58 | * This method updates the package's config file by adding or removing the domain handled by the caller command.
59 | * It calls either the addDomainToConfigFile or the removeDomainToConfigFile method of the caller.
60 | *
61 | * @param string $opType (add|remove)
62 | */
63 | protected function updateConfigFile($opType = 'add')
64 | {
65 | $filename = base_path('config/' . $this->configFile . '.php');
66 |
67 | $config = include $filename;
68 |
69 | $configStub = $this->getConfigStub();
70 |
71 | $methodName = $opType . 'DomainToConfigFile';
72 |
73 | $finalConfig = call_user_func_array([$this, $methodName], [$config]);
74 |
75 | $modelConfigStub = str_replace(
76 | '{{$configArray}}', var_export($finalConfig, true), $configStub
77 | );
78 |
79 | $modelConfigStub = str_replace(
80 | 'return array (', 'return [', $modelConfigStub
81 | );
82 | $modelConfigStub = str_replace(
83 | ');', ' ];', $modelConfigStub
84 | );
85 | $modelConfigStub = str_replace(
86 | ["\narray (", "\n array (", "\n array (", "\n array ("], '[', $modelConfigStub
87 | );
88 | $modelConfigStub = str_replace(
89 | ["),"], "],", $modelConfigStub
90 | );
91 |
92 | $this->files->put($filename, $modelConfigStub);
93 | Config::set($this->configFile, $finalConfig);
94 | }
95 |
96 | /**
97 | * This method gets the contents of a file formatted as a standard .env file
98 | * i.e. with each line in the form of KEY=VALUE
99 | * and returns the entries as an array
100 | *
101 | * @param $path
102 | * @return array
103 | */
104 | protected function getVarsArray($path)
105 | {
106 | $envFileContents = $this->files->get($path);
107 | $envFileContentsArray = explode("\n", $envFileContents);
108 | $varsArray = array();
109 | foreach ($envFileContentsArray as $line) {
110 | $lineArray = explode('=', $line);
111 |
112 | //Skip the line if there is no '='
113 | if (count($lineArray) < 2) {
114 | continue;
115 | }
116 |
117 | $value = substr($line, strlen($lineArray[0])+1);
118 | $varsArray[$lineArray[0]] = trim($value);
119 |
120 | }
121 | return $varsArray;
122 | }
123 |
124 | /**
125 | * This method prepares the values of an .env file to be stored
126 | * @param $domainValues
127 | * @return string
128 | */
129 | protected function makeDomainEnvFileContents($domainValues)
130 | {
131 | $contents = '';
132 | $previousKeyPrefix = '';
133 | foreach ($domainValues as $key => $value) {
134 | $keyPrefix = current(explode('_', $key));
135 | if ($keyPrefix !== $previousKeyPrefix && !empty($contents)) {
136 | $contents .= "\n";
137 | }
138 | $contents .= $key . '=' . $value . "\n";
139 | $previousKeyPrefix = $keyPrefix;
140 | }
141 | return $contents;
142 | }
143 |
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/tests/src/HttpSubfolderTestCase.php:
--------------------------------------------------------------------------------
1 | laravelAppPath = __DIR__ . '/../../vendor/orchestra/testbench-core/laravel';
70 | copy(__DIR__ . '/../artisan_sub',$this->laravelAppPath.'/artisan_sub');
71 | $process = new Process(['php', $this->laravelAppPath.'/artisan_sub', 'config:clear']);
72 | $process->run();
73 |
74 |
75 | $this->files = new Filesystem();
76 | copy($this->laravelAppPath . '/config/app.php', $this->laravelAppPath . '/config/appORIG.php');
77 | copy(__DIR__ . '/../config/app.php', $this->laravelAppPath . '/config/app.php');
78 | if (!is_dir($this->laravelAppPath.'/'.$this->envPath)) {
79 | mkdir($this->laravelAppPath.'/'.$this->envPath);
80 | }
81 | copy(__DIR__ . '/../.env.example', $this->laravelAppPath.'/'.$this->envPath.'/.env');
82 |
83 | $process = new Process(['php', $this->laravelAppPath.'/artisan_sub', 'vendor:publish', '--provider="Gecche\Multidomain\Foundation\Providers\DomainConsoleServiceProvider"']);
84 | $process->run();
85 |
86 | $process = new Process(['php', $this->laravelAppPath.'/artisan_sub', 'domain:remove', $this->site1, '--force']);
87 | $process->run();
88 | $process = new Process(['php', $this->laravelAppPath.'/artisan_sub', 'domain:remove', $this->site2, '--force']);
89 | $process->run();
90 | $process = new Process(['php', $this->laravelAppPath.'/artisan_sub', 'domain:remove', $this->subSite1, '--force']);
91 | $process->run();
92 |
93 | $process = new Process(['php', $this->laravelAppPath.'/artisan_sub', 'domain:add', $this->site1]);
94 | $process->run();
95 | $process = new Process(['php', $this->laravelAppPath.'/artisan_sub', 'domain:add', $this->site2]);
96 | $process->run();
97 | $process = new Process(['php', $this->laravelAppPath.'/artisan_sub', 'domain:add', $this->subSite1]);
98 | $process->run();
99 |
100 | $domainValues = [
101 | 'APP_NAME' => $this->siteAppName1,
102 | 'DB_DATABASE' => $this->siteDbName1,
103 | ];
104 | $process = new Process(['php', $this->laravelAppPath.'/artisan_sub', 'domain:update_env', $this->site1, '--domain_values='.json_encode($domainValues)]);
105 | $process->run();
106 |
107 | $domainValues = [
108 | 'APP_NAME' => $this->siteAppName2,
109 | 'DB_DATABASE' => $this->siteDbName2,
110 | ];
111 | $process = new Process(['php', $this->laravelAppPath.'/artisan_sub', 'domain:update_env', $this->site2, '--domain_values='.json_encode($domainValues)]);
112 | $process->run();
113 |
114 | $domainValues = [
115 | 'APP_NAME' => $this->subSiteAppName1,
116 | 'DB_DATABASE' => $this->subSiteDbName1,
117 | ];
118 | $process = new Process(['php', $this->laravelAppPath.'/artisan_sub', 'domain:update_env', $this->subSite1, '--domain_values='.json_encode($domainValues)]);
119 | $process->run();
120 |
121 | TestCase::setUp();
122 |
123 |
124 | }
125 |
126 |
127 | protected function resolveApplication()
128 | {
129 | return tap(new Application($_ENV['APP_BASE_PATH'], $_ENV['APP_BASE_PATH'].'/'.$this->envPath), function ($app) {
130 | $app->bind(
131 | 'Illuminate\Foundation\Bootstrap\LoadConfiguration',
132 | 'Orchestra\Testbench\Bootstrap\LoadConfiguration'
133 | );
134 | });
135 | }
136 |
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/src/Foundation/Console/AddDomainCommand.php:
--------------------------------------------------------------------------------
1 | domain = $this->argument('domain');
34 |
35 | /*
36 | * CREATE ENV FILE FOR DOMAIN
37 | */
38 | $this->createDomainEnvFile();
39 |
40 |
41 | /*
42 | * Setting domain storage directories
43 | */
44 |
45 | $this->createDomainStorageDirs();
46 |
47 | /*
48 | * Update config file
49 | */
50 | $this->updateConfigFile();
51 |
52 | /**
53 | * Configuring .gitignore file according local requirements
54 | */
55 | if ($this->option('dev')) {
56 | $this->setupGitIgnore();
57 | }
58 |
59 | $this->line("Added " . $this->domain . " to the application." . ($this->option('dev') ? ' (In dev mode)' : ''));
60 | }
61 |
62 |
63 | /**
64 | * Get the stub file for the generator.
65 | *
66 | * @return string
67 | */
68 | protected function getStub()
69 | {
70 | if ($this->files->exists($this->getDomainEnvFilePath())) {
71 | return $this->getDomainEnvFilePath();
72 | }
73 | return env_path(Config::get('domain.env_stub', '.env'));
74 | }
75 |
76 | protected function createDomainEnvFile()
77 | {
78 | $envFilePath = $this->getStub();
79 |
80 | $domainValues = json_decode($this->option("domain_values"), true);
81 |
82 |
83 | if (!is_array($domainValues)) {
84 | $domainValues = array();
85 | }
86 |
87 |
88 | $envArray = $this->getVarsArray($envFilePath);
89 |
90 | $domainEnvFilePath = $this->getDomainEnvFilePath();
91 |
92 | $domainEnvArray = array_merge($envArray, $domainValues);
93 | $domainEnvFileContents = $this->makeDomainEnvFileContents($domainEnvArray);
94 |
95 | $this->files->put($domainEnvFilePath, $domainEnvFileContents);
96 | }
97 |
98 | public function createDomainStorageDirs()
99 | {
100 | $storageDirs = Config::get('domain.storage_dirs', array());
101 | $path = $this->getDomainStoragePath($this->domain);
102 | $rootPath = storage_path();
103 | if ($this->files->exists($path) && !$this->option('force')) {
104 | return;
105 | }
106 |
107 | if ($this->files->exists($path)) {
108 | $this->files->deleteDirectory($path);
109 | }
110 |
111 |
112 | $this->createRecursiveDomainStorageDirs($rootPath, $path, $storageDirs);
113 |
114 |
115 | }
116 |
117 | protected function createRecursiveDomainStorageDirs($rootPath, $path, $dirs)
118 | {
119 | $this->files->makeDirectory($path, 0755, true);
120 | foreach (['.gitignore', '.gitkeep'] as $gitFile) {
121 | $rootGitPath = $rootPath . DIRECTORY_SEPARATOR . $gitFile;
122 | if ($this->files->exists($rootGitPath)) {
123 | $gitPath = $path . DIRECTORY_SEPARATOR . $gitFile;
124 | $this->files->copy($rootGitPath, $gitPath);
125 | }
126 | }
127 | foreach ($dirs as $subdir => $subsubdirs) {
128 | $fullPath = $path . DIRECTORY_SEPARATOR . $subdir;
129 | $fullRootPath = $rootPath . DIRECTORY_SEPARATOR . $subdir;
130 | $this->createRecursiveDomainStorageDirs($fullRootPath, $fullPath, $subsubdirs);
131 | }
132 |
133 | }
134 |
135 | protected function addDomainToConfigFile($config) {
136 | $domains = Arr::get($config, 'domains', []);
137 | if (!array_key_exists($this->domain, $domains)) {
138 | $domains[$this->domain] = $this->domain;
139 | }
140 |
141 | ksort($domains);
142 | $config['domains'] = $domains;
143 |
144 | return $config;
145 | }
146 |
147 | protected function setupGitIgnore()
148 | {
149 | $toplevelDomain = array_reverse(explode('.', $this->domain))[0];
150 |
151 | $finds = [
152 | 'domains' => Config::get('domain.env_stub', '.env') . ".*.{$toplevelDomain}",
153 | 'storage' => DIRECTORY_SEPARATOR . "storage" . DIRECTORY_SEPARATOR . "*_{$toplevelDomain}",
154 | ];
155 |
156 | foreach(file(base_path('.gitignore'), FILE_SKIP_EMPTY_LINES) as $line) {
157 | foreach ($finds as $key => $value) {
158 | if (strpos($line, $value) === 0) {
159 | $finds[$key] = null;
160 | }
161 | }
162 | }
163 |
164 | $contents = implode("\n", $finds);
165 |
166 | if (empty(trim($contents))) {
167 | return;
168 | }
169 |
170 | $fh = fopen(base_path('.gitignore'), 'a');
171 |
172 | fwrite($fh, "\n# Files and directories for local development\n");
173 | fwrite($fh, $contents);
174 |
175 | fclose($fh);
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/tests/config/app.php:
--------------------------------------------------------------------------------
1 | env('APP_NAME', 'Laravel'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Application Environment
21 | |--------------------------------------------------------------------------
22 | |
23 | | This value determines the "environment" your application is currently
24 | | running in. This may determine how you prefer to configure various
25 | | services the application utilizes. Set this in your ".env" file.
26 | |
27 | */
28 |
29 | 'env' => env('APP_ENV', 'testing'),
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Application Debug Mode
34 | |--------------------------------------------------------------------------
35 | |
36 | | When your application is in debug mode, detailed error messages with
37 | | stack traces will be shown on every error that occurs within your
38 | | application. If disabled, a simple generic error page is shown.
39 | |
40 | */
41 |
42 | 'debug' => (bool) env('APP_DEBUG', false),
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | Application URL
47 | |--------------------------------------------------------------------------
48 | |
49 | | This URL is used by the console to properly generate URLs when using
50 | | the Artisan command line tool. You should set this to the root of
51 | | your application so that it is used when running Artisan tasks.
52 | |
53 | */
54 |
55 | 'url' => env('APP_URL', 'http://localhost'),
56 |
57 | 'asset_url' => env('ASSET_URL', null),
58 |
59 | /*
60 | |--------------------------------------------------------------------------
61 | | Application Timezone
62 | |--------------------------------------------------------------------------
63 | |
64 | | Here you may specify the default timezone for your application, which
65 | | will be used by the PHP date and date-time functions. We have gone
66 | | ahead and set this to a sensible default for you out of the box.
67 | |
68 | */
69 |
70 | 'timezone' => 'UTC',
71 |
72 | /*
73 | |--------------------------------------------------------------------------
74 | | Application Locale Configuration
75 | |--------------------------------------------------------------------------
76 | |
77 | | The application locale determines the default locale that will be used
78 | | by the translation service provider. You are free to set this value
79 | | to any of the locales which will be supported by the application.
80 | |
81 | */
82 |
83 | 'locale' => 'en',
84 |
85 | /*
86 | |--------------------------------------------------------------------------
87 | | Application Fallback Locale
88 | |--------------------------------------------------------------------------
89 | |
90 | | The fallback locale determines the locale to use when the current one
91 | | is not available. You may change the value to correspond to any of
92 | | the language folders that are provided through your application.
93 | |
94 | */
95 |
96 | 'fallback_locale' => 'en',
97 |
98 | /*
99 | |--------------------------------------------------------------------------
100 | | Faker Locale
101 | |--------------------------------------------------------------------------
102 | |
103 | | This locale will be used by the Faker PHP library when generating fake
104 | | data for your database seeds. For example, this will be used to get
105 | | localized telephone numbers, street address information and more.
106 | |
107 | */
108 |
109 | 'faker_locale' => 'en_US',
110 |
111 | /*
112 | |--------------------------------------------------------------------------
113 | | Encryption Key
114 | |--------------------------------------------------------------------------
115 | |
116 | | This key is used by the Illuminate encrypter service and should be set
117 | | to a random, 32 character string, otherwise these encrypted strings
118 | | will not be safe. Please do this before deploying an application!
119 | |
120 | */
121 |
122 | 'key' => env('APP_KEY'),
123 |
124 | 'cipher' => 'AES-256-CBC',
125 |
126 | /*
127 | |--------------------------------------------------------------------------
128 | | Logging Configuration
129 | |--------------------------------------------------------------------------
130 | |
131 | | Here you may configure the log settings for your application. Out of
132 | | the box, Laravel uses the Monolog PHP logging library. This gives
133 | | you a variety of powerful log handlers / formatters to utilize.
134 | |
135 | | Available Settings: "single", "daily", "syslog"
136 | |
137 | */
138 |
139 | 'log' => 'daily',
140 |
141 | 'log_level' => env('APP_LOG_LEVEL', 'debug'),
142 |
143 | /*
144 | |--------------------------------------------------------------------------
145 | | Autoloaded Service Providers
146 | |--------------------------------------------------------------------------
147 | |
148 | | The service providers listed here will be automatically loaded on the
149 | | request to your application. Feel free to add your own services to
150 | | this array to grant expanded functionality to your applications.
151 | |
152 | */
153 |
154 | 'providers' => [
155 |
156 | /*
157 | * Laravel Framework Service Providers...
158 | */
159 | Illuminate\Auth\AuthServiceProvider::class,
160 | Illuminate\Broadcasting\BroadcastServiceProvider::class,
161 | Illuminate\Bus\BusServiceProvider::class,
162 | Illuminate\Cache\CacheServiceProvider::class,
163 | Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
164 | Illuminate\Cookie\CookieServiceProvider::class,
165 | Illuminate\Database\DatabaseServiceProvider::class,
166 | Illuminate\Encryption\EncryptionServiceProvider::class,
167 | Illuminate\Filesystem\FilesystemServiceProvider::class,
168 | Illuminate\Foundation\Providers\FoundationServiceProvider::class,
169 | Illuminate\Hashing\HashServiceProvider::class,
170 | Illuminate\Mail\MailServiceProvider::class,
171 | Illuminate\Database\MigrationServiceProvider::class,
172 | Illuminate\Notifications\NotificationServiceProvider::class,
173 | Illuminate\Pagination\PaginationServiceProvider::class,
174 | Illuminate\Pipeline\PipelineServiceProvider::class,
175 | Gecche\Multidomain\Queue\QueueServiceProvider::class,
176 | Illuminate\Redis\RedisServiceProvider::class,
177 | Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
178 | Illuminate\Session\SessionServiceProvider::class,
179 | Illuminate\Translation\TranslationServiceProvider::class,
180 | Illuminate\Validation\ValidationServiceProvider::class,
181 | Illuminate\View\ViewServiceProvider::class,
182 |
183 | ],
184 |
185 | /*
186 | |--------------------------------------------------------------------------
187 | | Class Aliases
188 | |--------------------------------------------------------------------------
189 | |
190 | | This array of class aliases will be registered when this application
191 | | is started. However, feel free to register as many as you wish as
192 | | the aliases are "lazy" loaded so they don't hinder performance.
193 | |
194 | */
195 |
196 | 'aliases' => [
197 |
198 | 'App' => Illuminate\Support\Facades\App::class,
199 | 'Arr' => Illuminate\Support\Arr::class,
200 | 'Artisan' => Illuminate\Support\Facades\Artisan::class,
201 | 'Auth' => Illuminate\Support\Facades\Auth::class,
202 | 'Blade' => Illuminate\Support\Facades\Blade::class,
203 | 'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
204 | 'Bus' => Illuminate\Support\Facades\Bus::class,
205 | 'Cache' => Illuminate\Support\Facades\Cache::class,
206 | 'Config' => Illuminate\Support\Facades\Config::class,
207 | 'Cookie' => Illuminate\Support\Facades\Cookie::class,
208 | 'Crypt' => Illuminate\Support\Facades\Crypt::class,
209 | 'DB' => Illuminate\Support\Facades\DB::class,
210 | 'Eloquent' => Illuminate\Database\Eloquent\Model::class,
211 | 'Event' => Illuminate\Support\Facades\Event::class,
212 | 'File' => Illuminate\Support\Facades\File::class,
213 | 'Gate' => Illuminate\Support\Facades\Gate::class,
214 | 'Hash' => Illuminate\Support\Facades\Hash::class,
215 | 'Http' => Illuminate\Support\Facades\Http::class,
216 | 'Lang' => Illuminate\Support\Facades\Lang::class,
217 | 'Log' => Illuminate\Support\Facades\Log::class,
218 | 'Mail' => Illuminate\Support\Facades\Mail::class,
219 | 'Notification' => Illuminate\Support\Facades\Notification::class,
220 | 'Password' => Illuminate\Support\Facades\Password::class,
221 | 'Queue' => Illuminate\Support\Facades\Queue::class,
222 | 'Redirect' => Illuminate\Support\Facades\Redirect::class,
223 | 'RedisManager' => Illuminate\Support\Facades\Redis::class,
224 | 'Request' => Illuminate\Support\Facades\Request::class,
225 | 'Response' => Illuminate\Support\Facades\Response::class,
226 | 'Route' => Illuminate\Support\Facades\Route::class,
227 | 'Schema' => Illuminate\Support\Facades\Schema::class,
228 | 'Session' => Illuminate\Support\Facades\Session::class,
229 | 'Storage' => Illuminate\Support\Facades\Storage::class,
230 | 'Str' => Illuminate\Support\Str::class,
231 | 'URL' => Illuminate\Support\Facades\URL::class,
232 | 'Validator' => Illuminate\Support\Facades\Validator::class,
233 | 'View' => Illuminate\Support\Facades\View::class,
234 |
235 | ],
236 |
237 | ];
238 |
--------------------------------------------------------------------------------
/src/Foundation/Application.php:
--------------------------------------------------------------------------------
1 | domainParams = $domainParams;
52 | $environmentPath = $environmentPath ?? $basePath;
53 | $this->useEnvironmentPath(rtrim($environmentPath,'\/'));
54 |
55 | parent::__construct($basePath);
56 | }
57 |
58 | /**
59 | * Begin configuring a new Laravel application instance.
60 | *
61 | * @param string|null $basePath
62 | * @param string|null $environmentPath
63 | * @param array $domainParams
64 | * @return \Gecche\Multidomain\Foundation\Configuration\ApplicationBuilder
65 | */
66 | public static function configure(?string $basePath = null, ?string $environmentPath = null, array $domainParams = [])
67 | {
68 | $basePath = match (true) {
69 | is_string($basePath) => $basePath,
70 | default => static::inferBasePath(),
71 | };
72 |
73 | return (new Configuration\ApplicationBuilder(new static($basePath, $environmentPath, $domainParams)))
74 | ->withKernels()
75 | ->withEvents()
76 | ->withCommands()
77 | ->withProviders();
78 | }
79 | /**
80 | * Detect the application's current domain.
81 | *
82 | * @param array|string $envs
83 | * @return void;
84 | */
85 | public function detectDomain()
86 | {
87 |
88 | $args = isset($_SERVER['argv']) ? $_SERVER['argv'] : null;
89 |
90 | $domainDetectionFunctionWeb = Arr::get($this->domainParams,'domain_detection_function_web');
91 | $domainDetector = new DomainDetector($domainDetectionFunctionWeb);
92 | $fullDomain = $domainDetector->detect($args);
93 | list($domain_scheme, $domain_name, $domain_port) = $domainDetector->split($fullDomain);
94 | $this['full_domain'] = $fullDomain;
95 | $this['domain'] = $domain_name;
96 | $this['domain_scheme'] = $domain_scheme;
97 | $this['domain_port'] = $domain_port;
98 |
99 | $this->domainDetected = true;
100 | return;
101 | }
102 |
103 |
104 | /**
105 | * Force the detection of the domain if it has never been detected.
106 | * It should not happens in standard flow.
107 | *
108 | * @return void;
109 | */
110 | protected function checkDomainDetection()
111 | {
112 | if (!$this->domainDetected)
113 | $this->detectDomain();
114 | return;
115 | }
116 |
117 | /**
118 | * Get or check the current application domain.
119 | *
120 | * @return string
121 | */
122 | public function domain()
123 | {
124 |
125 | $this->checkDomainDetection();
126 |
127 | if (count(func_get_args()) > 0) {
128 | return in_array($this['domain'], func_get_args());
129 | }
130 |
131 | return $this['domain'];
132 | }
133 |
134 | /**
135 | * Get or check the full current application domain with HTTP scheme and port.
136 | *
137 | * @param mixed
138 | * @return string
139 | */
140 | public function fullDomain()
141 | {
142 | $this->checkDomainDetection();
143 |
144 | if (count(func_get_args()) > 0) {
145 | return in_array($this['full_domain'], func_get_args());
146 | }
147 |
148 | return $this['full_domain'];
149 | }
150 |
151 | /**
152 | * Get the environment file the application is using.
153 | *
154 | * @return string
155 | */
156 | public function environmentFile()
157 | {
158 | return $this->environmentFile ?: $this->environmentFileDomain();
159 | }
160 |
161 | /**
162 | * Get the environment file of the current domain if it exists.
163 | * The file has to be named .env.
164 | * It returns the base .env file if a specific file does not exist.
165 | *
166 | * @return string
167 | */
168 | public function environmentFileDomain($domain = null)
169 | {
170 | $this->checkDomainDetection();
171 |
172 | $domain = $domain ?? $this['domain'] ?? '';
173 |
174 | $envFile = $this->searchForEnvFileDomain(explode('.',$domain));
175 |
176 | return $envFile;
177 |
178 | }
179 |
180 | protected function searchForEnvFileDomain($tokens = []) {
181 | $tokens = array_filter($tokens);
182 | if (count($tokens) == 0) {
183 | return '.env';
184 | }
185 |
186 | $file = '.env.' . implode('.',$tokens);
187 | return file_exists(env_path($file))
188 | ? $file
189 | : $this->searchForEnvFileDomain(array_splice($tokens,1));
190 | }
191 |
192 | /**
193 | * Get the path to the storage directory of the current domain.
194 | * The storage path is a folder in the main storage laravel folder
195 | * with the sanitized domain name (dots are replaced with underscores)
196 | * It is sanitized in order to avoid problems with dots in paths especially
197 | * in the case of using array_dot notation.
198 | *
199 | * @return string
200 | */
201 | public function domainStoragePath($domain = null)
202 | {
203 |
204 | $this->checkDomainDetection();
205 |
206 | if (is_null($domain)) {
207 | $domain = $this['domain'];
208 | }
209 |
210 | $domainStoragePath = $this->searchForDomainStoragePath(parent::storagePath(),explode('.',$domain));
211 |
212 | $this->domainStoragePath = $domainStoragePath;
213 |
214 | return $domainStoragePath;
215 |
216 | }
217 |
218 |
219 | /*
220 | * Returns the exact storage path based on the domain (useful for package commands, could not exists)
221 | *
222 | * @return string
223 | */
224 | public function exactDomainStoragePath($domain = null)
225 | {
226 | $this->checkDomainDetection();
227 |
228 | if (is_null($domain)) {
229 | $domain = $this['domain'];
230 | }
231 |
232 | return rtrim(parent::storagePath() . DIRECTORY_SEPARATOR . domain_sanitized($domain),DIRECTORY_SEPARATOR);
233 | }
234 |
235 | /*
236 | * Laravel storagePath updated with domains.
237 | *
238 | * @return string
239 | */
240 | public function storagePath($path = '', $domain = null)
241 | {
242 |
243 | $storagePath = $this->storagePath ?: ($this->domainStoragePath ?: $this->domainStoragePath($domain));
244 |
245 | return $storagePath.($path != '' ? DIRECTORY_SEPARATOR.$path : '');
246 | }
247 |
248 |
249 | protected function searchForDomainStoragePath($storagePath, $tokens = []) {
250 | if (count($tokens) == 0) {
251 | return $storagePath;
252 | }
253 |
254 | $tokensAsDomainString = implode('.',$tokens);
255 |
256 | $domainStoragePath = rtrim($storagePath . DIRECTORY_SEPARATOR . domain_sanitized($tokensAsDomainString), "\/");
257 | return file_exists($domainStoragePath)
258 | ? $domainStoragePath
259 | : $this->searchForDomainStoragePath($storagePath,array_splice($tokens,1));
260 | }
261 |
262 |
263 | /**
264 | * Get the path to the configuration cache file.
265 | *
266 | * @return string
267 | */
268 | public function getCachedConfigPath()
269 | {
270 | return $this->normalizeCacheDomainPath('APP_CONFIG_CACHE', 'cache/config.php');
271 | }
272 |
273 | /**
274 | * Get the path to the configuration cache file.
275 | *
276 | * @return string
277 | */
278 | public function getCachedRoutesPath()
279 | {
280 | return $this->normalizeCacheDomainPath('APP_ROUTES_CACHE', 'cache/routes.php');
281 | }
282 |
283 | /**
284 | * Get the path to the events cache file.
285 | *
286 | * @return string
287 | */
288 | public function getCachedEventsPath()
289 | {
290 | return $this->normalizeCacheDomainPath('APP_EVENTS_CACHE', 'cache/events.php');
291 | }
292 |
293 | /**
294 | * Get the path to the default cache file for config or routes.
295 | *
296 | * @param string
297 | * @return string
298 | */
299 | protected function normalizeCacheDomainPath($key, $default)
300 | {
301 |
302 | if (is_null($env = Env::get($key))) {
303 | $domainDefault = $this->getDomainCachedFileDefault($default);
304 | return $this->bootstrapPath($domainDefault);
305 | }
306 |
307 | return Str::startsWith($env, '/')
308 | ? $env
309 | : $this->basePath($env);
310 |
311 | }
312 |
313 | /**
314 | * Get a default file for cache files depending upon the loaded .env file
315 | *
316 | * @return string
317 | */
318 | protected function getDomainCachedFileDefault($default)
319 | {
320 | $envFile = $this->environmentFile();
321 | if ($envFile && $envFile == '.env')
322 | return $default;
323 |
324 | $this->checkDomainDetection();
325 |
326 | $defaultWithoutPhpExt = substr($default,0,-4);
327 |
328 | return $defaultWithoutPhpExt.'-'.domain_sanitized($this['domain']).'.php';
329 | }
330 |
331 |
332 | /*
333 | * Get the list of installed domains
334 | *
335 | * @return Array
336 | */
337 | public function domainsList()
338 | {
339 |
340 | $domainsInConfig = config('domain.domains', []);
341 |
342 | $domains = [];
343 |
344 | foreach ($domainsInConfig as $domain) {
345 | $domains[$domain] = [
346 | 'storage_path' => $this->domainStoragePath($domain),
347 | 'env' => $this->environmentFileDomain($domain),
348 | ];
349 | }
350 |
351 | return $domains;
352 |
353 | }
354 | }
355 |
--------------------------------------------------------------------------------
/tests/src/CommandsTestCase.php:
--------------------------------------------------------------------------------
1 | setPaths();
56 |
57 | $this->files = new Filesystem();
58 |
59 | if (!is_dir(env_path())) {
60 | mkdir(env_path());
61 | }
62 |
63 | copy(__DIR__ . '/../.env.example',env_path('.env'));
64 |
65 | $this->artisan('vendor:publish',['--provider' => 'Gecche\Multidomain\Foundation\Providers\DomainConsoleServiceProvider']);
66 |
67 |
68 | }
69 |
70 | protected function setPaths()
71 | {
72 | $this->laravelAppPath = __DIR__ . '/../../vendor/orchestra/testbench-core/laravel';
73 | $this->laravelEnvPath = $this->laravelAppPath;
74 | }
75 |
76 | /**
77 | * Resolve application Console Kernel implementation.
78 | *
79 | * @param \Illuminate\Foundation\Application $app
80 | * @return void
81 | */
82 | protected function resolveApplicationConsoleKernel($app)
83 | {
84 | $app->singleton(ConsoleKernelContract::class, ConsoleKernel::class);
85 | }
86 |
87 | protected function resolveApplicationHttpKernel($app)
88 | {
89 | $app->singleton(HttpKernelContract::class, HttpKernel::class);
90 | }
91 |
92 | protected function resolveApplication()
93 | {
94 | static::$cacheApplicationBootstrapFile ??= $this->getApplicationBootstrapFile('app.php');
95 |
96 | if (\is_string(static::$cacheApplicationBootstrapFile)) {
97 | $APP_BASE_PATH = $_ENV['APP_BASE_PATH'];
98 |
99 | return require static::$cacheApplicationBootstrapFile;
100 | }
101 |
102 | return $this->resolveGeccheApplication();
103 | }
104 |
105 | final protected function resolveGeccheApplication()
106 | {
107 | return (new ApplicationBuilder(new Application($_ENV['APP_BASE_PATH'])))
108 | ->withProviders()
109 | ->withMiddleware(static function ($middleware) {
110 | //
111 | })
112 | ->withCommands()
113 | ->create();
114 | }
115 |
116 | /**
117 | * Define environment setup.
118 | *
119 | * @param \Illuminate\Foundation\Application $app
120 | * @return void
121 | */
122 | protected function getEnvironmentSetUp($app)
123 | {
124 | // set up database configuration
125 | }
126 |
127 | /**
128 | * Get Sluggable package providers.
129 | *
130 | * @return array
131 | */
132 | protected function getPackageProviders($app)
133 | {
134 | return [
135 | TestServiceProvider::class,
136 | DomainConsoleServiceProvider::class,
137 | // ServiceProvider::class,
138 | ];
139 | }
140 |
141 |
142 | /*
143 | * TEST FOR DOMAIN COMMAND
144 | * First we add the domain adn we check is in the output of the domain:list command.
145 | * Then we remove the domain adn we check is no more in the output of the domain:list command.
146 | */
147 | public function testDomainCommand() {
148 |
149 |
150 | $serverName = Arr::get($_SERVER,'SERVER_NAME','');
151 |
152 | $this->artisan('domain');
153 |
154 | $artisanOutput = Artisan::output();
155 |
156 | //CHECK IS IN THE OUTPUT OF THE COMMAND
157 | $this->assertStringContainsString($serverName,$artisanOutput);
158 |
159 |
160 | }
161 |
162 | /*
163 | * TEST FOR DOMAIN ADD COMMAND
164 | * It checks if the env file and storage dirs exist and if the list of domains in the config file is updated
165 | */
166 | public function testDomainAddCommand()
167 | {
168 | $site = Arr::get($_SERVER, 'SERVER_NAME');
169 |
170 | if (!$site) {
171 | $this->assertTrue(true);
172 | return;
173 | }
174 |
175 | $argDomain = $site ? ['domain' => $site] : [];
176 |
177 | $this->artisan('domain:add', $argDomain);
178 |
179 | $this->assertFileExists(env_path('.env.'.$site));
180 |
181 | $this->artisan('config:clear');
182 |
183 | $domainListed = Config::get('domain.domains');
184 |
185 | $this->assertArrayHasKey($site,$domainListed);
186 |
187 | $this->assertDirectoryExists(app()->exactDomainStoragePath());
188 | }
189 |
190 | /*
191 | * TEST FOR DOMAIN REMOVE COMMAND
192 | * It checks if the .env file does not exist and if the list of domains in the config file is updated without the domain.
193 | * It checks also if storage dirs still exist
194 | */
195 | public function testDomainRemoveCommand()
196 | {
197 | $site = Arr::get($_SERVER, 'SERVER_NAME');
198 | if (!$site) {
199 | $this->assertTrue(true);
200 | return;
201 | }
202 | $argDomain = $site ? ['domain' => $site] : [];
203 |
204 | $this->artisan('domain:remove', $argDomain);
205 |
206 | $this->assertFileDoesNotExist(env_path('.env.'.$site));
207 |
208 | $domainListed = Config::get('domain.domains');
209 |
210 | $this->assertArrayNotHasKey($site,$domainListed);
211 |
212 | $this->assertDirectoryExists(app()->exactDomainStoragePath());
213 | }
214 |
215 | /*
216 | * TEST FOR DOMAIN REMOVE COMMAND (FORCE)
217 | * It checks if the .env file does not exist and if the list of domains in the config file is updated without the domain.
218 | * Now it checks also if storage dirs does not exist (force)
219 | */
220 | public function testDomainRemoveForceCommand()
221 | {
222 | $site = Arr::get($_SERVER, 'SERVER_NAME');
223 | if (!$site) {
224 | $this->assertTrue(true);
225 | return;
226 | }
227 | $argDomain = $site ? ['domain' => $site, '--force' => 1] : ['--force' => 1];
228 |
229 | $this->artisan('domain:remove', $argDomain);
230 |
231 | $this->assertFileDoesNotExist(env_path('.env.'.$site));
232 |
233 | $domainListed = Config::get('domain.domains');
234 |
235 | $this->assertArrayNotHasKey($site,$domainListed);
236 |
237 | //$this->assertDirectoryDoesNotExist(storage_path(domain_sanitized($site)));
238 | $this->assertDirectoryDoesNotExist(app()->exactDomainStoragePath());
239 | }
240 |
241 | /*
242 | * TEST FOR DOMAIN REMOVE COMMAND (FORCE)
243 | * It checks if the .env file does not exist and if the list of domains in the config file is updated without the domain.
244 | * Now it checks also if storage dirs does not exist (force)
245 | */
246 | public function testDomainRemoveForceCommandSubsite()
247 | {
248 |
249 | $mainStoragePath = $this->laravelAppPath . DIRECTORY_SEPARATOR . 'storage';
250 | $sites = [
251 | 'site1.com',
252 | 'sub1.site1.com',
253 | 'sub2.site1.com',
254 | ];
255 | foreach ($sites as $currSite) {
256 | $this->artisan('domain:add', ['domain' => $currSite]);
257 | $this->assertFileExists(env_path('.env.'.$currSite));
258 | $this->assertDirectoryExists($mainStoragePath . DIRECTORY_SEPARATOR . domain_sanitized($currSite));
259 | }
260 |
261 |
262 | $site = 'sub1.site1.com';
263 | $argDomain = ['domain' => $site, '--force' => 1];
264 | $this->artisan('domain:remove', $argDomain);
265 |
266 | foreach ($sites as $currSite) {
267 | if ($site == $currSite) {
268 | $this->assertFileDoesNotExist(env_path('.env.'.$currSite));
269 | $this->assertDirectoryDoesNotExist($mainStoragePath . DIRECTORY_SEPARATOR . domain_sanitized($currSite));
270 | } else {
271 | $this->assertFileExists(env_path('.env.'.$currSite));
272 | $this->assertDirectoryExists($mainStoragePath . DIRECTORY_SEPARATOR . domain_sanitized($currSite));
273 | }
274 | }
275 |
276 | foreach ($sites as $currSite) {
277 | if ($site != $currSite) {
278 | $this->artisan('domain:remove', ['domain' => $currSite,'--force' => 1]);
279 | $this->assertFileDoesNotExist(env_path('.env.'.$currSite));
280 | $this->assertDirectoryDoesNotExist($mainStoragePath . DIRECTORY_SEPARATOR . domain_sanitized($currSite));
281 | }
282 | }
283 |
284 | }
285 |
286 | /*
287 | * TEST FOR DOMAIN UPDATE_ENV COMMAND
288 | * First we remove and add the domain to be sure is fresh.
289 | * Then we check the DB_DATABASE value in the corresponding .env..
290 | * We update the DB_DATABASE value and then we check the final value.
291 | */
292 | public function testDomainUpdateEnvCommand() {
293 |
294 | $dbName = $this->siteDbName;
295 | $site = Arr::get($_SERVER, 'SERVER_NAME');
296 | if (!$site) {
297 | $this->assertTrue(true);
298 | return;
299 | }
300 | $argDomain = $site ? ['domain' => $site] : [];
301 |
302 | $this->artisan('domain:remove', array_merge($argDomain, ['--force' => 1]));
303 | $this->artisan('domain:add', $argDomain);
304 |
305 | $fileContents = explode("\n",$this->files->get(env_path('.env.'.$site)));
306 | $this->assertNotContains("DB_DATABASE=".$dbName,$fileContents);
307 |
308 | $this->artisan('domain:update_env', array_merge($argDomain, [
309 | '--domain_values' => '{"DB_DATABASE":"'.$dbName.'"}',
310 | ]));
311 |
312 | $fileContents = explode("\n",$this->files->get(env_path('.env.'.$site)));
313 | $this->assertContains("DB_DATABASE=".$dbName,$fileContents);
314 |
315 | $this->artisan('domain:remove', array_merge($argDomain, ['--force' => 1]));
316 |
317 | }
318 |
319 | /*
320 | * TEST FOR DOMAIN LIST COMMAND
321 | * First we add the domain adn we check is in the output of the domain:list command.
322 | * Then we remove the domain adn we check is no more in the output of the domain:list command.
323 | */
324 | public function testDomainListCommand() {
325 |
326 | $site = Arr::get($_SERVER, 'SERVER_NAME');
327 | $argDomain = $site ? ['domain' => $site] : [];
328 |
329 | //ADD THE DOMAIN
330 | if ($site) {
331 | $this->artisan('domain:add', $argDomain);
332 | }
333 |
334 | $this->artisan('domain:list');
335 |
336 | $artisanOutput = Artisan::output();
337 |
338 | //CHECK IS IN THE OUTPUT OF THE COMMAND
339 | if ($site) {
340 | $this->assertStringContainsString($site,$artisanOutput);
341 | } else {
342 | $this->assertEquals($site, $artisanOutput);
343 | }
344 |
345 |
346 | //REMOVE THE DOMAIN
347 | if ($site) {
348 | $this->artisan('domain:remove', array_merge($argDomain, ['--force' => 1]));
349 | }
350 | $this->artisan('domain:list');
351 |
352 | $artisanOutput = Artisan::output();
353 |
354 | //CHECK IS NOT IN THE OUTPUT OF THE COMMAND
355 | if ($site) {
356 | $this->assertStringNotContainsString($site,$artisanOutput);
357 | } else {
358 | $this->assertEquals($site, $artisanOutput);
359 | }
360 | }
361 |
362 |
363 | }
--------------------------------------------------------------------------------
/tests/src/HttpTestCase.php:
--------------------------------------------------------------------------------
1 | laravelAppPath = __DIR__ . '/../../vendor/orchestra/testbench-core/laravel';
78 | copy(__DIR__ . '/../artisan',$this->laravelAppPath.'/artisan');
79 | $process = new Process(['php', $this->laravelAppPath.'/artisan', 'config:clear']);
80 | $process->run();
81 |
82 |
83 | $this->files = new Filesystem();
84 | if (!file_exists($this->laravelAppPath.'/config/domain.php')) {
85 | file_put_contents($this->laravelAppPath.'/config/domain.php','laravelAppPath.'/config/app.php',$this->laravelAppPath.'/config/appORIG.php');
88 | copy(__DIR__ . '/../config/app.php',$this->laravelAppPath.'/config/app.php');
89 | copy(__DIR__ . '/../.env.example', $this->laravelAppPath.'/.env');
90 |
91 | $process = new Process(['php', $this->laravelAppPath.'/artisan', 'vendor:publish', '--provider="Gecche\Multidomain\Foundation\Providers\DomainConsoleServiceProvider"']);
92 | $process->run();
93 |
94 | $process = new Process(['php', $this->laravelAppPath.'/artisan', 'domain:remove', $this->site1, '--force']);
95 | $process->run();
96 | $process = new Process(['php', $this->laravelAppPath.'/artisan', 'domain:remove', $this->site2, '--force']);
97 | $process->run();
98 | $process = new Process(['php', $this->laravelAppPath.'/artisan', 'domain:remove', $this->subSite1, '--force']);
99 | $process->run();
100 |
101 | $process = new Process(['php', $this->laravelAppPath.'/artisan', 'domain:add', $this->site1]);
102 | $process->run();
103 | $process = new Process(['php', $this->laravelAppPath.'/artisan', 'domain:add', $this->site2]);
104 | $process->run();
105 | $process = new Process(['php', $this->laravelAppPath.'/artisan', 'domain:add', $this->subSite1]);
106 | $process->run();
107 |
108 | $domainValues = [
109 | 'APP_NAME' => $this->siteAppName1,
110 | 'DB_DATABASE' => $this->siteDbName1,
111 | ];
112 | $process = new Process(['php', $this->laravelAppPath.'/artisan', 'domain:update_env', $this->site1, '--domain_values='.json_encode($domainValues)]);
113 | $process->run();
114 |
115 | $domainValues = [
116 | 'APP_NAME' => $this->siteAppName2,
117 | 'DB_DATABASE' => $this->siteDbName2,
118 | ];
119 | $process = new Process(['php', $this->laravelAppPath.'/artisan', 'domain:update_env', $this->site2, '--domain_values='.json_encode($domainValues)]);
120 | $process->run();
121 |
122 | $domainValues = [
123 | 'APP_NAME' => $this->subSiteAppName1,
124 | 'DB_DATABASE' => $this->subSiteDbName1,
125 | ];
126 | $process = new Process(['php', $this->laravelAppPath.'/artisan', 'domain:update_env', $this->subSite1, '--domain_values='.json_encode($domainValues)]);
127 | $process->run();
128 |
129 | parent::setUp();
130 |
131 |
132 | }
133 |
134 | protected function tearDown(): void
135 | {
136 | $this->artisan('domain:remove', ['domain' => $this->site1, '--force' => 1]);
137 | $this->artisan('domain:remove', ['domain' => $this->site2, '--force' => 1]);
138 | $this->artisan('domain:remove', ['domain' => $this->subSite1, '--force' => 1]);
139 | parent::tearDown(); // TODO: Change the autogenerated stub
140 |
141 | }
142 |
143 |
144 | /**
145 | * Resolve application Console Kernel implementation.
146 | *
147 | * @param \Illuminate\Foundation\Application $app
148 | * @return void
149 | */
150 | protected function resolveApplicationConsoleKernel($app)
151 | {
152 | $app->singleton(ConsoleKernelContract::class, ConsoleKernel::class);
153 | }
154 |
155 | protected function resolveApplicationHttpKernel($app)
156 | {
157 | $app->singleton(HttpKernelContract::class, HttpKernel::class);
158 | }
159 |
160 | protected function resolveApplication()
161 | {
162 | static::$cacheApplicationBootstrapFile ??= $this->getApplicationBootstrapFile('app.php');
163 |
164 | if (\is_string(static::$cacheApplicationBootstrapFile)) {
165 | $APP_BASE_PATH = $_ENV['APP_BASE_PATH'];
166 |
167 | return require static::$cacheApplicationBootstrapFile;
168 | }
169 |
170 | return $this->resolveGeccheApplication();
171 | }
172 |
173 | final protected function resolveGeccheApplication()
174 | {
175 | return (new ApplicationBuilder(new Application($_ENV['APP_BASE_PATH'])))
176 | ->withProviders()
177 | ->withMiddleware(static function ($middleware) {
178 | //
179 | })
180 | ->withCommands()
181 | ->create();
182 | }
183 |
184 | /**
185 | * Define environment setup.
186 | *
187 | * @param \Illuminate\Foundation\Application $app
188 | * @return void
189 | */
190 | protected function getEnvironmentSetUp($app)
191 | {
192 | // set up database configuration
193 | }
194 |
195 | /**
196 | * Get Sluggable package providers.
197 | *
198 | * @return array
199 | */
200 | protected function getPackageProviders($app)
201 | {
202 | return [
203 | TestServiceProvider::class,
204 | DomainConsoleServiceProvider::class,
205 | // ServiceProvider::class,
206 | ];
207 | }
208 |
209 |
210 | /**
211 | * In this test we simply checks that in the route "/" we display
212 | * the right APP_NAME from the env file depending upon the $_SERVER['SERVER_NAME'] value
213 | * set when the test has been launched.
214 | *
215 | */
216 | public function testWelcomePage()
217 | {
218 |
219 | $this->serverName = Arr::get($_SERVER, 'SERVER_NAME');
220 | $stringToSee = 'Laravel';
221 | if (in_array($this->serverName, [$this->subSite1])) {
222 | $stringToSee = $this->subSiteAppName1;
223 | } elseif (in_array($this->serverName, [$this->site1]) || Str::endsWith($this->serverName, '.'.$this->site1)) {
224 | $stringToSee = $this->siteAppName1;
225 | } elseif (in_array($this->serverName, [$this->site2]) || Str::endsWith($this->serverName, '.'.$this->site2)) {
226 | $stringToSee = $this->siteAppName2;
227 | }
228 |
229 | $this->visit('http://' . $this->serverName)
230 | ->see($stringToSee);
231 | }
232 |
233 |
234 | /**
235 | * In this test we checks that the database connection is versus the right database set in the
236 | * DB_DATABASE entry of the env file.
237 | * The chosen env files again depends upon the $_SERVER['SERVER_NAME'] value
238 | * set when the test has been launched.
239 | *
240 | */
241 | public function testDBConnection()
242 | {
243 |
244 | $this->serverName = Arr::get($_SERVER, 'SERVER_NAME');
245 | $dbName = DB::connection('mysql')->getDatabaseName();
246 | $expectedDb = 'homestead';
247 | if (in_array($this->serverName, [$this->subSite1])) {
248 | $expectedDb = $this->subSiteDbName1;
249 | } elseif (in_array($this->serverName, [$this->site1]) || Str::endsWith($this->serverName, '.'.$this->site1)) {
250 | $expectedDb = $this->siteDbName1;
251 | } elseif (in_array($this->serverName, [$this->site2]) || Str::endsWith($this->serverName, '.'.$this->site2)) {
252 | $expectedDb = $this->siteDbName2;
253 | }
254 |
255 | $this->assertEquals($expectedDb, $dbName);
256 | }
257 |
258 |
259 | /**
260 | * In this test we checks that the database connection is versus the right database set in the
261 | * DB_DATABASE entry of the env file.
262 | * The chosen env files again depends upon the $_SERVER['SERVER_NAME'] value
263 | * set when the test has been launched.
264 | *
265 | */
266 | public function testEnvFile()
267 | {
268 |
269 | $this->serverName = Arr::get($_SERVER, 'SERVER_NAME');
270 | $envfileName = app()->environmentFile();
271 | $expectedEnvFile = '.env';
272 | if (in_array($this->serverName, [$this->subSite1])) {
273 | $expectedEnvFile = '.env.'.$this->subSite1;
274 | } elseif (in_array($this->serverName, [$this->site1]) || Str::endsWith($this->serverName, '.'.$this->site1)) {
275 | $expectedEnvFile = '.env.'.$this->site1;
276 | } elseif (in_array($this->serverName, [$this->site2]) || Str::endsWith($this->serverName, '.'.$this->site2)) {
277 | $expectedEnvFile = '.env.'.$this->site2;
278 | }
279 |
280 | $this->assertEquals($expectedEnvFile, $envfileName);
281 | }
282 |
283 | /**
284 | * In this test we checks that the database connection is versus the right database set in the
285 | * DB_DATABASE entry of the env file.
286 | * The chosen env files again depends upon the $_SERVER['SERVER_NAME'] value
287 | * set when the test has been launched.
288 | *
289 | */
290 | public function testStorageFolder()
291 | {
292 |
293 | $this->serverName = Arr::get($_SERVER, 'SERVER_NAME');
294 | $storageFolder = storage_path();
295 | $expectedStorageFolder = base_path() . '/storage';
296 |
297 | if (in_array($this->serverName, [$this->subSite1])) {
298 | $expectedStorageFolder = $expectedStorageFolder . DIRECTORY_SEPARATOR . domain_sanitized($this->subSite1);
299 | } elseif (in_array($this->serverName, [$this->site1]) || Str::endsWith($this->serverName, '.'.$this->site1)) {
300 | $expectedStorageFolder = $expectedStorageFolder . DIRECTORY_SEPARATOR . domain_sanitized($this->site1);
301 | } elseif (in_array($this->serverName, [$this->site2]) || Str::endsWith($this->serverName, '.'.$this->site2)) {
302 | $expectedStorageFolder = $expectedStorageFolder . DIRECTORY_SEPARATOR . domain_sanitized($this->site2);
303 | }
304 |
305 | $this->assertEquals($expectedStorageFolder, $storageFolder);
306 | }
307 |
308 |
309 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://tldrlegal.com/license/mit-license)
2 | [](http://laravel.com)
3 | [](http://laravel.com)
4 | [](http://laravel.com)
5 | [](http://laravel.com)
6 | [](http://laravel.com)
7 | [](http://laravel.com)
8 | [](http://laravel.com)
9 | [](http://laravel.com)
10 |
11 | # Laravel Multi Domain
12 | An extension for using Laravel in a multi domain setting
13 |
14 | 
15 |
16 | ## Description
17 | This package allows a single Laravel installation to work with multiple HTTP domains.
18 |
19 | There are many cases in which different customers use the same application in terms of code but not in terms of
20 | database, storage and configuration.
21 |
22 | This package gives a very simple way to get a specific env file, a specific storage path and a specific database
23 | for each such customer.
24 |
25 | ## Documentation
26 |
27 | ### Version Compatibility
28 |
29 | Laravel | Multidomain
30 | :----------|:-----------
31 | 12.x | 12.x
32 | 11.x | 11.x
33 | 10.x | 10.x
34 | 9.x | 5.x
35 | 8.x | 4.x
36 | 7.x | 3.x
37 | 6.x | 2.x
38 | 5.8.x | 1.4.x
39 | 5.7.x | 1.3.x
40 | 5.6.x | 1.2.x
41 | 5.5.x | 1.1.x
42 |
43 | #### Further notes on Compatibility
44 |
45 | Releases v1.1.x:
46 | - From v1.1.0 to v1.1.5, releases are fully compatibile with Laravel 5.5, 5.6, 5.7, 5.8 or 6.0.
47 | - From v1.1.6+ releases v1.1.x are only compatible with Laravel 5.5 in order to run tests correctly.
48 |
49 | To date, releases v1.1.6+, v1.2.x, v1.3.x, v1.4.x, v2.x and v3.x are functionally equivalent.
50 | Releases have been separated in order to run integration tests with the corresponding version of the
51 | Laravel framework.
52 |
53 | However, with the release of Laravel 8, releases v1.1.14, v1.2.8, v1.3.8 and v1.4.8 are the last releases
54 | including new features for the corresponding Laravel 5.x versions (bugfix support is still active for that versions).
55 | **2021-02-13 UPDATE**: some last features for v1.1+ releases are still ongoing :)
56 |
57 | v1.0 requires Laravel 5.1, 5.2, 5.3 and 5.4 (no longer maintained and not tested versus laravel 5.4,
58 | however the usage of the package is the same as for 1.1)
59 |
60 | **2023-02-20 UPDATE**: From Laravel 10.x up, the package versions follow the same numbering.
61 |
62 | ### Installation
63 |
64 | Add gecche/laravel-multidomain as a requirement to composer.json:
65 |
66 | ```javascript
67 | {
68 | "require": {
69 | "gecche/laravel-multidomain": "12.*"
70 | }
71 | }
72 | ```
73 |
74 | Update your packages with composer update or install with composer install.
75 |
76 | You can also add the package using `composer require gecche/laravel-multidomain` and later
77 | specify the version you want.
78 |
79 | This package needs to override the detection of the HTTP domain in a minimal set of Laravel core functions
80 | at the very start of the bootstrap process in order to get the specific environment file. So this package
81 | needs a few more configuration steps than most Laravel packages.
82 |
83 | Installation steps:
84 | 1. replace the whole Laravel container by modifying the following lines
85 | at the very top of the `bootstrap/app.php` file.
86 |
87 | ```php
88 | //use Illuminate\Foundation\Application
89 | use Gecche\Multidomain\Foundation\Application
90 | ```
91 |
92 | 2. Override the `QueueServiceProvider` with the extended
93 | one in the `config/app.php` file as follows:
94 |
95 | ```php
96 | 'providers' => \Illuminate\Support\ServiceProvider::defaultProviders()->merge([
97 | // Package Service Providers...
98 | ])->replace([
99 | \Illuminate\Queue\QueueServiceProvider::class => \Gecche\Multidomain\Queue\QueueServiceProvider::class,
100 | ])->merge([
101 | // Added Service Providers (Do not remove this line)...
102 | ])->toArray(),
103 | ```
104 |
105 | Please note that if you changed the `config/app.php` file due to other reasons, probably there is already
106 | the above `providers` entry in that file and the only important line is the one which replaces the `QueueServiceProvider`.
107 |
108 | 3. Publish the config file.
109 |
110 | ```
111 | php artisan vendor:publish
112 | ```
113 |
114 | (This package makes use of the discovery feature.)
115 |
116 | Following the above steps, your application will be aware of the HTTP domain
117 | in which is running, both for HTTP and CLI requests, including queue support.
118 |
119 | NOTE: in Laravel 11 the installation is simpler than before: if you use a
120 | previous version of Laravel, please check in the documentation the installation steps.
121 |
122 | #### Laravel Horizon installation:
123 |
124 | The package is compatible with Horizon, thatnks to community contributions. If you need to use
125 | this package together with Horizon you have to follow other two installation steps:
126 |
127 | 1. Install Laravel Horizon as usual
128 |
129 | 2. Replace the Laravel Horizon import at the very top of the app/Providers/HorizonServiceProvider.php file.
130 |
131 | ```php
132 | //use Laravel\Horizon\HorizonApplicationServiceProvider;
133 | use Gecche\Multidomain\Horizon\HorizonApplicationServiceProvider;
134 | ```
135 |
136 | ### Usage
137 |
138 | This package adds three commands to manage your application HTTP domains:
139 |
140 | #### `domain.add` artisan command
141 |
142 | The main command is the `domain:add` command which takes as argument the name of the
143 | HTTP domain to add to the application. Let us suppose we have two domains, `site1.com`
144 | and `site2.com`, sharing the same code.
145 |
146 | We simply do:
147 |
148 | ```
149 | php artisan domain:add site1.com
150 | ```
151 |
152 | and
153 |
154 | ```
155 | php artisan domain:add site2.com
156 | ```
157 |
158 | These commands create two new environment files, `.env.site1.com` and `.env.site2.com`,
159 | in which you can put the specific configuration for each site (e.g. databases configuration,
160 | cache configuration and other configurations, as usually found in an environment file).
161 |
162 | The command also adds an entry in the `domains` key in `config/domains.php` file.
163 |
164 | In addition, two new folders are created, `storage/site1_com/` and `storage/site2_com/`.
165 | They have the same folder structure as the main storage.
166 |
167 | Customizations to this `storage` substructure must be matched by values in the `config/domain.php` file.
168 |
169 | #### `domain.remove` artisan command
170 | The `domain:remove` command removes the specified HTTP domain from the
171 | application by deleting its environment file. E.g.:
172 |
173 | ```
174 | php artisan domain:remove site2.com
175 | ```
176 | Adding the `force` option will delete the domain storage folder.
177 |
178 | The command also removes the appropriate entry from, the `domains` key in `config/domains.php` file.
179 |
180 | #### `domain.update_env` artisan command
181 | The `domain:update_env` command passes a json encoded array of data to update one or all of the environment files.
182 | These values will be added at the end of the appropriate .env.
183 |
184 | Update a single domain environment file by adding the `domain` argument.
185 |
186 | When the `domain` argument is absent, the command updates all the environment files, including the standard `.env` one.
187 |
188 | The list of domains to be updated is maintained in the `domain.php` config file.
189 |
190 | E.g.:
191 |
192 | ```
193 | php artisan domain:update_env --domain_values='{"TOM_DRIVER":"TOMMY"}'
194 | ```
195 |
196 | will add the line `TOM_DRIVER=TOMMY` to all the domain environment files.
197 |
198 | #### `domain.list` artisan command
199 | The `domain:list` command lists the currently installed domains, with their .env file and storage path dir.
200 |
201 | The list is maintained in the `domains` key of the `config/domain.php` config file.
202 |
203 | This list is automatically updated at every `domain:add` and `domain:remove` commands run.
204 |
205 | #### `config:cache` artisan command
206 | The config:cache artisan command can be used with this package in the same way as any other
207 | artisan command.
208 |
209 | Note that this command will generate a file config.php file for each domain under which the command has been executed.
210 | I.e. the command
211 | ```
212 | php artisan config:cache --domain=site2.com
213 | ```
214 | will generate the file
215 | ```
216 | config-site2_com.php
217 | ```
218 |
219 | ### Further information
220 | At run-time, the current HTTP domain is maintained in the laravel container
221 | and can be accessed by its `domain()` method added by this package.
222 |
223 | A `domainList()` method is available. It returns an associative array
224 | containing the installed domains info, similar to the `domain.list` command above.
225 |
226 | E.g.
227 | ```
228 | [
229 | site1.com => [
230 | 'storage_path' => /site1_com,
231 | 'env' => '.env.site1.com'
232 | ]
233 | ]
234 | ```
235 |
236 | #### Distinguishing between HTTP domains in web pages
237 |
238 | For each HTTP request received by the application, the specific environment file is
239 | loaded and the specific storage folder is used.
240 |
241 | If no specific environment file and/or storage folder is found, the standard one is used.
242 |
243 | The detection of the right HTTP domain is done by using the `$_SERVER['SERVER_NAME']`
244 | PHP variable.
245 |
246 | IMPORTANT NOTE: in some execution environments $_SERVER['SERVER_NAME'] is not instantiated, so
247 | this package does not work properly until you customize the detection of HTTP domains
248 | as described below.
249 |
250 | #### Customizing the detection of HTTP domains
251 |
252 | Starting from release 1.1.15, the detection of HTTP domains can be customized passing a `Closure`
253 | as the `domain_detection_function_web` entry of the new `domainParams` argument of `Application`'s
254 | constructor. In the following example, the HTTP domain detection relies upon `$_SERVER['HTTP_HOST']`
255 | instead of `$_SERVER['SERVER_NAME']`.
256 |
257 | ```php
258 | //use Illuminate\Foundation\Application;
259 | use Gecche\Multidomain\Foundation\Application;
260 | use Illuminate\Foundation\Configuration\Exceptions;
261 | use Illuminate\Foundation\Configuration\Middleware;
262 |
263 | $environmentPath = null;
264 |
265 | $domainParams = [
266 | 'domain_detection_function_web' => function() {
267 | return \Illuminate\Support\Arr::get($_SERVER,'HTTP_HOST');
268 | }
269 | ];
270 |
271 | return Application::configure(basePath: dirname(__DIR__),
272 | environmentPath: $environmentPath,
273 | domainParams: $domainParams)
274 | ->withRouting(
275 | web: __DIR__.'/../routes/web.php',
276 | commands: __DIR__.'/../routes/console.php',
277 | health: '/up',
278 | )
279 | ->withMiddleware(function (Middleware $middleware) {
280 | //
281 | })
282 | ->withExceptions(function (Exceptions $exceptions) {
283 | //
284 | })->create();
285 | ```
286 |
287 |
288 | #### Using multi domains in artisan commands
289 |
290 | In order to distinguishing between domains, each artisan command accepts a new option: `domain`. E.g.:
291 |
292 | ```
293 | php artisan list --domain=site1.com
294 | ```
295 |
296 | The command will use the corresponding domain settings.
297 |
298 | #### About queues
299 |
300 | The artisan commands `queue:work` and `queue:listen` commands have been updated
301 | to accept a new `domain` option.
302 | ```
303 | php artisan queue:work --domain=site1.com
304 | ```
305 | As usual, the above command will use the corresponding domain settings.
306 |
307 | Keep in mind that if, for example, you are using the `database` driver and you have two domains sharing the same db,
308 | you should use two distinct queues if you want to manage the jobs of each domain separately.
309 |
310 | For example, you could:
311 | - put in your .env files a default queue for each domain, e.g.
312 | `QUEUE_DEFAULT=default1` for site1.com and `QUEUE_DEFAULT=default2` for site2.com
313 | - update the `queue.php` config file by changing the default queue accordingly:
314 | ```
315 | 'database' => [
316 | 'driver' => 'database',
317 | 'table' => 'jobs',
318 | 'queue' => env('QUEUE_DEFAULT','default'),
319 | 'retry_after' => 90,
320 | ],
321 | ```
322 |
323 | - launch two distinct workers
324 | ```
325 | php artisan queue:work --domain=site1.com --queue=default1
326 | ```
327 | and
328 | ```
329 | php artisan queue:work --domain=site1.com --queue=default2
330 | ```
331 |
332 | Obviously, the same can be done for each other queue driver, apart from the `sync` driver.
333 |
334 | #### `storage:link` command
335 |
336 | If you make use of the `storage:link` command and you want a distinct symbolic link for each domain, you have to create
337 | them manually because to date such command always creates a link named `storage` and that name is hard coded in the
338 | command. Extending the `storage:link` command allowing to choose the name is outside the scope of this package
339 | (and I hope it will be done directly in future versions of Laravel).
340 |
341 | A way to obtain multiple storage links could be the following.
342 | Let us suppose to have two domains, namely `site1.com` and `site2.com` with associated storage folders
343 | `storage/site1_com` and `storage/site2_com`.
344 |
345 | 1. We manually create links for each domain:
346 |
347 | ```
348 | ln -s storage/site1_com/app/public public/storage-site1_com
349 | ln -s storage/site2_com/app/public public/storage-site2_com
350 | ```
351 |
352 | 2. In `.env.site1.com` and `.env.site2.com` we add an entry, e.g., for the first domain:
353 |
354 | ```
355 | APP_PUBLIC_STORAGE=-site1_com
356 | ```
357 |
358 | 3. In the `filesystems.php` config file we change as follows:
359 |
360 | ```
361 | 'public' => [
362 | 'driver' => 'local',
363 | 'root' => storage_path('app/public'),
364 | 'url' => env('APP_URL').'/storage'.env('APP_PUBLIC_STORAGE'),
365 | 'visibility' => 'public',
366 | ],
367 | ```
368 |
369 | Furthermore, if you are using the package in a Single Page Application (SPA) setting, you could better handling distinct
370 | public resources for each domain via .htaccess or similar solutions as pointed out by [Scaenicus](https://github.com/Scaenicus) in his
371 | [.htaccess solution](https://github.com/gecche/laravel-multidomain/issues/11#issuecomment-559822284).
372 |
373 | #### Storing environment files in a custom folder
374 |
375 | Starting from version 1.1.11 a second argument has been added to the Application constructor in order to choose the
376 | folder where to place the environment files: if you have tens of domains, it is not very pleasant to have environment
377 | files in the root Laravel's app folder.
378 |
379 | So, if you want to use a different folder simply add it at the very top of the `bootstrap/app.php` file. for example,
380 | if you want to add environment files to the `envs` subfolder, simply do:
381 |
382 | ```php
383 | //use Illuminate\Foundation\Application;
384 | use Gecche\Multidomain\Foundation\Application;
385 | use Illuminate\Foundation\Configuration\Exceptions;
386 | use Illuminate\Foundation\Configuration\Middleware;
387 |
388 | $environmentPath = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'envs';
389 |
390 | $domainParams = [];
391 |
392 | return Application::configure(basePath: dirname(__DIR__),
393 | environmentPath: $environmentPath,
394 | domainParams: $domainParams)
395 | ->withRouting(
396 | web: __DIR__.'/../routes/web.php',
397 | commands: __DIR__.'/../routes/console.php',
398 | health: '/up',
399 | )
400 | ->withMiddleware(function (Middleware $middleware) {
401 | //
402 | })
403 | ->withExceptions(function (Exceptions $exceptions) {
404 | //
405 | })->create();
406 | ```
407 |
408 | If you do not specify the second argument, the standard folder is assumed. Please note that if you specify a folder,
409 | also the standard `.env` file has to be placed in it
410 |
411 | #### Default environment files and storage folders
412 |
413 | If you try to run a web page or an shell command under a certain domain, e.g. `sub1.site1.com` and there is no specific
414 | environment file for that domain, i.e. the file `.env.sub1.site1.com` does not exist, the package will use the first
415 | available environment file by splitting the domain name with dots. In this example, the package searches for the
416 | the first environment file among the followings:
417 |
418 | ```
419 | .env.site1.com
420 | .env.com
421 | .env
422 | ```
423 |
424 | The same logic applies to the storage folder as well.
425 |
426 | #### About Laravel's Scheduler, Supervisor and some limitation
427 |
428 | If in your setting you make use of the Laravel's Scheduler, remember that also the command `schedule:run` has to be
429 | launched with the domain option. Hence, you have to launch a scheduler for each domain.
430 | At first one could think that one Scheduler instance should handle the commands launched for any domain, but the
431 | Scheduler itself is run within a Laravel Application, so the "env" under which it is run, automatically applies to
432 | each scheduled command and the `--domain` option has no effect at all.
433 |
434 | The same applies to externals tools like Supervisor: if you use Supervisor for artisan commands, e.g. the `queue:work`
435 | command, please be sure to prepare a command for each domain you want to handle.
436 |
437 | Due to the above, there are some cases in which the package can't work: in those settings where you don't have the
438 | possibility of changing for example the supervisor configuration rather than the `crontab` entries for the scheduler.
439 | Such an example has been pointed out [here](https://github.com/gecche/laravel-multidomain/issues/21#issuecomment-600469868)
440 | in which a Docker instance has been used.
441 |
442 | Lastly, be aware that some Laravel commands call other Artisan commands from the inside,
443 | obviously without the `--domain` option. The above situation does not work properly because the subcommand will work with
444 | the standard environment file. An example is the `migrate` command when using the `--seed` option.
445 |
446 |
--------------------------------------------------------------------------------
/tests/work_in_progress/ArtisanTestCase.php:
--------------------------------------------------------------------------------
1 | files = new Filesystem();
72 | $this->setPaths();
73 | copy($this->laravelAppPath.'/config/app.php',$this->laravelAppPath.'/config/appORIG.php');
74 | copy(__DIR__ . '/../config/app.php',$this->laravelAppPath.'/config/app.php');
75 | copy($this->laravelAppPath.'/config/queue.php',$this->laravelAppPath.'/config/queueORIG.php');
76 | copy(__DIR__ . '/../config/queue.php',$this->laravelAppPath.'/config/queue.php');
77 | if (!is_dir($this->laravelEnvPath)) {
78 | mkdir($this->laravelEnvPath);
79 | }
80 | copy(__DIR__ . '/../.env.example',$this->laravelEnvPath.'/.env');
81 | copy(__DIR__ . '/../'.$this->laravelArtisanFile,$this->laravelAppPath.DIRECTORY_SEPARATOR.'artisan');
82 |
83 | foreach ($this->files->allFiles(__DIR__ . '/../database/migrations') as $file) {
84 | $relativeFile = substr($file,strrpos($file,'/'));
85 | copy($file,$this->laravelAppPath.'/database/migrations/'.$relativeFile);
86 | }
87 |
88 | $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'vendor:publish', '--provider="Gecche\Multidomain\Foundation\Providers\DomainConsoleServiceProvider"']);
89 | $process->run();
90 |
91 | parent::setUp();
92 |
93 | }
94 |
95 | protected function setPaths() {
96 | $this->laravelAppPath = __DIR__ . '/../../vendor/orchestra/testbench-core/laravel';
97 | $this->laravelEnvPath = $this->laravelAppPath;
98 | }
99 |
100 | protected function tearDown(): void {
101 |
102 | $this->files->delete($this->laravelEnvPath.'/.env');
103 | copy($this->laravelAppPath.'/config/appORIG.php',$this->laravelAppPath.'/config/app.php');
104 | $this->files->delete($this->laravelAppPath.'/config/appORIG.php');
105 | copy($this->laravelAppPath.'/config/queueORIG.php',$this->laravelAppPath.'/config/queue.php');
106 | $this->files->delete($this->laravelAppPath.'/config/queueORIG.php');
107 |
108 | }
109 |
110 | /**
111 | * Define environment setup.
112 | *
113 | * @param \Illuminate\Foundation\Application $app
114 | * @return void
115 | */
116 | protected function getEnvironmentSetUp($app)
117 | {
118 |
119 | }
120 |
121 |
122 | /*
123 | * TEST FOR A BASIC COMMAND
124 | * We created a very simple "name" command (Gecche\Multidomain\Tests\Console\Commands\NameCommand) which simply displays the
125 | * text in the APP_NAME environment variable.
126 | * The default .env file containts APP_NAME=Laravel
127 | * First we create a new domain site1.test and we update his .env with APP_NAME=LARAVELTEST
128 | * Then we launch the "name" command twice: without options and with the --domain=site1.test option.
129 | * We expect to see "Laravel" andd then "LARAVELTEST" accordingly.
130 | */
131 | public function testBasicCommand() {
132 |
133 | //Note that if the $_SERVER['SERVER_NAME'] value has been set and the --domain option has NOT been set,
134 | //the $_SERVER['SERVER_NAME'] value acts as the --domain option value.
135 | $serverName = Arr::get($_SERVER,'SERVER_NAME','');
136 |
137 | $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'domain:add', 'site1.test']);
138 | $process->run();
139 |
140 | $domainValues = ['APP_NAME'=>'LARAVELTEST'];
141 | $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'domain:update_env', 'site1.test', '--domain_values='.json_encode($domainValues)]);
142 | $process->run();
143 |
144 | $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'name']);
145 | $process->run();
146 |
147 | if (in_array($serverName, ['site1.test']) || Str::endsWith($serverName,'.site1.test')) {
148 | $this->assertEquals("LARAVELTEST",$process->getOutput());
149 | } else {
150 | $this->assertEquals("Laravel",$process->getOutput());
151 | }
152 |
153 | $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'key:generate', '--domain=site1.test']);
154 | $process->run();
155 |
156 | $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'name', '--domain=site1.test']);
157 | $process->run();
158 | $this->assertEquals("LARAVELTEST",$process->getOutput());
159 |
160 | $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'domain:remove', 'site1.test', '--force']);
161 | $process->run();
162 |
163 |
164 | return;
165 |
166 | }
167 |
168 | /*
169 | * TEST FOR key:generate COMMAND
170 | */
171 | public function testKeyGenerateCommand() {
172 |
173 | //Note that if the $_SERVER['SERVER_NAME'] value has been set and the --domain option has NOT been set,
174 | //the $_SERVER['SERVER_NAME'] value acts as the --domain option value.
175 | $serverName = Arr::get($_SERVER,'SERVER_NAME','');
176 |
177 | $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'domain:add', 'site1.test']);
178 | $process->run();
179 |
180 | $domainValues = ['APP_NAME'=>'LARAVELTEST'];
181 | $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'domain:update_env', 'site1.test', '--domain_values='.json_encode($domainValues)]);
182 | $process->run();
183 |
184 | $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'key:generate']);
185 | $process->run();
186 | $keyGenerateOutput = substr($process->getOutput(),17);
187 | $keyGenerateOutput = substr($keyGenerateOutput,0,strrpos($keyGenerateOutput,']'));
188 |
189 |
190 | if (in_array($serverName, ['site1.test']) || Str::endsWith($serverName,'.site1.test')) {
191 | $envFile = '.env.site1.test';
192 | } else {
193 | $envFile = '.env';
194 | }
195 |
196 | $this->assertStringContainsString('APP_KEY='.$keyGenerateOutput,$this->files->get($this->laravelEnvPath.DIRECTORY_SEPARATOR.$envFile));
197 |
198 | return;
199 |
200 | }
201 |
202 | /*
203 | * TEST FOR QUEUE COMMANDS
204 | * In this test we check that queues with the database driver refer to the correct db depending upon the domain.
205 | * We use for simplicity the queue:flush command.
206 | * The default .env file containts QUEUE_DRIVER=database and DB_DATABASE=homestead
207 | * First we create a new domain site1.test and we update his .env with DB_DATABASE=site1 and we reset and run the migrations
208 | * in the homestead db.
209 | * Then, we check that the queue:flush command launched without options displays the standard success message
210 | * "All failed jobs deleted successfully!" and that the queue:flush command launched with the --domain=site1.test
211 | * option fails because it doesn't find the failed_job table as we did'nt migrate for that db.
212 | * Last, we repeat the check with the other db.
213 | *
214 | */
215 | // public function testQueueCommand() {
216 | // //Note that if the $_SERVER['SERVER_NAME'] value has been set and the --domain option has NOT been set,
217 | // //the $_SERVER['SERVER_NAME'] value acts as the --domain option value.
218 | // // So all the artisan commands run as if the option were instantiated.
219 | // $serverName = Arr::get($_SERVER,'SERVER_NAME','');
220 | //
221 | //
222 | // //ADDING DOMAIN AND UPDATING ENV
223 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'domain:add', 'site1.test']);
224 | // $process->run();
225 | //
226 | // $domainValues = ['DB_DATABASE'=>'site1'];
227 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'domain:update_env', 'site1.test', '--domain_values='.json_encode($domainValues)]);
228 | // $process->run();
229 | //
230 | //
231 | // //RESET MIGRATIONS IN BOTH DBS
232 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'migrate:reset']);
233 | // $process->run();
234 | //
235 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'migrate:reset', '--domain=site1.test']);
236 | // $process->run();
237 | //
238 | // //MIGRATIONS WITHOUT DOMAIN OPTION
239 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'migrate']);
240 | // $process->run();
241 | //// $this->assertEquals("Laravel",$process->getOutput());
242 | //
243 | //
244 | // //CHECK QUEUE:FLUSH COMMAND: SUCCESS WITHOUT OPTIONS AND FAILURE WITH DOMAIN OPTION
245 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'queue:flush']);
246 | // $process->run();
247 | // $this->assertStringContainsString('All failed jobs deleted successfully.',$process->getOutput());
248 | //
249 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'queue:flush', '--domain=site1.test']);
250 | // $process->run();
251 | // if (in_array($serverName, ['site1.test']) || Str::endsWith($serverName,'.site1.test')) {
252 | // $this->assertStringContainsString('All failed jobs deleted successfully.',$process->getOutput());
253 | // } else {
254 | // $this->assertStringContainsString('SQLSTATE[42S02]: Base table or view not found: 1146 Table \'site1.failed',$process->getOutput());
255 | // }
256 | //
257 | //
258 | //
259 | // //RESET MIGRATIONS IN BOTH DBS
260 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'migrate:reset']);
261 | // $process->run();
262 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'migrate:reset', '--domain=site1.test']);
263 | // $process->run();
264 | //
265 | // //MIGRATIONS WITH DOMAIN OPTION
266 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'migrate', '--domain=site1.test']);
267 | // $process->run();
268 | //// $this->assertEquals("Laravel",$process->getOutput());
269 | //
270 | // //CHECK QUEUE:FLUSH COMMAND: SUCCESS WITH DOMAIN OPTIION AND FAILURE WITHOUT
271 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'queue:flush', '--domain=site1.test']);
272 | // $process->run();
273 | // $this->assertStringContainsString('All failed jobs deleted successfully.',$process->getOutput());
274 | //
275 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'queue:flush']);
276 | // $process->run();
277 | // if (in_array($serverName, ['site1.test']) || Str::endsWith($serverName,'.site1.test')) {
278 | // $this->assertStringContainsString('All failed jobs deleted successfully.',$process->getOutput());
279 | // } else {
280 | // $this->assertStringContainsString('SQLSTATE[42S02]: Base table or view not found: 1146 Table \'homestead.failed',$process->getOutput());
281 | // }
282 | //
283 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'domain:remove', 'site1.test', '--force']);
284 | // $process->run();
285 | //
286 | // return;
287 | //
288 | // }
289 |
290 | /*
291 | * TEST FOR QUEUE LISTEN COMMAND
292 | * In this test we check that queues listeners applied to distinct domains work with the jobs pushed from
293 | * the corresponding domain and that the queues where they are pushed are those configured in the .env files.
294 | *
295 | * The job Gecche\Multidomain\Tests\Jobs\AppNameJob simply put in a file the name of the queue and the env(APP_NAME) value.
296 | * We push that job using an artisan Gecche\Multidomain\Tests\Console\Commands\QueuePush (queue_push) command which simply push the job in the queue.
297 | *
298 | */
299 | // public function testQueueListenCommand() {
300 | //
301 | //
302 | // //Note that if the $_SERVER['SERVER_NAME'] value has been set and the --domain option has NOT been set,
303 | // //the $_SERVER['SERVER_NAME'] value acts as the --domain option value.
304 | // // So all the artisan commands run as if the option were instantiated.
305 | // $serverName = Arr::get($_SERVER,'SERVER_NAME','');
306 | //
307 | //
308 | // //ADDING DOMAIN AND UPDATING ENV
309 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'domain:add', 'site1.test']);
310 | // $process->run();
311 | //
312 | // $domainValues = [
313 | // 'APP_NAME' => 'LARAVELTEST',
314 | // 'DB_DATABASE'=>'site1',
315 | // 'QUEUE_DEFAULT' => 'site1'
316 | // ];
317 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'domain:update_env', 'site1.test', '--domain_values='.json_encode($domainValues)]);
318 | // $process->run();
319 | //
320 | //
321 | // //RESET MIGRATIONS IN BOTH DBS
322 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'migrate:reset']);
323 | // $process->run();
324 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'migrate:reset', '--domain=site1.test']);
325 | // $process->run();
326 | //
327 | // //MIGRATIONS WITHOUT DOMAIN OPTION
328 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'migrate']);
329 | // $process->run();
330 | //
331 | //
332 | // //This the file used by the AppNameJob job
333 | // $fileToTest = $this->laravelAppPath.'/queueresult.txt';
334 | //
335 | // $this->files->delete($fileToTest);
336 | //
337 | // $this->assertFileDoesNotExist($fileToTest);
338 | //
339 | // //We start the listener
340 | // $processName = "php ".$this->laravelAppPath. "/artisan queue:listen";
341 | // $process = new Process(explode(" ",$processName));
342 | // $process->start();
343 | //
344 | // //We push the Job using the queue_push command
345 | // $process2 = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'queue_push']);
346 | // $process2->run();
347 | //
348 | // //Wait for a reasonable time (the only way we managed to simulate queue listeners)
349 | // sleep(5);
350 | //
351 | // //We assert that the file exists
352 | // $this->assertFileExists($fileToTest);
353 | //
354 | // //Depending upon the domain option (or the SERVER_NAME value)
355 | // //we check accordingly the contents of the file
356 | // $fileContent = $this->files->get($fileToTest);
357 | // if (in_array($serverName, ['site1.test']) || Str::endsWith($serverName,'.site1.test')) {
358 | // $this->assertStringContainsString('LARAVELTEST --- site1',$fileContent);
359 | // } else {
360 | // $this->assertStringContainsString('Laravel --- default',$fileContent);
361 | // }
362 | //
363 | //
364 | // $process->stop(0);
365 | //
366 | // //We kill the listener process by name
367 | // $string = 'pkill -f "' . $processName . '"';
368 | // exec($string);
369 | //
370 | //
371 | // /*
372 | // * We repeat the stuff under the domain "site1.test"
373 | // */
374 | // /***/
375 | // //MIGRATIONS WITH DOMAIN OPTION
376 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'migrate', '--domain=site1.test']);
377 | // $process->run();
378 | //
379 | // $this->files->delete($fileToTest);
380 | //
381 | // $this->assertFileDoesNotExist($fileToTest);
382 | //
383 | // $processName = 'php '.$this->laravelAppPath.'/artisan queue:listen --domain=site1.test';
384 | // $process = new Process(explode(" ",$processName));
385 | // $process->start();
386 | //
387 | // $process2 = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'queue_push', '--domain=site1.test']);
388 | // $process2->run();
389 | //
390 | // //Wait for a reasonable time
391 | // sleep(5);
392 | //
393 | // $this->assertFileExists($fileToTest);
394 | //
395 | // $fileContent = $this->files->get($fileToTest);
396 | // $this->assertStringContainsString('LARAVELTEST --- site1',$fileContent);
397 | //
398 | // $process->stop(0);
399 | //
400 | // $string = 'pkill -f "' . $processName . '"';
401 | // exec($string);
402 | //
403 | // $process = new Process([$this->phpProcess, $this->laravelAppPath.'/artisan', 'domain:remove', 'site1.test', '--force']);
404 | // $process->run();
405 | //
406 | //
407 | // $this->files->delete($fileToTest);
408 | //
409 | // return;
410 | //
411 | // }
412 | }
413 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 |
6 | ## [Unreleased]
7 |
8 | ## 12.1 - 2025-04-15
9 | ### Changed
10 | - Bugfix (cache correctly included in trait)
11 |
12 | ## 12.0 - 2024-02-26
13 | ### Changed
14 | - `composer.json` updated for Laravel 12.x
15 | - Updated tests for PHPUnit 11
16 |
17 | ## 11.2 - 2025-04-15
18 | ### Changed
19 | - Bugfix (cache correctly included in trait)
20 |
21 | ## 11.1 - 2024-05-30
22 | ### Changed
23 | - Bugfix for discovering commands
24 | - Bugfix for passing environmentPath and domainParams to the application constructor
25 | - Updated README.md
26 |
27 | ## 11.0 - 2024-03-19
28 | ### Changed
29 | - `composer.json` updated for Laravel 11.x
30 | - Updated README.md for new streamlined installation instructions
31 |
32 | ## 10.2 - 2024-02-22
33 | ### Changed
34 | - Refixed a problem in the `queue:listen` command :)
35 |
36 | ## 10.1 - 2024-02-18
37 | ### Changed
38 | - Fixed a problem in the `queue:listen` command
39 |
40 | ## 10.0 - 2023-02-20
41 | ### Changed
42 | - Integrated [PR #72](https://github.com/gecche/laravel-multidomain/pull/72) for adding more output options to the
43 | `domain:list` command
44 | - Integrated [PR #88](https://github.com/gecche/laravel-multidomain/pull/88) for better handling git files in local
45 | development
46 | - Version numbering following Laravel versions from 10.x
47 | - Updated some methods to adapt to Laravel 10.x
48 | - `composer.json` updated for Laravel 10.x
49 |
50 | ## 5.0 - 2022-02-08
51 | ### Changed
52 | - Updated some core methods to adapt to Laravel 9.x changes
53 | - `composer.json` updated for Laravel 9.x
54 |
55 | ## 4.2.1 - 2022-02-04
56 | ### Changed
57 | - Improved PHP 8.1 compatibility, see [PR #68](https://github.com/gecche/laravel-multidomain/pull/68)
58 | - README.md updated for misleading `update_env` command's instructions
59 |
60 | ## 4.2 - 2021-02-13
61 | ### Added
62 | - New `Application`'s constructor param `domainParams` for storing miscellaneous params used for handling domains:
63 | the first one is the `domain_detection_function_web` param which accept a `Closure` for customizing
64 | the doamin detection function instead of relying upon `$_SERVER['SERVER_NAME']` value
65 | ### Changed
66 | - README.md updated
67 |
68 | ## 4.1 - 2020-11-17
69 | ### Changed
70 | - Updated `queue:listen` command adapting it to the new Laravel 8.x signature
71 | - Compatibility with Laravel Horizon
72 |
73 | ## 4.0 - 2020-10-07
74 | ### Changed
75 | - `composer.json` updated for Laravel 8.x
76 |
77 | ## 3.9 - 2021-02-13
78 | ### Added
79 | - New `Application`'s constructor param `domainParams` for storing miscellaneous params used for handling domains:
80 | the first one is the `domain_detection_function_web` param which accept a `Closure` for customizing
81 | the doamin detection function instead of relying upon `$_SERVER['SERVER_NAME']` value
82 | ### Changed
83 | - README.md updated
84 |
85 | ## 3.8 - 2020-10-07
86 | ### Changed
87 | - New logic for default environment files and storage folders
88 | - Tests improved and updated
89 | - README.md updated
90 |
91 | ## 3.7 - 2020-05-24
92 | ### Changed
93 | - README.md updated with some minor correction and frontend image. (I've done a mistake with tag names:
94 | from 3.2 to 3.7 instead of 3.3 :) )
95 |
96 | ## 3.2 - 2020-04-23
97 | ### Changed
98 | - Improved robustness in `environmentFileDomain` when no domain is given.
99 |
100 | ## 3.1 - 2020-04-19
101 | ### Added
102 | - An optional argument to the Application constructor for handling the storage of env files in a different folder
103 | than the root Laravel's app folder. With new tests.
104 | - The `env_path` helper to get the folder in which env files are placed.
105 | ### Changed
106 | - README.md updated
107 |
108 | ## 3.0 - 2020-03-09
109 | ### Changed
110 | - `composer.json` updated for Laravel 7.x
111 |
112 | ## 2.9 - 2021-02-13
113 | ### Added
114 | - New `Application`'s constructor param `domainParams` for storing miscellaneous params used for handling domains:
115 | the first one is the `domain_detection_function_web` param which accept a `Closure` for customizing
116 | the doamin detection function instead of relying upon `$_SERVER['SERVER_NAME']` value
117 | ### Changed
118 | - README.md updated
119 |
120 | ## 2.8 - 2020-10-07
121 | ### Changed
122 | - New logic for default environment files and storage folders
123 | - Tests improved and updated
124 | - README.md updated
125 |
126 | ## 2.7 - 2020-05-24
127 | ### Changed
128 | - README.md updated with some minor correction and frontend image.
129 |
130 | ## 2.6 - 2020-04-23
131 | ### Changed
132 | - Improved robustness in `environmentFileDomain` when no domain is given.
133 |
134 | ## 2.5 - 2020-04-19
135 | ### Added
136 | - An optional argument to the Application constructor for handling the storage of env files in a different folder
137 | than the root Laravel's app folder. With new tests.
138 | - The `env_path` helper to get the folder in which env files are placed.
139 | ### Changed
140 | - README.md updated
141 |
142 | ## 2.4 - 2020-03-09
143 | ### Changed
144 | - Force the detection of the domain when trying to access to domain info if the domain has never been detected
145 | - Improved tests
146 |
147 | ## 2.3 - 2020-01-17
148 | ### Changed
149 | - README.md: added notes on `storage:link` command
150 |
151 | ## 2.2 - 2019-11-15
152 | ### Changed
153 | - Changed `Gecche\Multidomain\Foundation\Application` for handling separated cache files for each
154 | domain when using the `route:cache` or `event:cache` Laravel commands
155 |
156 | ## 2.1 - 2019-11-12
157 | ### Changed
158 | - Bugfix in `Gecche\Multidomain\Foundation\Console\DomainCommandTrait`: running the
159 | `domain:add` command, sometimes some lines in the stub .env file were
160 | skipped and not replicated in the new .env. file
161 |
162 | ## 2.0 - 2019-10-29
163 | ### Changed
164 | - `composer.json` updated for Laravel 6.x
165 |
166 | ## 1.4.9 - 2021-02-13
167 | ### Added
168 | - New `Application`'s constructor param `domainParams` for storing miscellaneous params used for handling domains:
169 | the first one is the `domain_detection_function_web` param which accept a `Closure` for customizing
170 | the doamin detection function instead of relying upon `$_SERVER['SERVER_NAME']` value
171 | ### Changed
172 | - README.md updated
173 |
174 | ## 1.4.8 - 2020-10-07
175 | ### Changed
176 | - New logic for default environment files and storage folders
177 | - Tests improved and updated
178 | - README.md updated
179 |
180 | ## 1.4.7 - 2020-05-24
181 | ### Changed
182 | - README.md updated with some minor correction and frontend image.
183 |
184 | ## 1.4.6 - 2020-04-23
185 | ### Changed
186 | - Improved robustness in `environmentFileDomain` when no domain is given.
187 |
188 | ## 1.4.5 - 2020-04-19
189 | ### Added
190 | - An optional argument to the Application constructor for handling the storage of env files in a different folder
191 | than the root Laravel's app folder. With new tests.
192 | - The `env_path` helper to get the folder in which env files are placed.
193 | ### Changed
194 | - README.md updated
195 |
196 | ## 1.4.4 - 2020-03-09
197 | ### Changed
198 | - Force the detection of the domain when trying to access to domain info if the domain has never been detected
199 | - Improved tests
200 |
201 | ## 1.4.3 - 2020-01-17
202 | ### Changed
203 | - README.md: added notes on `storage:link` command
204 |
205 | ## 1.4.2 - 2019-11-15
206 | ### Changed
207 | - Changed `Gecche\Multidomain\Foundation\Application` for handling separated cache files for each
208 | domain when using the `route:cache` or `event:cache` Laravel commands
209 |
210 | ## 1.4.1 - 2019-11-12
211 | ### Changed
212 | - Bugfix in `Gecche\Multidomain\Foundation\Console\DomainCommandTrait`: running the
213 | `domain:add` command, sometimes some lines in the stub .env file were
214 | skipped and not replicated in the new .env. file
215 |
216 | ## 1.4.0 - 2019-10-29
217 | ### Changed
218 | - `composer.json` updated for Laravel 5.8
219 |
220 | ## 1.3.9 - 2021-02-13
221 | ### Added
222 | - New `Application`'s constructor param `domainParams` for storing miscellaneous params used for handling domains:
223 | the first one is the `domain_detection_function_web` param which accept a `Closure` for customizing
224 | the doamin detection function instead of relying upon `$_SERVER['SERVER_NAME']` value
225 | ### Changed
226 | - README.md updated
227 |
228 | ## 1.3.8 - 2020-10-07
229 | ### Changed
230 | - New logic for default environment files and storage folders
231 | - Tests improved and updated
232 | - README.md updated
233 |
234 | ## 1.3.7 - 2020-05-24
235 | ### Changed
236 | - README.md updated with some minor correction and frontend image.
237 |
238 | ## 1.3.6 - 2020-04-23
239 | ### Changed
240 | - Improved robustness in `environmentFileDomain` when no domain is given.
241 |
242 | ## 1.3.5 - 2020-04-19
243 | ### Added
244 | - An optional argument to the Application constructor for handling the storage of env files in a different folder
245 | than the root Laravel's app folder. With new tests.
246 | - The `env_path` helper to get the folder in which env files are placed.
247 | ### Changed
248 | - README.md updated
249 |
250 | ## 1.3.4 - 2020-03-09
251 | ### Changed
252 | - Force the detection of the domain when trying to access to domain info if the domain has never been detected
253 | - Improved tests
254 |
255 | ## 1.3.3 - 2020-01-17
256 | ### Changed
257 | - README.md: added notes on `storage:link` command
258 |
259 | ## 1.3.2 - 2019-11-15
260 | ### Changed
261 | - Changed `Gecche\Multidomain\Foundation\Application` for handling separated cache files for each
262 | domain when using the `route:cache` Laravel command
263 |
264 | ## 1.3.1 - 2019-11-12
265 | ### Changed
266 | - Bugfix in `Gecche\Multidomain\Foundation\Console\DomainCommandTrait`: running the
267 | `domain:add` command, sometimes some lines in the stub .env file were
268 | skipped and not replicated in the new .env. file
269 |
270 | ## 1.3.0 - 2019-10-29
271 | ### Changed
272 | - `composer.json` updated for Laravel 5.7
273 | - Bugfix in `Gecche\Multidomain\Queue\Listener` due to changes in handling
274 | worker commands in the parent class.
275 |
276 | ## 1.2.9 - 2021-02-13
277 | ### Added
278 | - New `Application`'s constructor param `domainParams` for storing miscellaneous params used for handling domains:
279 | the first one is the `domain_detection_function_web` param which accept a `Closure` for customizing
280 | the doamin detection function instead of relying upon `$_SERVER['SERVER_NAME']` value
281 | ### Changed
282 | - README.md updated
283 |
284 | ## 1.2.8 - 2020-10-06
285 | ### Changed
286 | - New logic for default environment files and storage folders
287 | - Tests improved and updated
288 | - README.md updated
289 |
290 | ## 1.2.7 - 2020-05-24
291 | ### Changed
292 | - README.md updated with some minor correction and frontend image.
293 |
294 | ## 1.2.6 - 2020-04-23
295 | ### Changed
296 | - Improved robustness in `environmentFileDomain` when no domain is given.
297 |
298 | ## 1.2.5 - 2020-04-19
299 | ### Added
300 | - An optional argument to the Application constructor for handling the storage of env files in a different folder
301 | than the root Laravel's app folder. With new tests.
302 | - The `env_path` helper to get the folder in which env files are placed.
303 | ### Changed
304 | - README.md updated
305 |
306 | ## 1.2.4 - 2020-03-09
307 | ### Changed
308 | - Force the detection of the domain when trying to access to domain info if the domain has never been detected
309 | - Improved tests
310 |
311 | ## 1.2.3 - 2020-01-17
312 | ### Changed
313 | - README.md: added notes on `storage:link` command
314 |
315 | ## 1.2.2 - 2019-11-15
316 | ### Changed
317 | - Changed `Gecche\Multidomain\Foundation\Application` for handling separated cache files for each
318 | domain when using the `route:cache` Laravel command
319 |
320 | ## 1.2.1 - 2019-11-12
321 | ### Changed
322 | - Bugfix in `Gecche\Multidomain\Foundation\Console\DomainCommandTrait`: running the
323 | `domain:add` command, sometimes some lines in the stub .env file were
324 | skipped and not replicated in the new .env. file
325 |
326 | ## 1.2.0 - 2019-10-29
327 | ### Changed
328 | - `composer.json` updated for Laravel 5.6
329 |
330 | ## 1.1.15 - 2021-02-13
331 | ### Added
332 | - New `Application`'s constructor param `domainParams` for storing miscellaneous params used for handling domains:
333 | the first one is the `domain_detection_function_web` param which accept a `Closure` for customizing
334 | the doamin detection function instead of relying upon `$_SERVER['SERVER_NAME']` value
335 | ### Changed
336 | - README.md updated
337 |
338 | ## 1.1.14 - 2020-10-06
339 | ### Changed
340 | - New logic for default environment files and storage folders
341 | - Tests improved and updated
342 | - README.md updated
343 |
344 | ## 1.1.13 - 2020-05-24
345 | ### Changed
346 | - README.md updated with some minor correction and frontend image.
347 |
348 | ## 1.1.12 - 2020-04-23
349 | ### Changed
350 | - Improved robustness in `environmentFileDomain` when no domain is given.
351 |
352 | ## 1.1.11 - 2020-04-16
353 | ### Added
354 | - An optional argument to the Application constructor for handling the storage of env files in a different folder
355 | than the root Laravel's app folder. With new tests.
356 | - The `env_path` helper to get the folder in which env files are placed.
357 | ### Changed
358 | - README.md updated
359 |
360 | ## 1.1.10 - 2020-03-09
361 | ### Changed
362 | - Force the detection of the domain when trying to access to domain info if the domain has never been detected
363 | - Improved tests
364 |
365 | ## 1.1.9 - 2020-01-17
366 | ### Changed
367 | - README.md: added notes on `storage:link` command
368 |
369 | ## 1.1.8 - 2019-11-15
370 | ### Changed
371 | - Changed `Gecche\Multidomain\Foundation\Application` for handling separated cache files for each
372 | domain when using the `route:cache` Laravel command
373 |
374 | ## 1.1.7 - 2019-11-12
375 | ### Changed
376 | - Bugfix in `Gecche\Multidomain\Foundation\Console\DomainCommandTrait`: running the
377 | `domain:add` command, sometimes some lines in the stub .env file were
378 | skipped and not replicated in the new .env. file
379 |
380 | ## 1.1.6 - 2019-10-29
381 | ### Added
382 | - Test suites
383 | ### Changed
384 | - Namespace of `DomainConsoleServiceProvider` provider from
385 | `Gecche\Multidomain\Foundation` to `Gecche\Multidomain\Foundation\Providers`
386 | in order to respect folder struture.
387 | - `composer.json` file for testing purposes (from now Git branches are separated for each
388 | Laravel release starting from 5.5 and as pointed out in the docs)
389 |
390 | ## 1.1.5 - 2019-09-19
391 | ### Changed
392 | - Compatibility with Laravel 6.x (note that previous version 1.1.4 is also compatible with Laravel 6.0.x including [laravel/helpers](https://github.com/laravel/helpers) in the composer.json. file of your Laravel Application)
393 |
394 | ## 1.1.4 - 2019-05-25
395 | ### Changed
396 | - Better English in README.md :) Thanks to leadegroot.
397 |
398 | ## 1.1.3 - 2019-05-22
399 | ### Changed
400 | - README.md improved with queue example
401 |
402 | ## 1.1.2 - 2019-03-23
403 | ### Added
404 | - Added new domain:list artisan command displaying info of installed domains
405 | - Added domainsList() method to Application
406 |
407 | ## 1.1.1 - 2019-03-10
408 | ### Added
409 | - This CHANGELOG.md file.
410 | - Added the handling of config:cache artisan command by multiple cache config files
411 |
412 | ## 1.1.0 - 2018-06-24
413 | ### Added
414 | - Initial version for Laravel 5.5.
415 |
416 | [Unreleased]: https://github.com/gecche/laravel-multidomain/compare/v12.1...HEAD
417 | [12.1]: https://github.com/gecche/laravel-multidomain/compare/v12.1...v12.0
418 | [12.0]: https://github.com/gecche/laravel-multidomain/compare/v12.0...v11.2
419 | [11.2]: https://github.com/gecche/laravel-multidomain/compare/v11.2...v11.1
420 | [11.1]: https://github.com/gecche/laravel-multidomain/compare/v11.0...v11.1
421 | [11.0]: https://github.com/gecche/laravel-multidomain/compare/v10.2...v11.0
422 | [10.2]: https://github.com/gecche/laravel-multidomain/compare/v10.1...v10.2
423 | [10.1]: https://github.com/gecche/laravel-multidomain/compare/v10.0...v10.1
424 | [10.0]: https://github.com/gecche/laravel-multidomain/compare/v5.0...v10.0
425 | [5.0]: https://github.com/gecche/laravel-multidomain/compare/v4.2.1...v5.0
426 | [4.2.1]: https://github.com/gecche/laravel-multidomain/compare/v4.2...v4.2.1
427 | [4.2]: https://github.com/gecche/laravel-multidomain/compare/v4.1...v4.2
428 | [4.1]: https://github.com/gecche/laravel-multidomain/compare/v4.0...v4.1
429 | [4.0]: https://github.com/gecche/laravel-multidomain/compare/v3.8...v4.0
430 | [3.9]: https://github.com/gecche/laravel-multidomain/compare/v3.8...v3.9
431 | [3.8]: https://github.com/gecche/laravel-multidomain/compare/v3.7...v3.8
432 | [3.7]: https://github.com/gecche/laravel-multidomain/compare/v3.2...v3.7
433 | [3.2]: https://github.com/gecche/laravel-multidomain/compare/v3.1...v3.2
434 | [3.1]: https://github.com/gecche/laravel-multidomain/compare/v3.0...v3.1
435 | [3.0]: https://github.com/gecche/laravel-multidomain/compare/v2.4...v3.0
436 | [2.9]: https://github.com/gecche/laravel-multidomain/compare/v2.8...v2.9
437 | [2.8]: https://github.com/gecche/laravel-multidomain/compare/v2.7...v2.8
438 | [2.7]: https://github.com/gecche/laravel-multidomain/compare/v2.6...v2.7
439 | [2.6]: https://github.com/gecche/laravel-multidomain/compare/v2.5...v2.6
440 | [2.5]: https://github.com/gecche/laravel-multidomain/compare/v2.4...v2.5
441 | [2.4]: https://github.com/gecche/laravel-multidomain/compare/v2.3...v2.4
442 | [2.3]: https://github.com/gecche/laravel-multidomain/compare/v2.2...v2.3
443 | [2.2]: https://github.com/gecche/laravel-multidomain/compare/v2.1...v2.2
444 | [2.1]: https://github.com/gecche/laravel-multidomain/compare/v2.0...v2.1
445 | [2.0]: https://github.com/gecche/laravel-multidomain/compare/v1.4.0...v2.0
446 | [1.4.9]: https://github.com/gecche/laravel-multidomain/compare/v1.4.8...v1.4.9
447 | [1.4.8]: https://github.com/gecche/laravel-multidomain/compare/v1.4.7...v1.4.8
448 | [1.4.7]: https://github.com/gecche/laravel-multidomain/compare/v1.4.6...v1.4.7
449 | [1.4.6]: https://github.com/gecche/laravel-multidomain/compare/v1.4.5...v1.4.6
450 | [1.4.5]: https://github.com/gecche/laravel-multidomain/compare/v1.4.4...v1.4.5
451 | [1.4.4]: https://github.com/gecche/laravel-multidomain/compare/v1.4.3...v1.4.4
452 | [1.4.3]: https://github.com/gecche/laravel-multidomain/compare/v1.4.2...v1.4.3
453 | [1.4.2]: https://github.com/gecche/laravel-multidomain/compare/v1.4.1...v1.4.2
454 | [1.4.1]: https://github.com/gecche/laravel-multidomain/compare/v1.4.0...v1.4.1
455 | [1.4.0]: https://github.com/gecche/laravel-multidomain/compare/v1.3.0...v1.4.0
456 | [1.3.9]: https://github.com/gecche/laravel-multidomain/compare/v1.3.8...v1.3.9
457 | [1.3.8]: https://github.com/gecche/laravel-multidomain/compare/v1.3.7...v1.3.8
458 | [1.3.7]: https://github.com/gecche/laravel-multidomain/compare/v1.3.6...v1.3.7
459 | [1.3.6]: https://github.com/gecche/laravel-multidomain/compare/v1.3.5...v1.3.6
460 | [1.3.5]: https://github.com/gecche/laravel-multidomain/compare/v1.3.4...v1.3.5
461 | [1.3.4]: https://github.com/gecche/laravel-multidomain/compare/v1.3.3...v1.3.4
462 | [1.3.3]: https://github.com/gecche/laravel-multidomain/compare/v1.3.2...v1.3.3
463 | [1.3.2]: https://github.com/gecche/laravel-multidomain/compare/v1.3.1...v1.3.2
464 | [1.3.1]: https://github.com/gecche/laravel-multidomain/compare/v1.3.0...v1.3.1
465 | [1.3.0]: https://github.com/gecche/laravel-multidomain/compare/v1.2.0...v1.3.0
466 | [1.2.9]: https://github.com/gecche/laravel-multidomain/compare/v1.2.8...v1.2.9
467 | [1.2.8]: https://github.com/gecche/laravel-multidomain/compare/v1.2.7...v1.2.8
468 | [1.2.7]: https://github.com/gecche/laravel-multidomain/compare/v1.2.6...v1.2.7
469 | [1.2.6]: https://github.com/gecche/laravel-multidomain/compare/v1.2.5...v1.2.6
470 | [1.2.5]: https://github.com/gecche/laravel-multidomain/compare/v1.2.4...v1.2.5
471 | [1.2.4]: https://github.com/gecche/laravel-multidomain/compare/v1.2.3...v1.2.4
472 | [1.2.3]: https://github.com/gecche/laravel-multidomain/compare/v1.2.2...v1.2.3
473 | [1.2.2]: https://github.com/gecche/laravel-multidomain/compare/v1.2.1...v1.2.2
474 | [1.2.1]: https://github.com/gecche/laravel-multidomain/compare/v1.2.0...v1.2.1
475 | [1.2.0]: https://github.com/gecche/laravel-multidomain/compare/v1.1.6...v1.2.0
476 | [1.1.15]: https://github.com/gecche/laravel-multidomain/compare/v1.1.14...v1.1.15
477 | [1.1.14]: https://github.com/gecche/laravel-multidomain/compare/v1.1.13...v1.1.14
478 | [1.1.13]: https://github.com/gecche/laravel-multidomain/compare/v1.1.12...v1.1.13
479 | [1.1.12]: https://github.com/gecche/laravel-multidomain/compare/v1.1.11...v1.1.12
480 | [1.1.11]: https://github.com/gecche/laravel-multidomain/compare/v1.1.10...v1.1.11
481 | [1.1.10]: https://github.com/gecche/laravel-multidomain/compare/v1.1.9...v1.1.10
482 | [1.1.9]: https://github.com/gecche/laravel-multidomain/compare/v1.1.8...v1.1.9
483 | [1.1.8]: https://github.com/gecche/laravel-multidomain/compare/v1.1.7...v1.1.8
484 | [1.1.7]: https://github.com/gecche/laravel-multidomain/compare/v1.1.6...v1.1.7
485 | [1.1.6]: https://github.com/gecche/laravel-multidomain/compare/v1.1.5...v1.1.6
486 | [1.1.5]: https://github.com/gecche/laravel-multidomain/compare/v1.1.4...v1.1.5
487 | [1.1.4]: https://github.com/gecche/laravel-multidomain/compare/v1.1.3...v1.1.4
488 | [1.1.3]: https://github.com/gecche/laravel-multidomain/compare/v1.1.2...v1.1.3
489 | [1.1.2]: https://github.com/gecche/laravel-multidomain/compare/v1.1.1...v1.1.2
490 | [1.1.1]: https://github.com/gecche/laravel-multidomain/compare/v1.1.0...v1.1.1
491 |
492 | [reference]: https://keepachangelog.com/en/1.0.0/
493 |
--------------------------------------------------------------------------------