From 15f09e59dfb86c0a018c11e5278ee6f7f5750db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Fri, 9 Aug 2019 19:06:00 +0200 Subject: [PATCH 01/15] [1.7.0] Add tenants:run (#81) * wip * Apply fixes from StyleCI * first implementation * Apply fixes from StyleCI * Add support for arguments and options * Apply fixes from StyleCI * Write docs, add support for = in arg/opt value * Apply fixes from StyleCI * add $ [ci skip] --- README.md | 14 ++++++++ src/Commands/Run.php | 66 ++++++++++++++++++++++++++++++++++ src/TenancyServiceProvider.php | 4 ++- tests/CommandsTest.php | 22 ++++++++++-- tests/Etc/ConsoleKernel.php | 17 +++++++++ tests/Etc/ExampleCommand.php | 39 ++++++++++++++++++++ tests/TestCase.php | 11 ++++++ 7 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 src/Commands/Run.php create mode 100644 tests/Etc/ConsoleKernel.php create mode 100644 tests/Etc/ExampleCommand.php diff --git a/README.md b/README.md index 31f03153..18b0ec15 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ You won't have to change a thing in your application's code.\* * [Artisan commands](#artisan-commands) - [`tenants:list`](#-tenants-list-) - [`tenants:migrate`, `tenants:rollback`, `tenants:seed`](#-tenants-migrate----tenants-rollback----tenants-seed-) + - [Running your commands for tenants](#running-your-commands-for-tenants) + [Tenant migrations](#tenant-migrations) * [Testing](#testing) - [Tips](#tips) @@ -487,6 +488,7 @@ Available commands for the "tenants" namespace: tenants:list List tenants. tenants:migrate Run migrations for tenant(s) tenants:rollback Rollback migrations for tenant(s). + tenants:run Run a command for tenant(s). tenants:seed Seed tenant database(s). ``` @@ -509,6 +511,18 @@ Tenant: 8075a580-1cb8-11e9-8822-49c5d8f8ff23 (laravel.localhost) Database seeding completed successfully. ``` +### Running your commands for tenants + +You can use the `tenants:run` command to run your own commands for tenants. + +If your command's signature were `email:send {user} {--queue} {--subject} {body}`, you would run this command like this: + +``` +$ artisan tenants:run email:send --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23 --option="queue=1" --option="subject=New Feature" --argument="body=We have launched a new feature. ..." +``` + +The `=` separates the argument/option name from its value, but you can still use `=` in the argument's value. + ### Tenant migrations Tenant migrations are located in `database/migrations/tenant`, so you should move your tenant migrations there. diff --git a/src/Commands/Run.php b/src/Commands/Run.php new file mode 100644 index 00000000..d3e00861 --- /dev/null +++ b/src/Commands/Run.php @@ -0,0 +1,66 @@ +initialized) { + $previous_tenants_domain = tenant('domain'); + } + + tenant()->all($this->option('tenants'))->each(function ($tenant) { + $this->line("Tenant: {$tenant['uuid']} ({$tenant['domain']})"); + tenancy()->init($tenant['domain']); + + $callback = function ($prefix = '') { + return function ($arguments, $argument) use ($prefix) { + [$key, $value] = explode('=', $argument, 2); + $arguments[$prefix . $key] = $value; + + return $arguments; + }; + }; + + // Turns ['foo=bar', 'abc=xyz=zzz'] into ['foo' => 'bar', 'abc' => 'xyz=zzz'] + $arguments = array_reduce($this->option('argument'), $callback(), []); + + // Turns ['foo=bar', 'abc=xyz=zzz'] into ['--foo' => 'bar', '--abc' => 'xyz=zzz'] + $options = array_reduce($this->option('option'), $callback('--'), []); + + // Run command + $this->call($this->argument('commandname'), array_merge($arguments, $options)); + + tenancy()->end(); + }); + + if ($tenancy_was_initialized) { + tenancy()->init($previous_tenants_domain); + } + } +} diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php index 12005e9f..233b87bb 100644 --- a/src/TenancyServiceProvider.php +++ b/src/TenancyServiceProvider.php @@ -2,6 +2,7 @@ namespace Stancl\Tenancy; +use Stancl\Tenancy\Commands\Run; use Stancl\Tenancy\Commands\Seed; use Illuminate\Cache\CacheManager; use Stancl\Tenancy\Commands\Migrate; @@ -23,9 +24,10 @@ class TenancyServiceProvider extends ServiceProvider { if ($this->app->runningInConsole()) { $this->commands([ + Run::class, + Seed::class, Migrate::class, Rollback::class, - Seed::class, TenantList::class, ]); } diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php index 3998719d..d26b71c1 100644 --- a/tests/CommandsTest.php +++ b/tests/CommandsTest.php @@ -76,7 +76,7 @@ class CommandsTest extends TestCase } /** @test */ - public function database_connection_is_switched_to_default_after_migrating_or_seeding_or_rolling_back() + public function database_connection_is_switched_to_default() { $originalDBName = DB::connection()->getDatabaseName(); @@ -88,13 +88,29 @@ class CommandsTest extends TestCase Artisan::call('tenants:rollback'); $this->assertSame($originalDBName, DB::connection()->getDatabaseName()); + + $this->run_commands_works(); + $this->assertSame($originalDBName, DB::connection()->getDatabaseName()); } /** @test */ - public function database_connection_is_switched_to_default_after_migrating_or_seeding_or_rolling_back_when_tenancy_has_been_initialized() + public function database_connection_is_switched_to_default_when_tenancy_has_been_initialized() { tenancy()->init('localhost'); - $this->database_connection_is_switched_to_default_after_migrating_or_seeding_or_rolling_back(); + $this->database_connection_is_switched_to_default(); + } + + /** @test */ + public function run_commands_works() + { + $uuid = tenant()->create('run.localhost')['uuid']; + + Artisan::call('tenants:migrate', ['--tenants' => $uuid]); + + $this->artisan("tenants:run foo --tenants=$uuid --argument='a=foo' --option='b=bar' --option='c=xyz'") + ->expectsOutput("User's name is Test command") + ->expectsOutput('foo') + ->expectsOutput('xyz'); } } diff --git a/tests/Etc/ConsoleKernel.php b/tests/Etc/ConsoleKernel.php new file mode 100644 index 00000000..a0d82a84 --- /dev/null +++ b/tests/Etc/ConsoleKernel.php @@ -0,0 +1,17 @@ + 999, + 'name' => 'Test command', + 'email' => 'test@command.com', + 'password' => bcrypt('password'), + ]); + + $this->line("User's name is " . User::find(999)->name); + $this->line($this->argument('a')); + $this->line($this->option('c')); + } +} + +class User extends \Illuminate\Database\Eloquent\Model +{ + protected $guarded = []; +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 7cc55500..3bc0889f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -130,6 +130,17 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase $app->singleton('Illuminate\Contracts\Http\Kernel', Etc\HttpKernel::class); } + /** + * Resolve application Console Kernel implementation. + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + protected function resolveApplicationConsoleKernel($app) + { + $app->singleton('Illuminate\Contracts\Console\Kernel', Etc\ConsoleKernel::class); + } + public function randomString(int $length = 10) { return substr(str_shuffle(str_repeat($x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil($length / strlen($x)))), 1, $length); From aec12ae1283dc095202415328f93fb08e6e1f17a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Fri, 9 Aug 2019 19:40:45 +0200 Subject: [PATCH 02/15] Collapse HTTPS certs section --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 31f03153..a97f16c8 100644 --- a/README.md +++ b/README.md @@ -530,6 +530,9 @@ To do this automatically, you can make this part of your `TestCase::setUp()` met ## HTTPS certificates +
+Click to expand/collapse + HTTPS certificates are very easy to deal with if you use the `yourclient1.yourapp.com`, `yourclient2.yourapp.com` model. You can use a wildcard HTTPS certificate. If you use the model where second level domains are used, there are multiple ways you can solve this. @@ -562,6 +565,8 @@ Creating this config dynamically from PHP is not easy, but is probably feasible. However, you still need to reload nginx configuration to apply the changes to configuration. This is problematic and I'm not sure if there is a simple and secure way to do this from PHP. +
+ # Development ## Running tests From d77d05e620546c24de027ad88fb17999c2596e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Sat, 10 Aug 2019 10:59:23 +0200 Subject: [PATCH 03/15] Use env vars instead of a python script for ./test --- test | 33 ++++++++------------------------- tests/TestCase.php | 18 ++---------------- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/test b/test index 0d11eef3..5516ec57 100755 --- a/test +++ b/test @@ -1,26 +1,9 @@ -#!/usr/bin/env python3 -from os import system -import argparse +#!/bin/bash -system('docker-compose up -d') - -parser = argparse.ArgumentParser() -parser.add_argument("--variants", default='1,2', - help="Comma-separated values. Which test variants should be run.") -args, other = parser.parse_known_args() - -variants = args.variants.split(',') - -for variant in variants: - filename_base = "phpunit_var_" + variant - with open('phpunit.xml', 'r') as inp, open(filename_base + '.xml', 'w') as out: - out.write(inp.read().replace('"STANCL_TENANCY_TEST_VARIANT" value="1"', - '"STANCL_TENANCY_TEST_VARIANT" value="%s"' % variant)) - - print("Test variant: %s\n" % variant) - - system('docker-compose exec test vendor/bin/phpunit --configuration "%s" --coverage-php %s %s' - % (filename_base + '.xml', 'coverage/' + filename_base + '.cov', ' '.join(other))) - -# todo delete folder contents first? -system("docker-compose exec test vendor/bin/phpcov merge --clover clover.xml coverage/") +# for development +docker-compose up -d +printf "Variant 1\n\n" +TENANCY_TEST_REDIS_TENANCY=1 TENANCY_TEST_REDIS_CLIENT=phpredis docker-compose exec test vendor/bin/phpunit --coverage-php coverage/1.cov "$@" +printf "Variant 2\n\n" +TENANCY_TEST_REDIS_TENANCY=0 TENANCY_TEST_REDIS_CLIENT=predis docker-compose exec test vendor/bin/phpunit --coverage-php coverage/2.cov "$@" +docker-compose exec test vendor/bin/phpcov merge --clover clover.xml coverage/ \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index 3bc0889f..b5d28acd 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -60,7 +60,6 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase } $app['config']->set([ - 'database.redis.client' => 'phpredis', 'database.redis.cache.host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'), 'database.redis.default.host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'), 'database.redis.options.prefix' => 'foo', @@ -86,24 +85,11 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase 'public', 's3', ], - 'tenancy.redis.tenancy' => true, + 'tenancy.redis.tenancy' => env('TENANCY_TEST_REDIS_TENANCY', true), + 'database.redis.client' => env('TENANCY_TEST_REDIS_CLIENT', 'phpredis'), 'tenancy.redis.prefixed_connections' => ['default'], 'tenancy.migrations_directory' => database_path('../migrations'), ]); - - switch ((string) env('STANCL_TENANCY_TEST_VARIANT', '1')) { - case '2': - $app['config']->set([ - 'tenancy.redis.tenancy' => false, - 'database.redis.client' => 'predis', - ]); - break; - default: - $app['config']->set([ - 'tenancy.redis.tenancy' => true, - 'database.redis.client' => 'phpredis', - ]); - } } protected function getPackageProviders($app) From 7765eb37b20f93ba1819a7935b81e3bfca8df79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Sat, 10 Aug 2019 13:40:47 +0200 Subject: [PATCH 04/15] Update badges for the 1.x branch --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 79a6dca4..77218892 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![Laravel 5.8](https://img.shields.io/badge/laravel-5.8-red.svg)](https://laravel.com) [![Latest Stable Version](https://poser.pugx.org/stancl/tenancy/version)](https://packagist.org/packages/stancl/tenancy) -[![Travis CI build](https://travis-ci.com/stancl/tenancy.svg?branch=master)](https://travis-ci.com/stancl/tenancy) -[![codecov](https://codecov.io/gh/stancl/tenancy/branch/master/graph/badge.svg)](https://codecov.io/gh/stancl/tenancy) +[![Travis CI build](https://travis-ci.com/stancl/tenancy.svg?branch=1.x)](https://travis-ci.com/stancl/tenancy) +[![codecov](https://codecov.io/gh/stancl/tenancy/branch/1.x/graph/badge.svg)](https://codecov.io/gh/stancl/tenancy) ### *A Laravel multi-database tenancy package that respects your code.* From 490c776bc8d2d489dfdb76a0a9ad2fa742274bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Sat, 10 Aug 2019 14:43:37 +0200 Subject: [PATCH 05/15] Use ?? instead of ?: --- src/Middleware/InitializeTenancy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Middleware/InitializeTenancy.php b/src/Middleware/InitializeTenancy.php index 3c37360a..10f41819 100644 --- a/src/Middleware/InitializeTenancy.php +++ b/src/Middleware/InitializeTenancy.php @@ -8,7 +8,7 @@ class InitializeTenancy { public function __construct(Closure $onFail = null) { - $this->onFail = $onFail ?: function ($e) { + $this->onFail = $onFail ?? function ($e) { throw $e; }; } From e2d35bc18d40ff266044a82aaa9e875c1c70b5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Tue, 13 Aug 2019 18:36:16 +0200 Subject: [PATCH 06/15] Remove psysh dependency --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 59ee0810..1bd0a0aa 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,6 @@ }, "require-dev": { "vlucas/phpdotenv": "^3.3", - "psy/psysh": "@stable", "laravel/framework": "5.8.*", "orchestra/testbench": "~3.8", "league/flysystem-aws-s3-v3": "~1.0", From 769ec16f895833ccf5780cc79111c9bf2c4f38dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Tue, 13 Aug 2019 18:36:41 +0200 Subject: [PATCH 07/15] Fix #89 (#90) --- src/TenancyServiceProvider.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php index 233b87bb..4ac33bd5 100644 --- a/src/TenancyServiceProvider.php +++ b/src/TenancyServiceProvider.php @@ -22,15 +22,13 @@ class TenancyServiceProvider extends ServiceProvider */ public function boot() { - if ($this->app->runningInConsole()) { - $this->commands([ - Run::class, - Seed::class, - Migrate::class, - Rollback::class, - TenantList::class, - ]); - } + $this->commands([ + Run::class, + Seed::class, + Migrate::class, + Rollback::class, + TenantList::class, + ]); $this->publishes([ __DIR__ . '/config/tenancy.php' => config_path('tenancy.php'), From d7358c588cb414606bf927f63490fdb1d0a74934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Tue, 13 Aug 2019 19:16:00 +0200 Subject: [PATCH 08/15] [1.7.0] Add the option to set values & db name during tenant creation (#86) * Add the option to set values & db name during tenant creation * Apply fixes from StyleCI * Add tests * Apply fixes from StyleCI * Rewrite conditional for clarity --- README.md | 26 ++++++++++ .../CannotChangeUuidOrDomainException.php | 8 ++++ src/TenantManager.php | 32 ++++++++++++- src/config/tenancy.php | 1 + tests/TenantManagerTest.php | 47 +++++++++++++++++++ 5 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/Exceptions/CannotChangeUuidOrDomainException.php diff --git a/README.md b/README.md index 77218892..f893f82c 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,32 @@ You can use the `tenancy()` and `tenant()` helpers to resolve `Stancl\Tenancy\Te ] ``` +You can also put data into the storage during the tenant creation process: + +```php +>>> tenant()->create('dev.localhost', [ + 'plan' => 'basic' +]) +=> [ + "uuid" => "49670df0-1a87-11e9-b7ba-cf5353777957", + "domain" => "dev.localhost", + "plan" => "basic", + ] +``` + +If you want to specify the tenant's database name, set the `tenancy.database_name_key` configuration key to the name of the key that is used to specify the database name in the tenant storage. You must use a name that you won't use for storing other data, so it's recommended to avoid names like `database` and use names like `_stancl_tenancy_database_name` instead. Then just give the key a value during the tenant creation process: + +```php +>>> tenant()->create('example.com', [ + '_stancl_tenancy_database_name' => 'example_com' +]) +=> [ + "uuid" => "49670df0-1a87-11e9-b7ba-cf5353777957", + "domain" => "example.com", + "_stancl_tenancy_database_name" => "example_com", + ] +``` + When you create a new tenant, you can [migrate](#tenant-migrations) their database like this: ```php diff --git a/src/Exceptions/CannotChangeUuidOrDomainException.php b/src/Exceptions/CannotChangeUuidOrDomainException.php new file mode 100644 index 00000000..52817767 --- /dev/null +++ b/src/Exceptions/CannotChangeUuidOrDomainException.php @@ -0,0 +1,8 @@ +currentDomain(); @@ -79,6 +87,13 @@ class TenantManager } $tenant = $this->jsonDecodeArrayValues($this->storage->createTenant($domain, (string) \Webpatser\Uuid\Uuid::generate(1, $domain))); + + if ($data) { + $this->put($data, null, $tenant['uuid']); + + $tenant = array_merge($tenant, $data); + } + $this->database->create($this->getDatabaseName($tenant)); return $tenant; @@ -168,6 +183,12 @@ class TenantManager { $tenant = $tenant ?: $this->tenant; + if ($key = $this->app['config']['tenancy.database_name_key']) { + if (isset($tenant[$key])) { + return $tenant[$key]; + } + } + return $this->app['config']['tenancy.database.prefix'] . $tenant['uuid'] . $this->app['config']['tenancy.database.suffix']; } @@ -254,6 +275,15 @@ class TenantManager */ public function put($key, $value = null, string $uuid = null) { + if (in_array($key, ['uuid', 'domain'], true) || ( + is_array($key) && ( + in_array('uuid', array_keys($key), true) || + in_array('domain', array_keys($key), true) + ) + )) { + throw new CannotChangeUuidOrDomainException; + } + if (\is_null($uuid)) { if (! isset($this->tenant['uuid'])) { throw new \Exception('No UUID supplied (and no tenant is currently identified).'); diff --git a/src/config/tenancy.php b/src/config/tenancy.php index c33f8f63..6ebd7251 100644 --- a/src/config/tenancy.php +++ b/src/config/tenancy.php @@ -43,4 +43,5 @@ return [ ], 'queue_database_creation' => false, 'queue_database_deletion' => false, + 'database_name_key' => null, ]; diff --git a/tests/TenantManagerTest.php b/tests/TenantManagerTest.php index 050c729a..453730ea 100644 --- a/tests/TenantManagerTest.php +++ b/tests/TenantManagerTest.php @@ -4,6 +4,7 @@ namespace Stancl\Tenancy\Tests; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; +use Stancl\Tenancy\Exceptions\CannotChangeUuidOrDomainException; class TenantManagerTest extends TestCase { @@ -194,4 +195,50 @@ class TenantManagerTest extends TestCase $tenant2 = tenant()->create('bar.localhost'); $this->assertEqualsCanonicalizing([$tenant1, $tenant2], tenant()->all()->toArray()); } + + /** @test */ + public function properites_can_be_passed_in_the_create_method() + { + $data = ['plan' => 'free', 'subscribed_until' => '2020-01-01']; + $tenant = tenant()->create('foo.localhost', $data); + + $tenant_data = $tenant; + unset($tenant_data['uuid']); + unset($tenant_data['domain']); + + $this->assertSame($data, $tenant_data); + } + + /** @test */ + public function database_name_can_be_passed_in_the_create_method() + { + $database = 'abc'; + config(['tenancy.database_name_key' => '_stancl_tenancy_database_name']); + + $tenant = tenant()->create('foo.localhost', [ + '_stancl_tenancy_database_name' => $database, + ]); + + $this->assertSame($database, tenant()->getDatabaseName($tenant)); + } + + /** @test */ + public function uuid_and_domain_cannot_be_changed() + { + $tenant = tenant()->create('foo.localhost'); + + $this->expectException(CannotChangeUuidOrDomainException::class); + tenant()->put('uuid', 'foo', $tenant['uuid']); + + $this->expectException(CannotChangeUuidOrDomainException::class); + tenant()->put(['uuid' => 'foo'], null, $tenant['uuid']); + + tenancy()->init('foo.localhost'); + + $this->expectException(CannotChangeUuidOrDomainException::class); + tenant()->put('uuid', 'foo'); + + $this->expectException(CannotChangeUuidOrDomainException::class); + tenant()->put(['uuid' => 'foo']); + } } From c1df4676012e572c7af36f5afa0ad4e9eef39d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Wed, 14 Aug 2019 17:15:47 +0200 Subject: [PATCH 09/15] [1.7.0] Add Events system (#93) * Add TenantManagerEvents * Apply fixes from StyleCI * Fix typos, add tests * end() events --- src/Traits/BootstrapsTenancy.php | 10 +++++ src/Traits/TenantManagerEvents.php | 70 ++++++++++++++++++++++++++++++ tests/TenantManagerTest.php | 69 +++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 src/Traits/TenantManagerEvents.php diff --git a/src/Traits/BootstrapsTenancy.php b/src/Traits/BootstrapsTenancy.php index 5f2b74d3..cbfd9465 100644 --- a/src/Traits/BootstrapsTenancy.php +++ b/src/Traits/BootstrapsTenancy.php @@ -9,6 +9,8 @@ use Stancl\Tenancy\Exceptions\PhpRedisNotInstalledException; trait BootstrapsTenancy { + use TenantManagerEvents; + public $originalSettings = []; /** * Was tenancy initialized/bootstrapped? @@ -19,6 +21,10 @@ trait BootstrapsTenancy public function bootstrap() { + array_map(function ($listener) { + $listener($this); + }, $this->listeners['bootstrapping']); + $this->initialized = true; $this->switchDatabaseConnection(); @@ -27,6 +33,10 @@ trait BootstrapsTenancy } $this->tagCache(); $this->suffixFilesystemRootPaths(); + + array_map(function ($listener) { + $listener($this); + }, $this->listeners['bootstrapped']); } public function end() diff --git a/src/Traits/TenantManagerEvents.php b/src/Traits/TenantManagerEvents.php new file mode 100644 index 00000000..c54b322b --- /dev/null +++ b/src/Traits/TenantManagerEvents.php @@ -0,0 +1,70 @@ + [], + 'bootstrapped' => [], + 'ending' => [], + 'ended' => [], + ]; + + /** + * Register a listener that will be executed before tenancy is bootstrapped. + * + * @param callable $callback + * @return self + */ + public function bootstrapping(callable $callback) + { + $this->listeners['bootstrapping'][] = $callback; + + return $this; + } + + /** + * Register a listener that will be executed after tenancy is bootstrapped. + * + * @param callable $callback + * @return self + */ + public function bootstrapped(callable $callback) + { + $this->listeners['bootstrapped'][] = $callback; + + return $this; + } + + /** + * Register a listener that will be executed before tenancy is ended. + * + * @param callable $callback + * @return self + */ + public function ending(callable $callback) + { + $this->listeners['ending'][] = $callback; + + return $this; + } + + /** + * Register a listener that will be executed after tenancy is ended. + * + * @param callable $callback + * @return self + */ + public function ended(callable $callback) + { + $this->listeners['ended'][] = $callback; + + return $this; + } +} diff --git a/tests/TenantManagerTest.php b/tests/TenantManagerTest.php index 453730ea..faa2da3d 100644 --- a/tests/TenantManagerTest.php +++ b/tests/TenantManagerTest.php @@ -2,6 +2,7 @@ namespace Stancl\Tenancy\Tests; +use Tenancy; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; use Stancl\Tenancy\Exceptions\CannotChangeUuidOrDomainException; @@ -241,4 +242,72 @@ class TenantManagerTest extends TestCase $this->expectException(CannotChangeUuidOrDomainException::class); tenant()->put(['uuid' => 'foo']); } + + /** @test */ + public function bootstrapping_event_works() + { + $uuid = tenant()->create('foo.localhost')['uuid']; + + Tenancy::bootstrapping(function ($tenantManager) use ($uuid) { + if ($tenantManager->tenant['uuid'] === $uuid) { + config(['tenancy.foo' => 'bar']); + } + }); + + $this->assertSame(null, config('tenancy.foo')); + tenancy()->init('foo.localhost'); + $this->assertSame('bar', config('tenancy.foo')); + } + + /** @test */ + public function bootstrapped_event_works() + { + $uuid = tenant()->create('foo.localhost')['uuid']; + + Tenancy::bootstrapped(function ($tenantManager) use ($uuid) { + if ($tenantManager->tenant['uuid'] === $uuid) { + config(['tenancy.foo' => 'bar']); + } + }); + + $this->assertSame(null, config('tenancy.foo')); + tenancy()->init('foo.localhost'); + $this->assertSame('bar', config('tenancy.foo')); + } + + /** @test */ + public function ending_event_works() + { + $uuid = tenant()->create('foo.localhost')['uuid']; + + Tenancy::ending(function ($tenantManager) use ($uuid) { + if ($tenantManager->tenant['uuid'] === $uuid) { + config(['tenancy.foo' => 'bar']); + } + }); + + $this->assertSame(null, config('tenancy.foo')); + tenancy()->init('foo.localhost'); + $this->assertSame(null, config('tenancy.foo')); + tenancy()->end(); + $this->assertSame('bar', config('tenancy.foo')); + } + + /** @test */ + public function ended_event_works() + { + $uuid = tenant()->create('foo.localhost')['uuid']; + + Tenancy::ended(function ($tenantManager) use ($uuid) { + if ($tenantManager->tenant['uuid'] === $uuid) { + config(['tenancy.foo' => 'bar']); + } + }); + + $this->assertSame(null, config('tenancy.foo')); + tenancy()->init('foo.localhost'); + $this->assertSame(null, config('tenancy.foo')); + tenancy()->end(); + $this->assertSame('bar', config('tenancy.foo')); + } } From 1a88cad4d684ad8b474db324693bfe4a527bcbe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Wed, 14 Aug 2019 17:32:33 +0200 Subject: [PATCH 10/15] [1.7.0] Fix Events system (#94) * Add TenantManagerEvents * Apply fixes from StyleCI * Fix typos, add tests * end() events * Travis should be failing * Uncomment ending --- src/Traits/BootstrapsTenancy.php | 8 ++++++++ test | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Traits/BootstrapsTenancy.php b/src/Traits/BootstrapsTenancy.php index cbfd9465..624b2180 100644 --- a/src/Traits/BootstrapsTenancy.php +++ b/src/Traits/BootstrapsTenancy.php @@ -41,6 +41,10 @@ trait BootstrapsTenancy public function end() { + array_map(function ($listener) { + $listener($this); + }, $this->listeners['ending']); + $this->initialized = false; $this->disconnectDatabase(); @@ -49,6 +53,10 @@ trait BootstrapsTenancy } $this->untagCache(); $this->resetFileSystemRootPaths(); + + array_map(function ($listener) { + $listener($this); + }, $this->listeners['ended']); } public function switchDatabaseConnection() diff --git a/test b/test index 5516ec57..ff55e60b 100755 --- a/test +++ b/test @@ -1,4 +1,5 @@ #!/bin/bash +set -e # for development docker-compose up -d @@ -6,4 +7,4 @@ printf "Variant 1\n\n" TENANCY_TEST_REDIS_TENANCY=1 TENANCY_TEST_REDIS_CLIENT=phpredis docker-compose exec test vendor/bin/phpunit --coverage-php coverage/1.cov "$@" printf "Variant 2\n\n" TENANCY_TEST_REDIS_TENANCY=0 TENANCY_TEST_REDIS_CLIENT=predis docker-compose exec test vendor/bin/phpunit --coverage-php coverage/2.cov "$@" -docker-compose exec test vendor/bin/phpcov merge --clover clover.xml coverage/ \ No newline at end of file +docker-compose exec test vendor/bin/phpcov merge --clover clover.xml coverage/ From 4aa35322dae6e3126b6c88e1c8b62f43dc7fd9cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Wed, 14 Aug 2019 22:16:51 +0200 Subject: [PATCH 11/15] Add event prevents, Tenant facade --- composer.json | 1 + src/DatabaseManager.php | 10 ++- src/TenantManager.php | 4 +- src/Traits/BootstrapsTenancy.php | 59 +++++++++------ src/Traits/TenantManagerEvents.php | 15 ++++ tests/FacadeTest.php | 11 +++ tests/TenantManagerEventsTest.php | 114 +++++++++++++++++++++++++++++ tests/TenantManagerTest.php | 69 ----------------- tests/TestCase.php | 1 + 9 files changed, 187 insertions(+), 97 deletions(-) create mode 100644 tests/TenantManagerEventsTest.php diff --git a/composer.json b/composer.json index 1bd0a0aa..78357d1c 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ ], "aliases": { "Tenancy": "Stancl\\Tenancy\\TenancyFacade", + "Tenant": "Stancl\\Tenancy\\TenancyFacade", "GlobalCache": "Stancl\\Tenancy\\GlobalCacheFacade" } } diff --git a/src/DatabaseManager.php b/src/DatabaseManager.php index 47bee9fc..e494ac69 100644 --- a/src/DatabaseManager.php +++ b/src/DatabaseManager.php @@ -6,7 +6,7 @@ use Stancl\Tenancy\Jobs\QueuedTenantDatabaseCreator; use Stancl\Tenancy\Jobs\QueuedTenantDatabaseDeleter; use Illuminate\Database\DatabaseManager as BaseDatabaseManager; -class DatabaseManager +final class DatabaseManager { public $originalDefaultConnection; @@ -19,8 +19,7 @@ class DatabaseManager public function connect(string $database) { $this->createTenantConnection($database); - $this->database->setDefaultConnection('tenant'); - $this->database->reconnect('tenant'); + $this->useConnection('tenant'); } public function connectToTenant($tenant) @@ -105,4 +104,9 @@ class DatabaseManager $database_name = $this->getDriver() === 'sqlite' ? database_path($database_name) : $database_name; config()->set(['database.connections.tenant.database' => $database_name]); } + + public function useConnection(string $connection) { + $this->database->setDefaultConnection($connection); + $this->database->reconnect($connection); + } } diff --git a/src/TenantManager.php b/src/TenantManager.php index 006bcce2..9d5531bb 100644 --- a/src/TenantManager.php +++ b/src/TenantManager.php @@ -7,7 +7,7 @@ use Stancl\Tenancy\Traits\BootstrapsTenancy; use Illuminate\Contracts\Foundation\Application; use Stancl\Tenancy\Exceptions\CannotChangeUuidOrDomainException; -class TenantManager +final class TenantManager { use BootstrapsTenancy; @@ -30,7 +30,7 @@ class TenantManager * * @var DatabaseManager */ - protected $database; + public $database; /** * Current tenant. diff --git a/src/Traits/BootstrapsTenancy.php b/src/Traits/BootstrapsTenancy.php index 624b2180..b7188968 100644 --- a/src/Traits/BootstrapsTenancy.php +++ b/src/Traits/BootstrapsTenancy.php @@ -21,42 +21,55 @@ trait BootstrapsTenancy public function bootstrap() { - array_map(function ($listener) { - $listener($this); - }, $this->listeners['bootstrapping']); - + $prevented = $this->event('bootstrapping'); $this->initialized = true; - $this->switchDatabaseConnection(); - if ($this->app['config']['tenancy.redis.tenancy']) { - $this->setPhpRedisPrefix($this->app['config']['tenancy.redis.prefixed_connections']); + if (! $prevented->contains('database')) { + $this->switchDatabaseConnection(); } - $this->tagCache(); - $this->suffixFilesystemRootPaths(); - array_map(function ($listener) { - $listener($this); - }, $this->listeners['bootstrapped']); + if (! $prevented->contains('redis')) { + if ($this->app['config']['tenancy.redis.tenancy']) { + $this->setPhpRedisPrefix($this->app['config']['tenancy.redis.prefixed_connections']); + } + } + + if (! $prevented->contains('cache')) { + $this->tagCache(); + } + + if (! $prevented->contains('filesystem')) { + $this->suffixFilesystemRootPaths(); + } + + $this->event('bootstrapped'); } public function end() { - array_map(function ($listener) { - $listener($this); - }, $this->listeners['ending']); + $prevented = $this->event('ending'); $this->initialized = false; - $this->disconnectDatabase(); - if ($this->app['config']['tenancy.redis.tenancy']) { - $this->resetPhpRedisPrefix($this->app['config']['tenancy.redis.prefixed_connections']); + if (! $prevented->contains('database')) { + $this->disconnectDatabase(); } - $this->untagCache(); - $this->resetFileSystemRootPaths(); - array_map(function ($listener) { - $listener($this); - }, $this->listeners['ended']); + if (! $prevented->contains('redis')) { + if ($this->app['config']['tenancy.redis.tenancy']) { + $this->resetPhpRedisPrefix($this->app['config']['tenancy.redis.prefixed_connections']); + } + } + + if (! $prevented->contains('cache')) { + $this->untagCache(); + } + + if (! $prevented->contains('filesystem')) { + $this->resetFileSystemRootPaths(); + } + + $this->event('ended'); } public function switchDatabaseConnection() diff --git a/src/Traits/TenantManagerEvents.php b/src/Traits/TenantManagerEvents.php index c54b322b..b4cb7455 100644 --- a/src/Traits/TenantManagerEvents.php +++ b/src/Traits/TenantManagerEvents.php @@ -2,6 +2,8 @@ namespace Stancl\Tenancy\Traits; +use Illuminate\Support\Collection; + trait TenantManagerEvents { /** @@ -67,4 +69,17 @@ trait TenantManagerEvents return $this; } + + /** + * Fire an event. + * + * @param string $name Event name + * @return Collection Prevented events + */ + public function event(string $name): Collection + { + return array_reduce($this->listeners[$name], function ($prevents, $listener) { + return $prevents->merge($listener($this)); + }, collect([])); + } } diff --git a/tests/FacadeTest.php b/tests/FacadeTest.php index a218f0dd..45da854c 100644 --- a/tests/FacadeTest.php +++ b/tests/FacadeTest.php @@ -2,6 +2,7 @@ namespace Stancl\Tenancy\Tests; +use Tenant; use Tenancy; class FacadeTest extends TestCase @@ -15,4 +16,14 @@ class FacadeTest extends TestCase $this->assertSame('bar', Tenancy::get('foo')); $this->assertSame('xyz', Tenancy::get('abc')); } + + /** @test */ + public function tenant_manager_can_be_accessed_using_the_Tenant_facade() + { + tenancy()->put('foo', 'bar'); + Tenant::put('abc', 'xyz'); + + $this->assertSame('bar', Tenant::get('foo')); + $this->assertSame('xyz', Tenant::get('abc')); + } } diff --git a/tests/TenantManagerEventsTest.php b/tests/TenantManagerEventsTest.php new file mode 100644 index 00000000..1863a2cc --- /dev/null +++ b/tests/TenantManagerEventsTest.php @@ -0,0 +1,114 @@ +create('foo.localhost')['uuid']; + + Tenancy::bootstrapping(function ($tenantManager) use ($uuid) { + if ($tenantManager->tenant['uuid'] === $uuid) { + config(['tenancy.foo' => 'bar']); + } + }); + + $this->assertSame(null, config('tenancy.foo')); + tenancy()->init('foo.localhost'); + $this->assertSame('bar', config('tenancy.foo')); + } + + /** @test */ + public function bootstrapped_event_works() + { + $uuid = tenant()->create('foo.localhost')['uuid']; + + Tenancy::bootstrapped(function ($tenantManager) use ($uuid) { + if ($tenantManager->tenant['uuid'] === $uuid) { + config(['tenancy.foo' => 'bar']); + } + }); + + $this->assertSame(null, config('tenancy.foo')); + tenancy()->init('foo.localhost'); + $this->assertSame('bar', config('tenancy.foo')); + } + + /** @test */ + public function ending_event_works() + { + $uuid = tenant()->create('foo.localhost')['uuid']; + + Tenancy::ending(function ($tenantManager) use ($uuid) { + if ($tenantManager->tenant['uuid'] === $uuid) { + config(['tenancy.foo' => 'bar']); + } + }); + + $this->assertSame(null, config('tenancy.foo')); + tenancy()->init('foo.localhost'); + $this->assertSame(null, config('tenancy.foo')); + tenancy()->end(); + $this->assertSame('bar', config('tenancy.foo')); + } + + /** @test */ + public function ended_event_works() + { + $uuid = tenant()->create('foo.localhost')['uuid']; + + Tenancy::ended(function ($tenantManager) use ($uuid) { + if ($tenantManager->tenant['uuid'] === $uuid) { + config(['tenancy.foo' => 'bar']); + } + }); + + $this->assertSame(null, config('tenancy.foo')); + tenancy()->init('foo.localhost'); + $this->assertSame(null, config('tenancy.foo')); + tenancy()->end(); + $this->assertSame('bar', config('tenancy.foo')); + } + + /** @test */ + public function event_returns_a_collection() + { + // Note: The event() method should not be called by your code. + tenancy()->bootstrapping(function ($tenancy) { + return ['database']; + }); + tenancy()->bootstrapping(function ($tenancy) { + return ['redis', 'cache']; + }); + + $prevents = tenancy()->event('bootstrapping'); + $this->assertEquals(collect(['database', 'redis', 'cache']), $prevents); + } + + /** @test */ + public function database_can_be_reconnected_using_event_hooks() + { + config(['database.connections.tenantabc' => [ + 'driver' => 'sqlite', + 'database' => database_path('some_special_database.sqlite'), + ]]); + + $uuid = Tenant::create('abc.localhost')['uuid']; + + Tenancy::bootstrapping(function ($tenancy) use ($uuid) { + if ($tenancy->tenant['uuid'] === $uuid) { + $tenancy->database->useConnection('tenantabc'); + return ['database']; + } + }); + + $this->assertNotSame('tenantabc', \DB::connection()->getConfig()['name']); + tenancy()->init('abc.localhost'); + $this->assertSame('tenantabc', \DB::connection()->getConfig()['name']); + } +} \ No newline at end of file diff --git a/tests/TenantManagerTest.php b/tests/TenantManagerTest.php index faa2da3d..453730ea 100644 --- a/tests/TenantManagerTest.php +++ b/tests/TenantManagerTest.php @@ -2,7 +2,6 @@ namespace Stancl\Tenancy\Tests; -use Tenancy; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; use Stancl\Tenancy\Exceptions\CannotChangeUuidOrDomainException; @@ -242,72 +241,4 @@ class TenantManagerTest extends TestCase $this->expectException(CannotChangeUuidOrDomainException::class); tenant()->put(['uuid' => 'foo']); } - - /** @test */ - public function bootstrapping_event_works() - { - $uuid = tenant()->create('foo.localhost')['uuid']; - - Tenancy::bootstrapping(function ($tenantManager) use ($uuid) { - if ($tenantManager->tenant['uuid'] === $uuid) { - config(['tenancy.foo' => 'bar']); - } - }); - - $this->assertSame(null, config('tenancy.foo')); - tenancy()->init('foo.localhost'); - $this->assertSame('bar', config('tenancy.foo')); - } - - /** @test */ - public function bootstrapped_event_works() - { - $uuid = tenant()->create('foo.localhost')['uuid']; - - Tenancy::bootstrapped(function ($tenantManager) use ($uuid) { - if ($tenantManager->tenant['uuid'] === $uuid) { - config(['tenancy.foo' => 'bar']); - } - }); - - $this->assertSame(null, config('tenancy.foo')); - tenancy()->init('foo.localhost'); - $this->assertSame('bar', config('tenancy.foo')); - } - - /** @test */ - public function ending_event_works() - { - $uuid = tenant()->create('foo.localhost')['uuid']; - - Tenancy::ending(function ($tenantManager) use ($uuid) { - if ($tenantManager->tenant['uuid'] === $uuid) { - config(['tenancy.foo' => 'bar']); - } - }); - - $this->assertSame(null, config('tenancy.foo')); - tenancy()->init('foo.localhost'); - $this->assertSame(null, config('tenancy.foo')); - tenancy()->end(); - $this->assertSame('bar', config('tenancy.foo')); - } - - /** @test */ - public function ended_event_works() - { - $uuid = tenant()->create('foo.localhost')['uuid']; - - Tenancy::ended(function ($tenantManager) use ($uuid) { - if ($tenantManager->tenant['uuid'] === $uuid) { - config(['tenancy.foo' => 'bar']); - } - }); - - $this->assertSame(null, config('tenancy.foo')); - tenancy()->init('foo.localhost'); - $this->assertSame(null, config('tenancy.foo')); - tenancy()->end(); - $this->assertSame('bar', config('tenancy.foo')); - } } diff --git a/tests/TestCase.php b/tests/TestCase.php index b5d28acd..0183cc5a 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -101,6 +101,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase { return [ 'Tenancy' => \Stancl\Tenancy\TenancyFacade::class, + 'Tenant' => \Stancl\Tenancy\TenancyFacade::class, 'GlobalCache' => \Stancl\Tenancy\GlobalCacheFacade::class, ]; } From 155ef0da5a255da66d75aa6c443a5471140641d1 Mon Sep 17 00:00:00 2001 From: stancl Date: Wed, 14 Aug 2019 20:17:04 +0000 Subject: [PATCH 12/15] Apply fixes from StyleCI --- src/DatabaseManager.php | 3 ++- tests/TenantManagerEventsTest.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/DatabaseManager.php b/src/DatabaseManager.php index e494ac69..68e0cde0 100644 --- a/src/DatabaseManager.php +++ b/src/DatabaseManager.php @@ -105,7 +105,8 @@ final class DatabaseManager config()->set(['database.connections.tenant.database' => $database_name]); } - public function useConnection(string $connection) { + public function useConnection(string $connection) + { $this->database->setDefaultConnection($connection); $this->database->reconnect($connection); } diff --git a/tests/TenantManagerEventsTest.php b/tests/TenantManagerEventsTest.php index 1863a2cc..095629a6 100644 --- a/tests/TenantManagerEventsTest.php +++ b/tests/TenantManagerEventsTest.php @@ -103,6 +103,7 @@ class TenantManagerEventsTest extends TestCase Tenancy::bootstrapping(function ($tenancy) use ($uuid) { if ($tenancy->tenant['uuid'] === $uuid) { $tenancy->database->useConnection('tenantabc'); + return ['database']; } }); @@ -111,4 +112,4 @@ class TenantManagerEventsTest extends TestCase tenancy()->init('abc.localhost'); $this->assertSame('tenantabc', \DB::connection()->getConfig()['name']); } -} \ No newline at end of file +} From 9c269b087eaf637a0fa3b02c6455838fdeb1f133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Wed, 14 Aug 2019 22:18:18 +0200 Subject: [PATCH 13/15] Add one more test for prevents --- tests/TenantManagerEventsTest.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/TenantManagerEventsTest.php b/tests/TenantManagerEventsTest.php index 1863a2cc..28fc35fa 100644 --- a/tests/TenantManagerEventsTest.php +++ b/tests/TenantManagerEventsTest.php @@ -111,4 +111,26 @@ class TenantManagerEventsTest extends TestCase tenancy()->init('abc.localhost'); $this->assertSame('tenantabc', \DB::connection()->getConfig()['name']); } + + /** @test */ + public function database_cannot_be_reconnected_without_using_prevents() + { + config(['database.connections.tenantabc' => [ + 'driver' => 'sqlite', + 'database' => database_path('some_special_database.sqlite'), + ]]); + + $uuid = Tenant::create('abc.localhost')['uuid']; + + Tenancy::bootstrapping(function ($tenancy) use ($uuid) { + if ($tenancy->tenant['uuid'] === $uuid) { + $tenancy->database->useConnection('tenantabc'); + // return ['database']; + } + }); + + $this->assertNotSame('tenantabc', \DB::connection()->getConfig()['name']); + tenancy()->init('abc.localhost'); + $this->assertSame('tenant', \DB::connection()->getConfig()['name']); + } } \ No newline at end of file From aca5567c101910b39b07b298822e9a69a1b22cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Wed, 14 Aug 2019 22:55:18 +0200 Subject: [PATCH 14/15] Add empty array default --- src/Traits/TenantManagerEvents.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/TenantManagerEvents.php b/src/Traits/TenantManagerEvents.php index b4cb7455..416ebd72 100644 --- a/src/Traits/TenantManagerEvents.php +++ b/src/Traits/TenantManagerEvents.php @@ -79,7 +79,7 @@ trait TenantManagerEvents public function event(string $name): Collection { return array_reduce($this->listeners[$name], function ($prevents, $listener) { - return $prevents->merge($listener($this)); + return $prevents->merge($listener($this) ?? []); }, collect([])); } } From 674f4b3f9a5f7509d6dabd41fbf6ebcc03001b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Thu, 15 Aug 2019 19:16:25 +0200 Subject: [PATCH 15/15] [1.7.0] Install command (#95) --- src/Commands/Install.php | 76 ++++++++++++++ src/Commands/TenantList.php | 10 -- src/TenancyServiceProvider.php | 2 + tests/CommandsTest.php | 180 +++++++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 10 deletions(-) create mode 100644 src/Commands/Install.php diff --git a/src/Commands/Install.php b/src/Commands/Install.php new file mode 100644 index 00000000..de1a81b2 --- /dev/null +++ b/src/Commands/Install.php @@ -0,0 +1,76 @@ +comment('Installing stancl/tenancy...'); + $this->callSilent('vendor:publish', [ + '--provider' => 'Stancl\Tenancy\TenancyServiceProvider', + '--tag' => 'config', + ]); + $this->info('✔️ Created config/tenancy.php'); + + file_put_contents(app_path('Http/Kernel.php'), str_replace( + 'protected $middlewarePriority = [', + "protected \$middlewarePriority = [\n \Stancl\Tenancy\Middleware\InitializeTenancy::class,", + file_get_contents(app_path('Http/Kernel.php')) + )); + $this->info('✔️ Set middleware priority'); + + file_put_contents(base_path('routes/tenant.php'), +"info('✔️ Created routes/tenant.php'); + + $this->line(''); + $this->line("This package lets you store data about tenants either in Redis or in a relational database like MySQL. If you're going to use the database storage, you need to create a tenants table."); + if ($this->confirm('Do you want to publish the default database migration?', true)) { + $this->callSilent('vendor:publish', [ + '--provider' => 'Stancl\Tenancy\TenancyServiceProvider', + '--tag' => 'migrations', + ]); + $this->info('✔️ Created migration.'); + } + + $this->comment('✨️ stancl/tenancy installed successfully.'); + } +} diff --git a/src/Commands/TenantList.php b/src/Commands/TenantList.php index 6a8fc400..a1d2b049 100644 --- a/src/Commands/TenantList.php +++ b/src/Commands/TenantList.php @@ -20,16 +20,6 @@ class TenantList extends Command */ protected $description = 'List tenants.'; - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - /** * Execute the console command. * diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php index 4ac33bd5..76218665 100644 --- a/src/TenancyServiceProvider.php +++ b/src/TenancyServiceProvider.php @@ -5,6 +5,7 @@ namespace Stancl\Tenancy; use Stancl\Tenancy\Commands\Run; use Stancl\Tenancy\Commands\Seed; use Illuminate\Cache\CacheManager; +use Stancl\Tenancy\Commands\Install; use Stancl\Tenancy\Commands\Migrate; use Illuminate\Support\Facades\Route; use Stancl\Tenancy\Commands\Rollback; @@ -25,6 +26,7 @@ class TenancyServiceProvider extends ServiceProvider $this->commands([ Run::class, Seed::class, + Install::class, Migrate::class, Rollback::class, TenantList::class, diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php index d26b71c1..df90a21b 100644 --- a/tests/CommandsTest.php +++ b/tests/CommandsTest.php @@ -113,4 +113,184 @@ class CommandsTest extends TestCase ->expectsOutput('foo') ->expectsOutput('xyz'); } + + /** @test */ + public function install_command_works() + { + if (! is_dir($dir = app_path('Http'))) { + mkdir($dir, 0777, true); + } + if (! is_dir($dir = base_path('routes'))) { + mkdir($dir, 0777, true); + } + + file_put_contents(app_path('Http/Kernel.php'), " [ + \App\Http\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + // \Illuminate\Session\Middleware\AuthenticateSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \App\Http\Middleware\VerifyCsrfToken::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + + 'api' => [ + 'throttle:60,1', + 'bindings', + ], + ]; + + /** + * The application's route middleware. + * + * These middleware may be assigned to groups or used individually. + * + * @var array + */ + protected \$routeMiddleware = [ + 'auth' => \App\Http\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + ]; + + /** + * The priority-sorted list of middleware. + * + * This forces non-global middleware to always be in the given order. + * + * @var array + */ + protected \$middlewarePriority = [ + \Illuminate\Session\Middleware\StartSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \App\Http\Middleware\Authenticate::class, + \Illuminate\Session\Middleware\AuthenticateSession::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + \Illuminate\Auth\Middleware\Authorize::class, + ]; +} +"); + + $this->artisan('tenancy:install') + ->expectsQuestion('Do you want to publish the default database migration?', 'yes'); + $this->assertFileExists(base_path('routes/tenant.php')); + $this->assertFileExists(base_path('config/tenancy.php')); + $this->assertSame(" [ + \App\Http\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + // \Illuminate\Session\Middleware\AuthenticateSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \App\Http\Middleware\VerifyCsrfToken::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + + 'api' => [ + 'throttle:60,1', + 'bindings', + ], + ]; + + /** + * The application's route middleware. + * + * These middleware may be assigned to groups or used individually. + * + * @var array + */ + protected \$routeMiddleware = [ + 'auth' => \App\Http\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + ]; + + /** + * The priority-sorted list of middleware. + * + * This forces non-global middleware to always be in the given order. + * + * @var array + */ + protected \$middlewarePriority = [ + \Stancl\Tenancy\Middleware\InitializeTenancy::class, + \Illuminate\Session\Middleware\StartSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \App\Http\Middleware\Authenticate::class, + \Illuminate\Session\Middleware\AuthenticateSession::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + \Illuminate\Auth\Middleware\Authorize::class, + ]; +} +", \file_get_contents(app_path('Http/Kernel.php'))); + } }