diff --git a/README.md b/README.md index 5cff204e..22321f04 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ Add following line into the `Register Service Providers` section. ```php $app->register(\KitLoong\MigrationsGenerator\MigrationsGeneratorServiceProvider::class); ``` + ## Usage @@ -105,7 +106,7 @@ php artisan migrate:generate --connection="connection_name" ### Squash Migrations -By default, Generator will generate multiple migration files for each table. +By default, Generator will generate multiple migration files for each table. You can squash all migrations into a single file with: @@ -134,6 +135,7 @@ Run `php artisan help migrate:generate` for a list of options. | --default-fk-names | Don\'t use DB foreign key names for migrations | | --use-db-collation | Generate migrations with existing DB collation | | --skip-log | Don\'t log into migrations table | +| --skip-vendor | Don\'t generate vendor migrations | | --skip-views | Don\'t generate views | | --skip-proc | Don\'t generate stored procedures | | --squash | Generate all migrations into a single file | diff --git a/phpcs.xml b/phpcs.xml index 23afd992..69b85ae5 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -144,6 +144,7 @@ + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bfcbc1ab..b18123a7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,21 +1,23 @@ - - + + + + tests + + + src src/KitLoong/MigrationsGenerator/Types - - - - tests - - + diff --git a/src/MigrateGenerateCommand.php b/src/MigrateGenerateCommand.php index 0194dfba..ef2e9154 100644 --- a/src/MigrateGenerateCommand.php +++ b/src/MigrateGenerateCommand.php @@ -11,6 +11,7 @@ use Illuminate\Support\Facades\DB; use KitLoong\MigrationsGenerator\Enum\Driver; use KitLoong\MigrationsGenerator\Migration\ForeignKeyMigration; +use KitLoong\MigrationsGenerator\Migration\Migrator\Migrator; use KitLoong\MigrationsGenerator\Migration\ProcedureMigration; use KitLoong\MigrationsGenerator\Migration\Squash; use KitLoong\MigrationsGenerator\Migration\TableMigration; @@ -50,6 +51,7 @@ class MigrateGenerateCommand extends Command {--default-fk-names : Don\'t use DB foreign key names for migrations} {--use-db-collation : Generate migrations with existing DB collation} {--skip-log : Don\'t log into migrations table} + {--skip-vendor : Don\'t generate vendor migrations} {--skip-views : Don\'t generate views} {--skip-proc : Don\'t generate stored procedures} {--squash : Generate all migrations into a single file} @@ -300,7 +302,12 @@ protected function getExcludedTables(): array $ignore = (string) $this->option('ignore'); if (!empty($ignore)) { - return array_merge([$migrationTable], explode(',', $ignore)); + $excludes = array_merge($excludes, explode(',', $ignore)); + } + + if ($this->option('skip-vendor')) { + $vendorTables = app(Migrator::class)->getVendorTableNames(); + $excludes = array_merge($excludes, $vendorTables); } return $excludes; diff --git a/src/Migration/Migrator/Migrator.php b/src/Migration/Migrator/Migrator.php new file mode 100644 index 00000000..1e82a4b2 --- /dev/null +++ b/src/Migration/Migrator/Migrator.php @@ -0,0 +1,91 @@ + 'sqlite', + 'database' => ':memory:', + ]); + + DB::setDefaultConnection('lgm_sqlite'); + + $vendorPaths = app('migrator')->paths(); + + foreach ($vendorPaths as $path) { + $files = File::files($path); + + foreach ($files as $file) { + $queries = $this->getMigrationQueries($file->getPathname()); + + foreach ($queries as $q) { + $matched = Regex::match('/^create table ["|`](.*?)["|`]/', $q['query']); + + if ($matched === '') { + continue; + } + + $tables[] = $matched; + } + } + } + } finally { + // Restore backup DB connection. + DB::setDefaultConnection($previousConnection); + } + + return $tables; + } + + /** + * Resolve migration instance from `$path` and get all of the queries that would be run for a migration. + * + * @return array>, 'time': float|null}> + */ + protected function getMigrationQueries(string $path): array + { + $migration = $this->resolveMigration($path); + + return $this->getQueries($migration, 'up'); + } + + /** + * Resolve migration instance with backward compatibility. + * + * @return object + */ + protected function resolveMigration(string $path) + { + if (method_exists(DefaultMigrator::class, 'resolvePath')) { + return $this->resolvePath($path); + } + + // @codeCoverageIgnoreStart + return $this->resolve( + $this->getMigrationName($path) + ); + // @codeCoverageIgnoreEnd + } +} diff --git a/src/MigrationsGeneratorServiceProvider.php b/src/MigrationsGeneratorServiceProvider.php index 17ff7fe9..b5d66a22 100644 --- a/src/MigrationsGeneratorServiceProvider.php +++ b/src/MigrationsGeneratorServiceProvider.php @@ -20,6 +20,7 @@ use KitLoong\MigrationsGenerator\Migration\Generator\Columns\PresetValuesColumn; use KitLoong\MigrationsGenerator\Migration\Generator\Columns\SoftDeleteColumn; use KitLoong\MigrationsGenerator\Migration\Generator\Columns\StringColumn; +use KitLoong\MigrationsGenerator\Migration\Migrator\Migrator; use KitLoong\MigrationsGenerator\Repositories\MariaDBRepository; use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; use KitLoong\MigrationsGenerator\Repositories\PgSQLRepository; @@ -34,9 +35,6 @@ class MigrationsGeneratorServiceProvider extends ServiceProvider { /** * Register the service provider. - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ public function register(): void { @@ -69,13 +67,23 @@ public function register(): void } // Bind the Repository Interface to $app['migrations.repository'] - $this->app->bind( + $this->app->singleton( MigrationRepositoryInterface::class, function ($app) { return $app['migration.repository']; } ); + // Backward compatible for older Laravel version which failed to resolve Illuminate\Database\ConnectionResolverInterface. + $this->app->singleton( + Migrator::class, + function ($app) { + $repository = $app['migration.repository']; + + return new Migrator($repository, $app['db'], $app['files'], $app['events']); + } + ); + $this->registerColumnTypeGenerator(); } @@ -92,9 +100,6 @@ public function boot(): void /** * Register the config path. - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ protected function registerConfig(): void { diff --git a/tests/Feature/FeatureTestCase.php b/tests/Feature/FeatureTestCase.php index 950efa77..ddeac5b6 100644 --- a/tests/Feature/FeatureTestCase.php +++ b/tests/Feature/FeatureTestCase.php @@ -81,6 +81,11 @@ protected function getStorageFromPath(string $path = ''): string return storage_path('from') . ($path ? DIRECTORY_SEPARATOR . $path : $path); } + protected function getStorageFromVendorsPath(string $path = ''): string + { + return storage_path("from/vendors") . ($path ? DIRECTORY_SEPARATOR . $path : $path); + } + protected function getStorageSqlPath(string $path = ''): string { return storage_path('sql') . ($path ? DIRECTORY_SEPARATOR . $path : $path); @@ -96,6 +101,11 @@ protected function migrateCollation(string $connection): void $this->migrateFromTemplate($connection, base_path('tests/resources/database/migrations/collation')); } + protected function migrateVendors(string $connection): void + { + $this->migrateFromVendorsTemplate($connection, base_path('tests/resources/database/migrations/vendors')); + } + protected function migrateFromTemplate(string $connection, string $templatePath): void { File::copyDirectory($templatePath, $this->getStorageFromPath()); @@ -119,6 +129,29 @@ protected function migrateFromTemplate(string $connection, string $templatePath) $this->runMigrationsFrom($connection, $this->getStorageFromPath()); } + protected function migrateFromVendorsTemplate(string $connection, string $templatePath): void + { + File::copyDirectory($templatePath, $this->getStorageFromVendorsPath()); + + foreach (File::files($this->getStorageFromVendorsPath()) as $file) { + $content = str_replace([ + '[db]', + '_DB_', + ], [ + $connection, + ucfirst("$connection"), + ], $file->getContents()); + + File::put($this->getStorageFromVendorsPath($file->getBasename()), $content); + File::move( + $this->getStorageFromVendorsPath($file->getBasename()), + $this->getStorageFromVendorsPath(str_replace('_db_', "_{$connection}_", $file->getBasename())) + ); + } + + $this->runMigrationsFrom($connection, $this->getStorageFromVendorsPath()); + } + protected function runMigrationsFrom(string $connection, string $path): void { $this->artisan('migrate', [ diff --git a/tests/Feature/MySQL57/CommandTest.php b/tests/Feature/MySQL57/CommandTest.php index 4fce7119..033e2882 100644 --- a/tests/Feature/MySQL57/CommandTest.php +++ b/tests/Feature/MySQL57/CommandTest.php @@ -3,6 +3,7 @@ namespace KitLoong\MigrationsGenerator\Tests\Feature\MySQL57; use Illuminate\Database\Migrations\MigrationRepositoryInterface; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Schema; @@ -508,6 +509,48 @@ public function testLogWithBatchNaN(): void ); } + public function testSkipVendor(): void + { + $this->migrateGeneral('mysql57'); + + $this->migrateVendors('mysql57'); + + // Load migrations from vendors path to mock vendors migration. + // Loaded migrations should not be generated. + app('migrator')->path($this->getStorageFromVendorsPath()); + + $tables = $this->getTableNames(); + + $vendors = [ + 'personal_access_tokens_mysql57', + 'telescope_entries_mysql57', + 'telescope_entries_tags_mysql57', + 'telescope_monitoring_mysql57', + ]; + + foreach ($vendors as $vendor) { + $this->assertContains($vendor, $tables); + } + + $tablesWithoutVendors = (new Collection($tables))->filter(function ($table) use ($vendors) { + return !in_array($table, $vendors); + }) + ->values() + ->all(); + + $this->truncateMigrationsTable(); + + $this->generateMigrations(['--skip-vendor' => true]); + + $this->refreshDatabase(); + + $this->runMigrationsFrom('mysql57', $this->getStorageMigrationsPath()); + + $generatedTables = $this->getTableNames(); + + $this->assertSame($tablesWithoutVendors, $generatedTables); + } + private function verify(callable $migrateTemplates, callable $generateMigrations): void { $migrateTemplates(); diff --git a/tests/Feature/MySQL8/CommandTest.php b/tests/Feature/MySQL8/CommandTest.php index 2af4358c..c877d0e3 100644 --- a/tests/Feature/MySQL8/CommandTest.php +++ b/tests/Feature/MySQL8/CommandTest.php @@ -2,6 +2,7 @@ namespace KitLoong\MigrationsGenerator\Tests\Feature\MySQL8; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; /** @@ -54,6 +55,48 @@ public function testCollation(): void $this->verify($migrateTemplates, $generateMigrations); } + public function testSkipVendor(): void + { + $this->migrateGeneral('mysql8'); + + $this->migrateVendors('mysql8'); + + // Load migrations from vendors path to mock vendors migration. + // Loaded migrations should not be generated. + app('migrator')->path($this->getStorageFromVendorsPath()); + + $tables = $this->getTableNames(); + + $vendors = [ + 'personal_access_tokens_mysql8', + 'telescope_entries_mysql8', + 'telescope_entries_tags_mysql8', + 'telescope_monitoring_mysql8', + ]; + + foreach ($vendors as $vendor) { + $this->assertContains($vendor, $tables); + } + + $tablesWithoutVendors = (new Collection($tables))->filter(function ($table) use ($vendors) { + return !in_array($table, $vendors); + }) + ->values() + ->all(); + + $this->truncateMigrationsTable(); + + $this->generateMigrations(['--skip-vendor' => true]); + + $this->refreshDatabase(); + + $this->runMigrationsFrom('mysql8', $this->getStorageMigrationsPath()); + + $generatedTables = $this->getTableNames(); + + $this->assertSame($tablesWithoutVendors, $generatedTables); + } + private function verify(callable $migrateTemplates, callable $generateMigrations): void { $migrateTemplates(); diff --git a/tests/Feature/PgSQL/CommandTest.php b/tests/Feature/PgSQL/CommandTest.php index bca5de8c..816a7386 100644 --- a/tests/Feature/PgSQL/CommandTest.php +++ b/tests/Feature/PgSQL/CommandTest.php @@ -2,6 +2,7 @@ namespace KitLoong\MigrationsGenerator\Tests\Feature\PgSQL; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\File; @@ -170,6 +171,51 @@ public function testWithHasTableSquash(): void $this->verify($migrateTemplates, $generateMigrations); } + public function testSkipVendor(): void + { + $this->migrateGeneral('pgsql'); + + $this->migrateVendors('pgsql'); + + // Load migrations from vendors path to mock vendors migration. + // Loaded migrations should not be generated. + app('migrator')->path($this->getStorageFromVendorsPath()); + + $tables = $this->getTableNames(); + + $vendors = [ + 'personal_access_tokens_pgsql', + 'telescope_entries_pgsql', + 'telescope_entries_tags_pgsql', + 'telescope_monitoring_pgsql', + ]; + + foreach ($vendors as $vendor) { + $this->assertContains($vendor, $tables); + } + + $tablesWithoutVendors = (new Collection($tables))->filter(function ($table) use ($vendors) { + return !in_array($table, $vendors); + }) + ->values() + ->all(); + + $this->truncateMigrationsTable(); + + $this->generateMigrations(['--skip-vendor' => true]); + + $this->refreshDatabase(); + + $this->runMigrationsFrom('pgsql', $this->getStorageMigrationsPath()); + + $generatedTables = $this->getTableNames(); + + sort($tablesWithoutVendors); + sort($generatedTables); + + $this->assertSame($tablesWithoutVendors, $generatedTables); + } + private function verify(callable $migrateTemplates, callable $generateMigrations, ?callable $beforeVerify = null): void { $migrateTemplates(); diff --git a/tests/Feature/SQLSrv/CommandTest.php b/tests/Feature/SQLSrv/CommandTest.php index 07966cfb..c5e03b92 100644 --- a/tests/Feature/SQLSrv/CommandTest.php +++ b/tests/Feature/SQLSrv/CommandTest.php @@ -2,6 +2,7 @@ namespace KitLoong\MigrationsGenerator\Tests\Feature\SQLSrv; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\File; @@ -126,6 +127,48 @@ public function testGenerateXml(): void $this->assertTrue(true); } + public function testSkipVendor(): void + { + $this->migrateGeneral('sqlsrv'); + + $this->migrateVendors('sqlsrv'); + + // Load migrations from vendors path to mock vendors migration. + // Loaded migrations should not be generated. + app('migrator')->path($this->getStorageFromVendorsPath()); + + $tables = $this->getTableNames(); + + $vendors = [ + 'personal_access_tokens_sqlsrv', + 'telescope_entries_sqlsrv', + 'telescope_entries_tags_sqlsrv', + 'telescope_monitoring_sqlsrv', + ]; + + foreach ($vendors as $vendor) { + $this->assertContains($vendor, $tables); + } + + $tablesWithoutVendors = (new Collection($tables))->filter(function ($table) use ($vendors) { + return !in_array($table, $vendors); + }) + ->values() + ->all(); + + $this->truncateMigrationsTable(); + + $this->generateMigrations(['--skip-vendor' => true]); + + $this->refreshDatabase(); + + $this->runMigrationsFrom('sqlsrv', $this->getStorageMigrationsPath()); + + $generatedTables = $this->getTableNames(); + + $this->assertSame($tablesWithoutVendors, $generatedTables); + } + /** * @throws \Doctrine\DBAL\Exception */ diff --git a/tests/Feature/SQLite/CommandTest.php b/tests/Feature/SQLite/CommandTest.php index b4ddc55f..4ded17f3 100644 --- a/tests/Feature/SQLite/CommandTest.php +++ b/tests/Feature/SQLite/CommandTest.php @@ -2,6 +2,7 @@ namespace KitLoong\MigrationsGenerator\Tests\Feature\SQLite; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use KitLoong\MigrationsGenerator\Support\CheckMigrationMethod; @@ -57,6 +58,48 @@ public function testCollation(): void $this->verify($migrateTemplates, $generateMigrations); } + public function testSkipVendor(): void + { + $this->migrateGeneral('sqlite'); + + $this->migrateVendors('sqlite'); + + // Load migrations from vendors path to mock vendors migration. + // Loaded migrations should not be generated. + app('migrator')->path($this->getStorageFromVendorsPath()); + + $tables = $this->getTableNames(); + + $vendors = [ + 'personal_access_tokens_sqlite', + 'telescope_entries_sqlite', + 'telescope_entries_tags_sqlite', + 'telescope_monitoring_sqlite', + ]; + + foreach ($vendors as $vendor) { + $this->assertContains($vendor, $tables); + } + + $tablesWithoutVendors = (new Collection($tables))->filter(function ($table) use ($vendors) { + return !in_array($table, $vendors); + }) + ->values() + ->all(); + + $this->truncateMigrationsTable(); + + $this->generateMigrations(['--skip-vendor' => true]); + + $this->refreshDatabase(); + + $this->runMigrationsFrom('sqlite', $this->getStorageMigrationsPath()); + + $generatedTables = $this->getTableNames(); + + $this->assertSame($tablesWithoutVendors, $generatedTables); + } + private function verify(callable $migrateTemplates, callable $generateMigrations): void { $migrateTemplates(); diff --git a/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_primary_db_table.php b/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_primary_db_table.php index 042fd29e..59b9e5a3 100644 --- a/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_primary_db_table.php +++ b/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_primary_db_table.php @@ -39,7 +39,7 @@ public function up() }); // Test short table name - Schema::create('s[db]', function (Blueprint $table) { + Schema::create('s_[db]', function (Blueprint $table) { $table->bigIncrements('id'); }); } @@ -55,6 +55,6 @@ public function down() Schema::dropIfExists('primary_name_[db]'); Schema::dropIfExists('signed_primary_id_[db]'); Schema::dropIfExists('composite_primary_[db]'); - Schema::dropIfExists('s[db]'); + Schema::dropIfExists('s_[db]'); } } diff --git a/tests/resources/database/migrations/vendors/2018_08_08_100000_create_telescope_db_table.php b/tests/resources/database/migrations/vendors/2018_08_08_100000_create_telescope_db_table.php new file mode 100644 index 00000000..e59a0fe5 --- /dev/null +++ b/tests/resources/database/migrations/vendors/2018_08_08_100000_create_telescope_db_table.php @@ -0,0 +1,74 @@ +getConnection()); + + $schema->create('telescope_entries_[db]', function (Blueprint $table) { + $table->bigIncrements('sequence'); + $table->uuid('uuid'); + $table->uuid('batch_id'); + $table->string('family_hash')->nullable(); + $table->boolean('should_display_on_index')->default(true); + $table->string('type', 20); + $table->longText('content'); + $table->dateTime('created_at')->nullable(); + + $table->unique('uuid'); + $table->index('batch_id'); + $table->index('family_hash'); + $table->index('created_at'); + $table->index(['type', 'should_display_on_index']); + }); + + $schema->create('telescope_entries_tags_[db]', function (Blueprint $table) { + $table->uuid('entry_uuid'); + $table->string('tag'); + + $table->index(['entry_uuid', 'tag']); + $table->index('tag'); + + $table->foreign('entry_uuid') + ->references('uuid') + ->on('telescope_entries_[db]') + ->onDelete('cascade'); + }); + + $schema->create('telescope_monitoring_[db]', function (Blueprint $table) { + $table->string('tag'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $schema = Schema::connection($this->getConnection()); + + $schema->dropIfExists('telescope_entries_tags_[db]'); + $schema->dropIfExists('telescope_entries_[db]'); + $schema->dropIfExists('telescope_monitoring_[db]'); + } +} diff --git a/tests/resources/database/migrations/vendors/2019_12_14_000001_create_personal_access_tokens_db_table.php b/tests/resources/database/migrations/vendors/2019_12_14_000001_create_personal_access_tokens_db_table.php new file mode 100644 index 00000000..5e1d9010 --- /dev/null +++ b/tests/resources/database/migrations/vendors/2019_12_14_000001_create_personal_access_tokens_db_table.php @@ -0,0 +1,37 @@ +bigIncrements('id'); + $table->morphs('tokenable'); + $table->string('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('personal_access_tokens_[db]'); + } +}