From c53a05cff4ff175c819e6f42c9fd1c48cc95e82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= <33033094+stancl@users.noreply.github.com> Date: Thu, 7 Feb 2019 18:12:24 +0100 Subject: [PATCH] Make database creation queueable (#17) * SOLIDify database creation * Return the result of fclose in SQLiteDBCreator * Make database creation queueable * Add DatabaseCreationTest * Make comment more descriptive [ci skip] * Add composer script to copy .env.example to .env * Minor tweaks * Fix env section * Fix DB_PASSWORD for Travis --- .env.example | 3 ++ .gitignore | 1 + .travis.yml | 10 ++++- composer.json | 3 +- phpunit.xml | 1 - src/DatabaseCreators/MySQLDatabaseCreator.php | 14 ++++++ .../SQLiteDatabaseCreator.php | 13 ++++++ src/DatabaseManager.php | 17 ++++--- src/Interfaces/DatabaseCreator.php | 14 ++++++ src/Jobs/QueuedDatabaseCreator.php | 38 ++++++++++++++++ src/config/tenancy.php | 6 ++- tests/DatabaseCreationTest.php | 45 +++++++++++++++++++ tests/TestCase.php | 45 +++++++++++++------ 13 files changed, 186 insertions(+), 24 deletions(-) create mode 100644 .env.example create mode 100644 src/DatabaseCreators/MySQLDatabaseCreator.php create mode 100644 src/DatabaseCreators/SQLiteDatabaseCreator.php create mode 100644 src/Interfaces/DatabaseCreator.php create mode 100644 src/Jobs/QueuedDatabaseCreator.php create mode 100644 tests/DatabaseCreationTest.php diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..55e2e43d --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# DB_DATABASE=travis_tenancy +# DB_USERNAME=foo +# DB_PASSWORD=bar \ No newline at end of file diff --git a/.gitignore b/.gitignore index d8a7996a..6733b4e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.env composer.lock vendor/ diff --git a/.travis.yml b/.travis.yml index 88e84dda..1e2657b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ +env: + - DB_USERNAME=root DB_PASSWORD="" DB_DATABASE=travis_tenancy CODECOV_TOKEN="24382d15-84e7-4a55-bea4-c4df96a24a9b" + language: php php: - '7.2' @@ -8,6 +11,7 @@ branches: - master services: + - mysql - redis-server before_install: @@ -16,8 +20,10 @@ before_install: install: - travis_retry composer install --no-interaction -script: vendor/bin/phpunit --coverage-clover=coverage.xml +before_script: + - mysql -e 'CREATE DATABASE travis_tenancy;' + +script: vendor/bin/phpunit -v --coverage-clover=coverage.xml after_success: - - export CODECOV_TOKEN="24382d15-84e7-4a55-bea4-c4df96a24a9b" - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/composer.json b/composer.json index 3ee667cf..ca99db8e 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ }, "require-dev": { "orchestra/testbench": "~3.0", - "laravel/framework": "5.7.*" + "laravel/framework": "5.7.*", + "vlucas/phpdotenv": "^2.2" }, "autoload": { "psr-4": { diff --git a/phpunit.xml b/phpunit.xml index da25c19f..75cb7545 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -29,6 +29,5 @@ - \ No newline at end of file diff --git a/src/DatabaseCreators/MySQLDatabaseCreator.php b/src/DatabaseCreators/MySQLDatabaseCreator.php new file mode 100644 index 00000000..745143d5 --- /dev/null +++ b/src/DatabaseCreators/MySQLDatabaseCreator.php @@ -0,0 +1,14 @@ +createTenantConnection($name); $driver = $driver ?: $this->getDriver(); - if ($driver === "sqlite") { - $f = fopen(database_path($name), 'w'); - fclose($f); - - return; + + $databaseCreators = config('tenancy.database_creators'); + + if (! array_key_exists($driver, $databaseCreators)) { + throw new \Exception("Database could not be created: no database creator for driver $driver is registered."); } - return DB::statement("CREATE DATABASE `$name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); + if (config('tenancy.queue_database_creation', false)) { + QueuedDatabaseCreator::dispatch(app($databaseCreators[$driver]), $name); + } else { + app($databaseCreators[$driver])->createDatabase($name); + } } public function delete() diff --git a/src/Interfaces/DatabaseCreator.php b/src/Interfaces/DatabaseCreator.php new file mode 100644 index 00000000..9b6fd52a --- /dev/null +++ b/src/Interfaces/DatabaseCreator.php @@ -0,0 +1,14 @@ +databaseCreator = $databaseCreator; + $this->databaseName = $databaseName; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $this->databaseCreator->createDatabase($databaseName); + } +} diff --git a/src/config/tenancy.php b/src/config/tenancy.php index 50b6ad75..876c5b65 100644 --- a/src/config/tenancy.php +++ b/src/config/tenancy.php @@ -21,7 +21,6 @@ return [ 'cache' => [ 'prefix_base' => 'tenant', ], - 'filesystem' => [ 'suffix_base' => 'tenant', // Disks which should be suffixed with the suffix_base + tenant UUID. @@ -30,4 +29,9 @@ return [ // 's3', ], ], + 'database_creators' => [ + 'sqlite' => 'Stancl\Tenancy\DatabaseCreators\SQLiteDatabaseCreator', + 'mysql' => 'Stancl\Tenancy\DatabaseCreators\MySQLDatabaseCreator', + ], + 'queue_database_creation' => false, ]; diff --git a/tests/DatabaseCreationTest.php b/tests/DatabaseCreationTest.php new file mode 100644 index 00000000..9b1428d0 --- /dev/null +++ b/tests/DatabaseCreationTest.php @@ -0,0 +1,45 @@ +randomString(10) . '.sqlite'; + app(DatabaseManager::class)->create($db_name, 'sqlite'); + $this->assertFileExists(database_path($db_name)); + } + + /** @test */ + public function mysql_database_is_created() + { + if (! $this->isTravis()) { + $this->markTestSkipped('As to not bloat your MySQL instance with test databases, this test is not run by default.'); + } + + config()->set('database.default', 'mysql'); + + $db_name = 'testdatabase' . $this->randomString(10); + app(DatabaseManager::class)->create($db_name, 'mysql'); + $this->assertNotEmpty(DB::select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$db_name'")); + } + + /** @test */ + public function database_creation_can_be_queued() + { + Queue::fake(); + + config()->set('tenancy.queue_database_creation', true); + $db_name = 'testdatabase' . $this->randomString(10) . '.sqlite'; + app(DatabaseManager::class)->create($db_name, 'sqlite'); + + Queue::assertPushed(QueuedDatabaseCreator::class); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 793eb166..4cc9bdb5 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -30,19 +30,26 @@ class TestCase extends \Orchestra\Testbench\TestCase */ protected function getEnvironmentSetUp($app) { - $app['config']->set('database.redis.client', 'phpredis'); - $app['config']->set('database.redis.tenancy', [ - 'host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'), - 'password' => env('TENANCY_TEST_REDIS_PASSWORD', null), - 'port' => env('TENANCY_TEST_REDIS_PORT', 6379), - // Use the #14 Redis database unless specified otherwise. - // Make sure you don't store anything in this db! - 'database' => env('TENANCY_TEST_REDIS_DB', 14), - ]); - $app['config']->set('tenancy.database', [ - 'based_on' => 'sqlite', - 'prefix' => 'tenant', - 'suffix' => '.sqlite', + if (file_exists(__DIR__ . '/../.env')) { + (new \Dotenv\Dotenv(__DIR__ . '/..'))->load(); + } + + $app['config']->set([ + 'database.redis.client' => 'phpredis', + 'database.redis.tenancy' => [ + 'host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'), + 'password' => env('TENANCY_TEST_REDIS_PASSWORD', null), + 'port' => env('TENANCY_TEST_REDIS_PORT', 6379), + // Use the #14 Redis database unless specified otherwise. + // Make sure you don't store anything in this db! + 'database' => env('TENANCY_TEST_REDIS_DB', 14), + ], + 'tenancy.database' => [ + 'based_on' => 'sqlite', + 'prefix' => 'tenant', + 'suffix' => '.sqlite', + ], + 'database.connections.sqlite.database' => ':memory:', ]); } @@ -61,4 +68,16 @@ class TestCase extends \Orchestra\Testbench\TestCase { $app->singleton('Illuminate\Contracts\Http\Kernel', HttpKernel::class); } + + public function randomString(int $length = 10) + { + return substr(str_shuffle(str_repeat($x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil($length / strlen($x)))), 1, $length); + } + + public function isTravis() + { + // Multiple, just to make sure. Someone might accidentally + // set one of these environment vars on their computer. + return env('CI') && env('TRAVIS') && env('CONTINUOUS_INTEGRATION'); + } }