├── .github └── workflows │ ├── ci.yml │ └── static-analysis.yml ├── .gitignore ├── .php_cs ├── LICENSE ├── README.md ├── UPGRADE.md ├── composer.json ├── config └── migrations.php ├── phpstan.neon.dist ├── phpunit.xml.dist ├── src ├── Configuration │ ├── ConfigurationFactory.php │ └── DependencyFactoryProvider.php ├── Console │ ├── BaseCommand.php │ ├── DiffCommand.php │ ├── DumpSchemaCommand.php │ ├── ExecuteCommand.php │ ├── GenerateCommand.php │ ├── LatestCommand.php │ ├── ListCommand.php │ ├── MigrateCommand.php │ ├── RefreshCommand.php │ ├── ResetCommand.php │ ├── RollbackCommand.php │ ├── RollupCommand.php │ ├── StatusCommand.php │ ├── SyncMetadataCommand.php │ └── VersionCommand.php ├── MigrationsServiceProvider.php └── Schema │ ├── Builder.php │ └── Table.php └── tests ├── CommandConfigurationTest.php ├── ConfigurationTest.php ├── Schema ├── SchemaBuilderTest.php └── SchemaTableTest.php └── bootstrap.php /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | push: 7 | pull_request: 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | 13 | name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} 14 | 15 | strategy: 16 | matrix: 17 | php: ['8.2', '8.3', '8.4'] 18 | laravel: ['10', '11', '12'] 19 | exclude: 20 | - php: '8.4' 21 | laravel: '10' 22 | 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | 27 | - name: Setup PHP 28 | uses: shivammathur/setup-php@v2 29 | with: 30 | php-version: ${{ matrix.php }} 31 | ini-values: error_reporting=E_ALL 32 | tools: phpunit, git 33 | 34 | - name: Install Composer dependencies 35 | run: rm -f composer.lock 36 | 37 | - name: Install dependencies for Laravel ${{ matrix.laravel}} 38 | run: composer require --no-progress --no-scripts --no-plugins illuminate/config ^${{ matrix.laravel }} illuminate/contracts ^${{ matrix.laravel }} illuminate/console ^${{ matrix.laravel }} -v 39 | 40 | - name: Update dependencies 41 | run: composer update --no-interaction 42 | 43 | - name: PHPUnit 44 | run: vendor/bin/phpunit 45 | -------------------------------------------------------------------------------- /.github/workflows/static-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Static Analysis" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | static-analysis-phpstan: 9 | name: "Static Analysis with PHPStan" 10 | runs-on: "ubuntu-22.04" 11 | 12 | strategy: 13 | matrix: 14 | php-version: 15 | - "8.2" 16 | - "8.3" 17 | - "8.4" 18 | 19 | steps: 20 | - name: "Checkout code" 21 | uses: "actions/checkout@v3" 22 | 23 | - name: "Install PHP" 24 | uses: "shivammathur/setup-php@v2" 25 | with: 26 | coverage: "none" 27 | php-version: "${{ matrix.php-version }}" 28 | extensions: "pdo_sqlite" 29 | 30 | - name: "Install dependencies with Composer" 31 | uses: "ramsey/composer-install@v2" 32 | with: 33 | dependency-versions: "${{ matrix.dependencies }}" 34 | 35 | - name: "Run a static analysis with phpstan/phpstan" 36 | run: "vendor/bin/phpstan analyse -c phpstan.neon.dist" 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | .php_cs.cache 6 | 7 | .idea 8 | laravel-doctrine-orm.iml 9 | phpunit.xml 10 | .phpunit.result.cache -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | exclude('vendor') 5 | ->in(__DIR__); 6 | 7 | return Symfony\CS\Config\Config::create() 8 | ->setUsingCache(true) 9 | ->level(Symfony\CS\FixerInterface::PSR2_LEVEL) 10 | ->fixers(array( 11 | 'psr4', 12 | 'encoding', 13 | 'short_tag', 14 | 'blankline_after_open_tag', 15 | 'namespace_no_leading_whitespace', 16 | 'no_blank_lines_after_class_opening', 17 | 'single_array_no_trailing_comma', 18 | 'no_empty_lines_after_phpdocs', 19 | 'concat_with_spaces', 20 | 'eof_ending', 21 | 'ordered_use', 22 | 'extra_empty_lines', 23 | 'single_line_after_imports', 24 | 'trailing_spaces', 25 | 'remove_lines_between_uses', 26 | 'return', 27 | 'indentation', 28 | 'linefeed', 29 | 'braces', 30 | 'visibility', 31 | 'unused_use', 32 | 'whitespacy_lines', 33 | 'php_closing_tag', 34 | 'phpdoc_order', 35 | 'phpdoc_params', 36 | 'phpdoc_trim', 37 | 'phpdoc_scalar', 38 | 'short_array_syntax', 39 | 'align_double_arrow', 40 | 'align_equals', 41 | 'lowercase_constants', 42 | 'lowercase_keywords', 43 | 'multiple_use', 44 | 'line_after_namespace', 45 | ))->finder($finder); 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Laravel Doctrine 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Doctrine Migrations 2 | 3 | 4 | 5 | [![GitHub release](https://img.shields.io/github/release/laravel-doctrine/migrations.svg?style=flat-square)](https://packagist.org/packages/laravel-doctrine/migrations) 6 | [![Github actions](https://github.com/laravel-doctrine/migrations/workflows/CI/badge.svg?branch=2.0)](https://github.com/laravel-doctrine/migrations/actions?query=workflow%3ACI+branch%3A2.0) 7 | [![Scrutinizer](https://img.shields.io/scrutinizer/g/laravel-doctrine/migrations.svg?style=flat-square)](https://github.com/laravel-doctrine/migrations) 8 | [![Packagist](https://img.shields.io/packagist/dm/laravel-doctrine/migrations.svg?style=flat-square)](https://packagist.org/packages/laravel-doctrine/migrations) 9 | [![Packagist](https://img.shields.io/packagist/dt/laravel-doctrine/migrations.svg?style=flat-square)](https://packagist.org/packages/laravel-doctrine/migrations) 10 | 11 | *Doctrine Migrations for Laravel* 12 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade to 2.0 2 | 3 | ## Breaking Changes 4 | With the move to doctrine/migrations 2.0 you will need to update all your existing migration files 5 | There is **no** need to re-run you migrations after these changes 6 | 7 | ### Update `use` Statements 8 | At the top of you migration file update two `use` statements as bellow 9 | - `use Doctrine\Schema\Schema` -> `use Doctrine\DBAL\Schema\Schema` 10 | - `use Doctrine\DBAL\Migrations\AbstractMigration` -> `use Doctrine\Migrations\AbstractMigration` 11 | 12 | ### Update method declarations 13 | Both the `up` and `down` methods need the `void` return type adding. 14 | 15 | Before 16 | ``` 17 | public function up(Schema $schema) 18 | public function down(Schema $schema) 19 | ``` 20 | After 21 | ``` 22 | public function up(Schema $schema): void 23 | public function down(Schema $schema): void 24 | ``` 25 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel-doctrine/migrations", 3 | "type": "library", 4 | "description": "Doctrine Migrations for Laravel", 5 | "license": "MIT", 6 | "keywords": [ 7 | "doctrine", 8 | "laravel", 9 | "orm", 10 | "data mapper", 11 | "database", 12 | "migrations" 13 | ], 14 | "authors": [ 15 | { 16 | "name": "Patrick Brouwers", 17 | "email": "patrick@maatwebsite.nl" 18 | } 19 | ], 20 | "require": { 21 | "php": "^8", 22 | "doctrine/migrations": "^3.4", 23 | "doctrine/dbal": "^2.10.1|^3", 24 | "illuminate/config": "^9.0|^10.0|^11.0|^12.0", 25 | "illuminate/contracts": "^9.0|^10.0|^11.0|^12.0", 26 | "illuminate/console": "^9.0|^10.0|^11.0|^12.0", 27 | "laravel-doctrine/orm": "^3.1.0" 28 | }, 29 | "require-dev": { 30 | "phpunit/phpunit": "^7.0 | ^8.3 | ^9.3", 31 | "phpstan/phpstan": "^1.9", 32 | "mockery/mockery": "^1.3.1" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "LaravelDoctrine\\Migrations\\": "src/" 37 | } 38 | }, 39 | "extra": { 40 | "laravel": { 41 | "providers": [ 42 | "LaravelDoctrine\\Migrations\\MigrationsServiceProvider" 43 | ] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /config/migrations.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'table_storage' => [ 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Migration Repository Table 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This table keeps track of all the migrations that have already run for 26 | | your application. Using this information, we can determine which of 27 | | the migrations on disk haven't actually been run in the database. 28 | | 29 | */ 30 | 'table_name' => 'migrations', 31 | 32 | /* 33 | |-------------------------------------------------------------------------- 34 | | Schema filter 35 | |-------------------------------------------------------------------------- 36 | | 37 | | Tables which are filtered by Regular Expression. You optionally 38 | | exclude or limit to certain tables. The default will 39 | | filter all tables. 40 | | 41 | */ 42 | 'schema_filter' => '/^(?!password_resets|failed_jobs).*$/' 43 | ], 44 | 45 | 'migrations_paths' => [ 46 | 'Database\\Migrations' => database_path('migrations') 47 | ], 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Migration Organize Directory 52 | |-------------------------------------------------------------------------- 53 | | 54 | | Organize migrations file by directory. 55 | | Possible values: "year", "year_and_month" and "none" 56 | | 57 | | none: 58 | | directory/ 59 | | "year": 60 | | directory/2020/ 61 | | "year_and_month": 62 | | directory/2020/01/ 63 | | 64 | */ 65 | 'organize_migrations' => 'none', 66 | ], 67 | ]; 68 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 7 3 | paths: 4 | - src 5 | 6 | ignoreErrors: 7 | - 8 | message: '~^Function config_path not found.$~' 9 | - 10 | message: '~^Function database_path not found.$~' 11 | - 12 | message: '~^Used function database_path not found.$~' 13 | - 14 | path: src/Console 15 | message: '~^Parameter #1 \$name of method LaravelDoctrine\\Migrations\\Configuration\\DependencyFactoryProvider::fromEntityManagerName\(\)~' 16 | - 17 | path: src/Console 18 | message: '~^Parameter #1 \$name of method LaravelDoctrine\\Migrations\\Configuration\\ConfigurationFactory::getConfigAsRepository\(\)~' 19 | - 20 | path: src/MigrationsServiceProvider.php 21 | message: '~^Call to an undefined method Illuminate\\Contracts\\Foundation\\Application::configure\(\).~' 22 | - 23 | path: src/Configuration/DependencyFactoryProvider.php 24 | message: '~^Parameter #1 \$entityManager of class Doctrine\\Migrations\\Configuration\\EntityManager\\ExistingEntityManager constructor expects Doctrine\\ORM\\EntityManagerInterface, Doctrine\\Persistence\\ObjectManager given.~' 25 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | src/ 17 | 18 | 19 | 20 | 21 | ./tests/ 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Configuration/ConfigurationFactory.php: -------------------------------------------------------------------------------- 1 | config = $config; 24 | } 25 | 26 | /** 27 | * @param string|null $name The EntityManager name 28 | * @return array The configuration (see config/migrations.php) 29 | */ 30 | public function getConfig(?string $name = null): array 31 | { 32 | if ($name && $this->config->has('migrations.' . $name)) { 33 | return $this->config->get('migrations.' . $name, []); 34 | } 35 | return $this->config->get('migrations.default', []); 36 | } 37 | 38 | public function getConfigAsRepository(?string $name = null): Repository 39 | { 40 | return new Repository($this->getConfig($name)); 41 | } 42 | 43 | public function make(?string $name = null): ConfigurationArray 44 | { 45 | $config = $this->getConfigAsRepository($name); 46 | 47 | $configAsArray = array_merge($config->all(), [ 48 | 'table_storage' => $config->get('table_storage', [ 49 | 'table_name' => $config->get('table', 'migrations'), 50 | 'version_column_length' => $config->get('version_column_length', 191) 51 | ]), 52 | 'migrations_paths' => $config->get('migrations_paths', [ 53 | $config->get('namespace', 'Database\\Migrations') => $config->get( 54 | 'directory', 55 | database_path('migrations') 56 | ) 57 | ]), 58 | 'organize_migrations' => $config->get('organize_migrations') ?: 'none' 59 | ]); 60 | 61 | // Unset previous laravel-doctrine configuration structure 62 | unset( 63 | $configAsArray['table_storage']['schema_filter'], 64 | $configAsArray['table'], 65 | $configAsArray['directory'], 66 | $configAsArray['namespace'], 67 | $configAsArray['schema'], 68 | $configAsArray['version_column_length'] 69 | ); 70 | 71 | return new ConfigurationArray($configAsArray); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Configuration/DependencyFactoryProvider.php: -------------------------------------------------------------------------------- 1 | registry = $registry; 21 | $this->factory = $factory; 22 | } 23 | 24 | /** 25 | * @param string|null $name 26 | * 27 | * @return DependencyFactory 28 | */ 29 | public function fromEntityManagerName(?string $name = null): DependencyFactory 30 | { 31 | $configuration = $this->factory->make($name); 32 | return DependencyFactory::fromEntityManager( 33 | $configuration, 34 | new ExistingEntityManager($this->registry->getManager($name)) 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Console/BaseCommand.php: -------------------------------------------------------------------------------- 1 | getDefinition(); 21 | $inputArgs = []; 22 | 23 | foreach ($definition->getArguments() as $argument) { 24 | $argName = $argument->getName(); 25 | 26 | if ($argName === 'command' || !$this->argumentExists($command, $argName)) { 27 | continue; 28 | } 29 | 30 | if ($this->hasArgument($argName)) { 31 | $inputArgs[$argName] = $this->argument($argName); 32 | } 33 | } 34 | 35 | foreach ($definition->getOptions() as $option) { 36 | $optionName = $option->getName(); 37 | 38 | if ($optionName === 'em' || !$this->optionExists($command, $optionName)) { 39 | continue; 40 | } 41 | 42 | if ($this->input->hasOption($optionName)) { 43 | $inputArgs['--' . $optionName] = $this->input->getOption($optionName); 44 | } 45 | } 46 | 47 | $input = new ArrayInput($inputArgs); 48 | $input->setInteractive(!($this->input->getOption("no-interaction") ?? false)); 49 | 50 | return $input; 51 | } 52 | 53 | private function argumentExists(\Symfony\Component\Console\Command\Command $command, string $argName): bool 54 | { 55 | foreach ($command->getDefinition()->getArguments() as $arg) { 56 | if ($arg->getName() === $argName) { 57 | return true; 58 | } 59 | } 60 | return false; 61 | } 62 | 63 | private function optionExists(\Symfony\Component\Console\Command\Command $command, string $optionName): bool 64 | { 65 | foreach ($command->getDefinition()->getOptions() as $option) { 66 | if ($option->getName() === $optionName) { 67 | return true; 68 | } 69 | } 70 | return false; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Console/DiffCommand.php: -------------------------------------------------------------------------------- 1 | getDefinition()->getOption('check-database-platform')->setDefault(false); 37 | } 38 | 39 | /** 40 | * Execute the console command. 41 | * 42 | * @param DependencyFactoryProvider $provider 43 | */ 44 | public function handle( 45 | DependencyFactoryProvider $provider, 46 | ConfigurationFactory $configurationFactory 47 | ): int { 48 | $dependencyFactory = $provider->fromEntityManagerName($this->option('em')); 49 | $migrationConfig = $configurationFactory->getConfigAsRepository($this->option('em')); 50 | 51 | $command = new \Doctrine\Migrations\Tools\Console\Command\DiffCommand($dependencyFactory); 52 | 53 | if ($this->input->getOption('filter-expression') === null) { 54 | $this->input->setOption('filter-expression', $migrationConfig->get('table_storage.schema_filter', $migrationConfig->get('schema.filter'))); 55 | } 56 | 57 | try { 58 | return $command->run($this->getDoctrineInput($command), $this->output->getOutput()); 59 | } catch (NoChangesDetected $exception) { 60 | $this->error($exception->getMessage()); 61 | return 0; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Console/DumpSchemaCommand.php: -------------------------------------------------------------------------------- 1 | fromEntityManagerName($this->option('em')); 26 | 27 | $command = new \Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand($dependencyFactory); 28 | 29 | return $command->run($this->getDoctrineInput($command), $this->output->getOutput()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Console/ExecuteCommand.php: -------------------------------------------------------------------------------- 1 | getDefinition()->getOption('write-sql')->setDefault(false); 33 | } 34 | 35 | /** 36 | * Execute the console command. 37 | * 38 | * @param DependencyFactoryProvider $provider 39 | */ 40 | public function handle(DependencyFactoryProvider $provider): int 41 | { 42 | $dependencyFactory = $provider->fromEntityManagerName($this->option('em')); 43 | 44 | $command = new \Doctrine\Migrations\Tools\Console\Command\ExecuteCommand($dependencyFactory); 45 | 46 | $this->getDefinition()->getOption('write-sql')->setDefault(false); 47 | 48 | return $command->run($this->getDoctrineInput($command), $this->output->getOutput()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Console/GenerateCommand.php: -------------------------------------------------------------------------------- 1 | fromEntityManagerName($this->option('em')); 33 | 34 | $command = new \Doctrine\Migrations\Tools\Console\Command\GenerateCommand($dependencyFactory); 35 | 36 | return $command->run($this->getDoctrineInput($command), $this->output->getOutput()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Console/LatestCommand.php: -------------------------------------------------------------------------------- 1 | fromEntityManagerName($this->option('em')); 31 | 32 | $command = new \Doctrine\Migrations\Tools\Console\Command\LatestCommand($dependencyFactory); 33 | 34 | return $command->run($this->getDoctrineInput($command), $this->output->getOutput()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Console/ListCommand.php: -------------------------------------------------------------------------------- 1 | fromEntityManagerName($this->option('em')); 31 | 32 | $command = new \Doctrine\Migrations\Tools\Console\Command\ListCommand($dependencyFactory); 33 | 34 | return $command->run($this->getDoctrineInput($command), $this->output->getOutput()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Console/MigrateCommand.php: -------------------------------------------------------------------------------- 1 | getDefinition()->getOption('write-sql')->setDefault(false); 36 | } 37 | 38 | /** 39 | * Execute the console command. 40 | * 41 | * @param DependencyFactoryProvider $provider 42 | * @return int 43 | * @throws \Exception 44 | */ 45 | public function handle(DependencyFactoryProvider $provider): int 46 | { 47 | $dependencyFactory = $provider->fromEntityManagerName($this->option('em')); 48 | 49 | $command = new \Doctrine\Migrations\Tools\Console\Command\MigrateCommand($dependencyFactory); 50 | 51 | return $command->run($this->getDoctrineInput($command), $this->output->getOutput()); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/Console/RefreshCommand.php: -------------------------------------------------------------------------------- 1 | call('doctrine:migrations:reset', [ 29 | '--em' => $this->option('em') 30 | ]); 31 | 32 | if ($resetReturn !== 0) { 33 | return 1; 34 | } 35 | 36 | return $this->call('doctrine:migrations:migrate', [ 37 | '--em' => $this->option('em') 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Console/ResetCommand.php: -------------------------------------------------------------------------------- 1 | confirmToProceed()) { 39 | return 1; 40 | } 41 | 42 | $dependencyFactory = $provider->fromEntityManagerName( 43 | $this->option('em') 44 | ); 45 | $this->connection = $dependencyFactory->getConnection(); 46 | 47 | $this->safelyDropTables(); 48 | 49 | $this->info('Database was reset'); 50 | return 0; 51 | } 52 | 53 | private function safelyDropTables(): void 54 | { 55 | $this->throwExceptionIfPlatformIsNotSupported(); 56 | 57 | if (method_exists($this->connection, 'createSchemaManager')) { 58 | $schemaManager = $this->connection->createSchemaManager(); 59 | } else { 60 | $schemaManager = $this->connection->getSchemaManager(); 61 | } 62 | 63 | if ($this->connection->getDatabasePlatform()->supportsSequences()) { 64 | $sequences = $schemaManager->listSequences(); 65 | foreach ($sequences as $s) { 66 | $schemaManager->dropSequence($s->getQuotedName($this->connection->getDatabasePlatform())); 67 | } 68 | } 69 | 70 | $tables = $schemaManager->listTableNames(); 71 | foreach ($tables as $table) { 72 | $foreigns = $schemaManager->listTableForeignKeys($table); 73 | foreach ($foreigns as $f) { 74 | $schemaManager->dropForeignKey($f, $table); 75 | } 76 | } 77 | 78 | foreach ($tables as $table) { 79 | $this->safelyDropTable($table); 80 | } 81 | } 82 | 83 | /** 84 | * @throws Exception 85 | */ 86 | private function throwExceptionIfPlatformIsNotSupported(): void 87 | { 88 | $platformName = $this->connection->getDatabasePlatform()->getName(); 89 | 90 | if (!array_key_exists($platformName, $this->getCardinalityCheckInstructions())) { 91 | throw new Exception(sprintf('The platform %s is not supported', $platformName)); 92 | } 93 | } 94 | 95 | /** 96 | * @param string $table 97 | * @throws \Doctrine\DBAL\Exception 98 | */ 99 | private function safelyDropTable(string $table): void 100 | { 101 | $platformName = $this->connection->getDatabasePlatform()->getName(); 102 | $instructions = $this->getCardinalityCheckInstructions()[$platformName]; 103 | 104 | $queryDisablingCardinalityChecks = $instructions['needsTableIsolation'] ? 105 | sprintf($instructions['disable'], $table) : 106 | $instructions['disable']; 107 | $this->connection->query($queryDisablingCardinalityChecks); 108 | 109 | $schema = $this->connection->getSchemaManager(); 110 | $schema->dropTable($table); 111 | 112 | // When table is already dropped we cannot enable any cardinality checks on it 113 | // See https://github.com/laravel-doctrine/migrations/issues/50 114 | if (!$instructions['needsTableIsolation']) { 115 | $this->connection->query($instructions['enable']); 116 | } 117 | } 118 | 119 | /** 120 | * @return array> 121 | */ 122 | private function getCardinalityCheckInstructions(): array 123 | { 124 | return [ 125 | 'mssql' => [ 126 | 'needsTableIsolation' => true, 127 | 'disable' => 'ALTER TABLE %s CHECK CONSTRAINT ALL', 128 | ], 129 | 'mysql' => [ 130 | 'needsTableIsolation' => false, 131 | 'enable' => 'SET FOREIGN_KEY_CHECKS = 1', 132 | 'disable' => 'SET FOREIGN_KEY_CHECKS = 0', 133 | ], 134 | 'postgresql' => [ 135 | 'needsTableIsolation' => true, 136 | 'disable' => 'ALTER TABLE %s DISABLE TRIGGER ALL', 137 | ], 138 | 'sqlite' => [ 139 | 'needsTableIsolation' => false, 140 | 'enable' => 'PRAGMA foreign_keys = ON', 141 | 'disable' => 'PRAGMA foreign_keys = OFF', 142 | ], 143 | ]; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Console/RollbackCommand.php: -------------------------------------------------------------------------------- 1 | call('doctrine:migrations:migrate', [ 31 | 'version' => $this->argument('version'), 32 | '--em' => $this->option('em') 33 | ]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Console/RollupCommand.php: -------------------------------------------------------------------------------- 1 | fromEntityManagerName($this->option('em')); 23 | 24 | $command = new \Doctrine\Migrations\Tools\Console\Command\RollupCommand($dependencyFactory); 25 | 26 | return $command->run($this->getDoctrineInput($command), $this->output->getOutput()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Console/StatusCommand.php: -------------------------------------------------------------------------------- 1 | fromEntityManagerName($this->option('em')); 31 | 32 | $command = new \Doctrine\Migrations\Tools\Console\Command\StatusCommand($dependencyFactory); 33 | 34 | return $command->run($this->getDoctrineInput($command), $this->output->getOutput()); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/Console/SyncMetadataCommand.php: -------------------------------------------------------------------------------- 1 | fromEntityManagerName($this->option('em')); 30 | 31 | $command = new \Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand($dependencyFactory); 32 | 33 | return $command->run($this->getDoctrineInput($command), $this->output->getOutput()); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/Console/VersionCommand.php: -------------------------------------------------------------------------------- 1 | fromEntityManagerName($this->option('em')); 36 | 37 | $command = new \Doctrine\Migrations\Tools\Console\Command\VersionCommand($dependencyFactory); 38 | return $command->run($this->getDoctrineInput($command), $this->output->getOutput()); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/MigrationsServiceProvider.php: -------------------------------------------------------------------------------- 1 | isLumen()) { 38 | $this->publishes([ 39 | $this->getConfigPath() => config_path('migrations.php'), 40 | ], 'config'); 41 | } 42 | } 43 | 44 | /** 45 | * Register the service provider. 46 | * @return void 47 | */ 48 | public function register() 49 | { 50 | $this->mergeConfig(); 51 | 52 | $this->commands([ 53 | DiffCommand::class, 54 | ResetCommand::class, 55 | LatestCommand::class, 56 | StatusCommand::class, 57 | MigrateCommand::class, 58 | ExecuteCommand::class, 59 | VersionCommand::class, 60 | RefreshCommand::class, 61 | RollbackCommand::class, 62 | GenerateCommand::class, 63 | SyncMetadataCommand::class, 64 | ListCommand::class, 65 | DumpSchemaCommand::class 66 | ]); 67 | } 68 | 69 | /** 70 | * Merge config 71 | */ 72 | protected function mergeConfig(): void 73 | { 74 | if ($this->isLumen()) { 75 | $this->app->configure('migrations'); 76 | } 77 | 78 | $this->mergeConfigFrom( 79 | $this->getConfigPath(), 'migrations' 80 | ); 81 | } 82 | 83 | /** 84 | * @return string 85 | */ 86 | protected function getConfigPath(): string 87 | { 88 | return __DIR__ . '/../config/migrations.php'; 89 | } 90 | 91 | /** 92 | * @return class-string[] 93 | */ 94 | public function provides(): array 95 | { 96 | return [ 97 | DependencyFactoryProvider::class 98 | ]; 99 | } 100 | 101 | /** 102 | * @return bool 103 | */ 104 | protected function isLumen(): bool 105 | { 106 | return Str::contains($this->app->version(), 'Lumen'); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Schema/Builder.php: -------------------------------------------------------------------------------- 1 | schema = $schema; 25 | } 26 | 27 | /** 28 | * Modify a table on the schema. 29 | * 30 | * @param string $table 31 | * @param Closure $callback 32 | * 33 | * @return \Doctrine\DBAL\Schema\Table|string 34 | */ 35 | public function create(string $table, Closure $callback) 36 | { 37 | $table = $this->schema->createTable($table); 38 | 39 | $callback($this->build($table)); 40 | 41 | return $table; 42 | } 43 | 44 | /** 45 | * Create a new table on the schema. 46 | * 47 | * @param string $table 48 | * @param Closure $callback 49 | * 50 | * @return \Doctrine\DBAL\Schema\Table|string 51 | * @throws SchemaException 52 | */ 53 | public function table(string $table, Closure $callback) 54 | { 55 | $table = $this->schema->getTable($table); 56 | 57 | $callback($this->build($table)); 58 | 59 | return $table; 60 | } 61 | 62 | /** 63 | * Drop a table from the schema. 64 | * 65 | * @param string $table 66 | * 67 | * @return Schema|string 68 | */ 69 | public function drop(string $table) 70 | { 71 | return $this->schema->dropTable($table); 72 | } 73 | 74 | /** 75 | * Drop a table from the schema if it exists. 76 | * 77 | * @param string $table 78 | * 79 | * @return Schema|string|null 80 | */ 81 | public function dropIfExists(string $table) 82 | { 83 | if ($this->schema->hasTable($table)) { 84 | return $this->drop($table); 85 | } 86 | 87 | return null; 88 | } 89 | 90 | /** 91 | * Rename a table on the schema. 92 | * 93 | * @param string $from 94 | * @param string $to 95 | * 96 | * @return Schema|string 97 | */ 98 | public function rename(string $from, string $to) 99 | { 100 | return $this->schema->renameTable($from, $to); 101 | } 102 | 103 | /** 104 | * Determine if the given table exists. 105 | * 106 | * @param string $table 107 | * 108 | * @return bool 109 | */ 110 | public function hasTable(string $table): bool 111 | { 112 | return $this->schema->hasTable($table); 113 | } 114 | 115 | /** 116 | * Determine if the given table has a given column. 117 | * 118 | * @param string $table 119 | * @param string $column 120 | * 121 | * @return bool 122 | * @throws SchemaException 123 | */ 124 | public function hasColumn(string $table, string $column): bool 125 | { 126 | return $this->schema->getTable($table)->hasColumn($column); 127 | } 128 | 129 | /** 130 | * Determine if the given table has given columns. 131 | * 132 | * @param string $table 133 | * @param string[] $columns 134 | * 135 | * @return bool 136 | * @throws SchemaException 137 | */ 138 | public function hasColumns(string $table, array $columns): bool 139 | { 140 | $tableColumns = array_map('strtolower', array_keys($this->getColumnListing($table))); 141 | 142 | foreach ($columns as $column) { 143 | if (!in_array(strtolower($column), $tableColumns)) { 144 | return false; 145 | } 146 | } 147 | 148 | return true; 149 | } 150 | 151 | /** 152 | * Get the column listing for a given table. 153 | * 154 | * @param string $table 155 | * 156 | * @return Column[] 157 | * @throws SchemaException 158 | */ 159 | public function getColumnListing(string $table): array 160 | { 161 | return $this->schema->getTable($table)->getColumns(); 162 | } 163 | 164 | /** 165 | * @param \Doctrine\DBAL\Schema\Table $table 166 | * @param Closure|null $callback 167 | * 168 | * @return Table 169 | */ 170 | protected function build($table, ?Closure $callback = null): Table 171 | { 172 | return new Table($table, $callback); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/Schema/Table.php: -------------------------------------------------------------------------------- 1 | table = $table; 27 | 28 | if (!is_null($callback)) { 29 | $callback($this); 30 | } 31 | } 32 | 33 | /** 34 | * Create a new guid column on the table. 35 | * 36 | * @param string $column 37 | * 38 | * @return Column|null 39 | */ 40 | public function guid(string $column): ?Column 41 | { 42 | return $this->table->addColumn($column, Types::GUID); 43 | } 44 | 45 | /** 46 | * Specify the primary key(s) for the table. 47 | * 48 | * @param string|string[] $columns 49 | * @param string|false $indexName 50 | * 51 | * @return Blueprint|null 52 | */ 53 | public function primary($columns, $indexName = false): ?Blueprint 54 | { 55 | $columns = is_array($columns) ? $columns : [$columns]; 56 | 57 | return $this->table->setPrimaryKey($columns, $indexName); 58 | } 59 | 60 | /** 61 | * Specify a unique index for the table. 62 | * 63 | * @param string|string[] $columns 64 | * @param string $name 65 | * @param mixed[] $options 66 | * 67 | * @return Blueprint|null 68 | */ 69 | public function unique($columns, $name = null, $options = []): ?Blueprint 70 | { 71 | $columns = is_array($columns) ? $columns : [$columns]; 72 | 73 | return $this->table->addUniqueIndex($columns, $name, $options); 74 | } 75 | 76 | /** 77 | * Specify an index for the table. 78 | * 79 | * @param string|string[] $columns 80 | * @param string $name 81 | * @param string[] $flags 82 | * @param mixed[] $options 83 | * 84 | * @return Blueprint|null 85 | */ 86 | public function index($columns, $name = null, $flags = [], $options = []): ?Blueprint 87 | { 88 | $columns = is_array($columns) ? $columns : [$columns]; 89 | 90 | return $this->table->addIndex($columns, $name, $flags, $options); 91 | } 92 | 93 | /** 94 | * Specify a foreign key for the table. 95 | * 96 | * @param string $table 97 | * @param string[]|string $localColumnNames 98 | * @param string[]|string $foreignColumnNames 99 | * @param mixed[] $options 100 | * @param null $constraintName 101 | * 102 | * @return Blueprint|null 103 | */ 104 | public function foreign( 105 | string $table, 106 | $localColumnNames, 107 | $foreignColumnNames = 'id', 108 | $options = [], 109 | $constraintName = null 110 | ): ?Blueprint 111 | { 112 | $localColumnNames = is_array($localColumnNames) ? $localColumnNames : [$localColumnNames]; 113 | $foreignColumnNames = is_array($foreignColumnNames) ? $foreignColumnNames : [$foreignColumnNames]; 114 | 115 | return $this->table->addForeignKeyConstraint($table, $localColumnNames, $foreignColumnNames, $options, 116 | $constraintName); 117 | } 118 | 119 | /** 120 | * Create a new auto-incrementing integer (4-byte) column on the table. 121 | * 122 | * @param string $columnName 123 | * 124 | * @return Column|null 125 | */ 126 | public function increments(string $columnName): ?Column 127 | { 128 | $column = $this->integer($columnName, true, true); 129 | $this->primary($columnName); 130 | 131 | return $column; 132 | } 133 | 134 | /** 135 | * Create a new auto-incrementing small integer (2-byte) column on the table. 136 | * 137 | * @param string $columnName 138 | * 139 | * @return Column|null 140 | */ 141 | public function smallIncrements(string $columnName): ?Column 142 | { 143 | $column = $this->smallInteger($columnName, true, true); 144 | $this->primary($columnName); 145 | 146 | return $column; 147 | } 148 | 149 | /** 150 | * Create a new auto-incrementing big integer (8-byte) column on the table. 151 | * 152 | * @param string $columnName 153 | * 154 | * @return Column|null 155 | */ 156 | public function bigIncrements(string $columnName): ?Column 157 | { 158 | $column = $this->bigInteger($columnName, true, true); 159 | $this->primary($columnName); 160 | 161 | return $column; 162 | } 163 | 164 | /** 165 | * Create a new string column on the table. 166 | * 167 | * @param string $column 168 | * @param int $length 169 | * 170 | * @return Column|null 171 | */ 172 | public function string(string $column, $length = 255): ?Column 173 | { 174 | return $this->table->addColumn($column, Types::STRING, compact('length')); 175 | } 176 | 177 | /** 178 | * Create a new text column on the table. 179 | * 180 | * @param string $column 181 | * 182 | * @return Column|null 183 | */ 184 | public function text(string $column): ?Column 185 | { 186 | return $this->table->addColumn($column, Types::TEXT); 187 | } 188 | 189 | /** 190 | * Create a new integer (4-byte) column on the table. 191 | * 192 | * @param string $column 193 | * @param bool $autoIncrement 194 | * @param bool $unsigned 195 | * 196 | * @return Column|null 197 | */ 198 | public function integer(string $column, $autoIncrement = false, $unsigned = false): ?Column 199 | { 200 | return $this->table->addColumn($column, Types::INTEGER, compact('autoIncrement', 'unsigned')); 201 | } 202 | 203 | /** 204 | * Create a new small integer (2-byte) column on the table. 205 | * 206 | * @param string $column 207 | * @param bool $autoIncrement 208 | * @param bool $unsigned 209 | * 210 | * @return Column|null 211 | */ 212 | public function smallInteger(string $column, $autoIncrement = false, $unsigned = false): ?Column 213 | { 214 | return $this->table->addColumn($column, Types::SMALLINT, compact('autoIncrement', 'unsigned')); 215 | } 216 | 217 | /** 218 | * Create a new big integer (8-byte) column on the table. 219 | * 220 | * @param string $column 221 | * @param bool $autoIncrement 222 | * @param bool $unsigned 223 | * 224 | * @return Column|null 225 | */ 226 | public function bigInteger(string $column, $autoIncrement = false, $unsigned = false): ?Column 227 | { 228 | return $this->table->addColumn($column, Types::BIGINT, compact('autoIncrement', 'unsigned')); 229 | } 230 | 231 | /** 232 | * Create a new unsigned small integer (2-byte) column on the table. 233 | * 234 | * @param string $column 235 | * @param bool $autoIncrement 236 | * 237 | * @return Column|null 238 | */ 239 | public function unsignedSmallInteger(string $column, $autoIncrement = false): ?Column 240 | { 241 | return $this->smallInteger($column, $autoIncrement, true); 242 | } 243 | 244 | /** 245 | * Create a new unsigned integer (4-byte) column on the table. 246 | * 247 | * @param string $column 248 | * @param bool $autoIncrement 249 | * 250 | * @return Column|null 251 | */ 252 | public function unsignedInteger(string $column, $autoIncrement = false): ?Column 253 | { 254 | return $this->integer($column, $autoIncrement, true); 255 | } 256 | 257 | /** 258 | * Create a new unsigned big integer (8-byte) column on the table. 259 | * 260 | * @param string $column 261 | * @param bool $autoIncrement 262 | * 263 | * @return Column|null 264 | */ 265 | public function unsignedBigInteger(string $column, $autoIncrement = false): ?Column 266 | { 267 | return $this->bigInteger($column, $autoIncrement, true); 268 | } 269 | 270 | /** 271 | * Create a new float column on the table. 272 | * 273 | * @param string $column 274 | * @param int $precision 275 | * @param int $scale 276 | * 277 | * @return Column|null 278 | */ 279 | public function float(string $column, $precision = 8, $scale = 2): ?Column 280 | { 281 | return $this->table->addColumn($column, Types::FLOAT, compact('precision', 'scale')); 282 | } 283 | 284 | /** 285 | * Create a new decimal column on the table. 286 | * 287 | * @param string $column 288 | * @param int $precision 289 | * @param int $scale 290 | * 291 | * @return Column|null 292 | */ 293 | public function decimal(string $column, $precision = 8, $scale = 2): ?Column 294 | { 295 | return $this->table->addColumn($column, Types::DECIMAL, compact('precision', 'scale')); 296 | } 297 | 298 | /** 299 | * Create a new boolean column on the table. 300 | * 301 | * @param string $column 302 | * 303 | * @return Column|null 304 | */ 305 | public function boolean(string $column): ?Column 306 | { 307 | return $this->table->addColumn($column, Types::BOOLEAN); 308 | } 309 | 310 | /** 311 | * Create a new json column on the table. 312 | * 313 | * @param string $column 314 | * 315 | * @return Column|null 316 | */ 317 | public function json(string $column): ?Column 318 | { 319 | return $this->table->addColumn($column, Types::JSON); 320 | } 321 | 322 | /** 323 | * Create a new date column on the table. 324 | * 325 | * @param string $column 326 | * 327 | * @return Column|null 328 | */ 329 | public function date(string $column): ?Column 330 | { 331 | return $this->table->addColumn($column, Types::DATE_MUTABLE); 332 | } 333 | 334 | /** 335 | * Create a new date-time column on the table. 336 | * 337 | * @param string $column 338 | * 339 | * @return Column|null 340 | */ 341 | public function dateTime(string $column): ?Column 342 | { 343 | return $this->table->addColumn($column, Types::DATETIME_MUTABLE); 344 | } 345 | 346 | /** 347 | * Create a new date-time column (with time zone) on the table. 348 | * 349 | * @param string $column 350 | * 351 | * @return Column|null 352 | */ 353 | public function dateTimeTz(string $column): ?Column 354 | { 355 | return $this->table->addColumn($column, Types::DATETIMETZ_MUTABLE); 356 | } 357 | 358 | /** 359 | * Create a new time column on the table. 360 | * 361 | * @param string $column 362 | * 363 | * @return Column|null 364 | */ 365 | public function time(string $column): ?Column 366 | { 367 | return $this->table->addColumn($column, Types::TIME_MUTABLE); 368 | } 369 | 370 | /** 371 | * Create a new timestamp column on the table. 372 | * 373 | * @param string $column 374 | * 375 | * @return Column|null 376 | */ 377 | public function timestamp(string $column): ?Column 378 | { 379 | return $this->table->addColumn($column, Types::DATETIME_MUTABLE); 380 | } 381 | 382 | /** 383 | * Create a new timestamp (with time zone) column on the table. 384 | * 385 | * @param string $column 386 | * 387 | * @return Column|null 388 | */ 389 | public function timestampTz(string $column): ?Column 390 | { 391 | return $this->table->addColumn($column, Types::DATETIMETZ_MUTABLE); 392 | } 393 | 394 | /** 395 | * Add nullable creation and update timestamps to the table. 396 | * @return void 397 | */ 398 | public function nullableTimestamps() 399 | { 400 | $this->timestamp('created_at')->setNotnull(false); 401 | 402 | $this->timestamp('updated_at')->setNotnull(false); 403 | } 404 | 405 | /** 406 | * Add creation and update timestamps to the table. 407 | * @return void 408 | */ 409 | public function timestamps() 410 | { 411 | $this->timestamp('created_at'); 412 | 413 | $this->timestamp('updated_at'); 414 | } 415 | 416 | /** 417 | * Add creation and update timestampTz columns to the table. 418 | * @return void 419 | */ 420 | public function timestampsTz() 421 | { 422 | $this->timestampTz('created_at'); 423 | 424 | $this->timestampTz('updated_at'); 425 | } 426 | 427 | /** 428 | * Add a "deleted at" timestamp for the table. 429 | * 430 | * @return Column|null 431 | */ 432 | public function softDeletes(): ?Column 433 | { 434 | return $this->timestamp('deleted_at')->setNotnull(false); 435 | } 436 | 437 | /** 438 | * Create a new binary column on the table. 439 | * 440 | * @param string $column 441 | * @param int $length 442 | * @return Column|null 443 | */ 444 | public function binary(string $column, $length = 255): ?Column 445 | { 446 | return $this->table->addColumn($column, Types::BINARY, compact('length'))->setNotnull(false); 447 | } 448 | 449 | /** 450 | * Adds the `remember_token` column to the table. 451 | * 452 | * @return Column|null 453 | */ 454 | public function rememberToken(): ?Column 455 | { 456 | return $this->string('remember_token', 100)->setNotnull(false); 457 | } 458 | 459 | /** 460 | * @return Blueprint 461 | */ 462 | public function getTable(): Blueprint 463 | { 464 | return $this->table; 465 | } 466 | 467 | /** 468 | * @return Blueprint|null 469 | */ 470 | public function dropColumn(string $column): ?Blueprint 471 | { 472 | return $this->table->dropColumn($column); 473 | } 474 | } 475 | -------------------------------------------------------------------------------- /tests/CommandConfigurationTest.php: -------------------------------------------------------------------------------- 1 | \Doctrine\Migrations\Tools\Console\Command\DiffCommand::class, 32 | ExecuteCommand::class => \Doctrine\Migrations\Tools\Console\Command\ExecuteCommand::class, 33 | LatestCommand::class => \Doctrine\Migrations\Tools\Console\Command\LatestCommand::class, 34 | StatusCommand::class => \Doctrine\Migrations\Tools\Console\Command\StatusCommand::class, 35 | MigrateCommand::class => \Doctrine\Migrations\Tools\Console\Command\MigrateCommand::class, 36 | VersionCommand::class => \Doctrine\Migrations\Tools\Console\Command\VersionCommand::class, 37 | GenerateCommand::class => \Doctrine\Migrations\Tools\Console\Command\GenerateCommand::class, 38 | SyncMetadataCommand::class => \Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand::class, 39 | RollupCommand::class => \Doctrine\Migrations\Tools\Console\Command\RollupCommand::class, 40 | ListCommand::class => \Doctrine\Migrations\Tools\Console\Command\ListCommand::class, 41 | DumpSchemaCommand::class => \Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand::class, 42 | ]; 43 | 44 | foreach ($commands as $ourCommandClass => $doctrineCommandClass) { 45 | /** @var Command $ourCommand */ 46 | $ourCommand = new $ourCommandClass(); 47 | /** @var DoctrineCommand $theirCommand */ 48 | $theirCommand = new $doctrineCommandClass(); 49 | 50 | self::assertDefinedSameOptions($ourCommand, $theirCommand); 51 | self::assertSameCommandConfiguration($ourCommand, $theirCommand); 52 | } 53 | } 54 | 55 | private static function assertSameCommandConfiguration(Command $ourCommand, DoctrineCommand $theirCommand): void 56 | { 57 | // We set a default value for these options 58 | $optionsIgnoredForRequired = ['em', 'filter-expression', 'filter-tables']; 59 | 60 | // Assert option configuration 61 | foreach ($ourCommand->getDefinition()->getOptions() as $ourOption) { 62 | foreach ($theirCommand->getDefinition()->getOptions() as $theirOption) { 63 | if ($ourOption->getName() === $theirOption->getName()) { 64 | // Assert property is required in our and their configuration 65 | if (in_array($ourOption->getName(), $optionsIgnoredForRequired) === false) { 66 | // Laravel do not mark CLI options as required when a value is specified.. 67 | if (empty($ourOption->getDefault())) { 68 | self::assertEquals($ourOption->isValueRequired(), $theirOption->isValueRequired(), "Mismatch 'required' state on option value for {$ourCommand->getName()} {$ourOption->getName()}. Their '{$theirOption->isValueRequired()}', our: '{$ourOption->isValueRequired()}'"); 69 | } 70 | } 71 | self::assertEquals($ourOption->acceptValue(), $theirOption->acceptValue(), "Mismatch 'acceptValue' state on option value for {$ourCommand->getName()} {$ourOption->getName()}. Their '{$theirOption->acceptValue()}', our: '{$ourOption->acceptValue()}'"); 72 | self::assertEquals($ourOption->isArray(), $theirOption->isArray(), "Mismatch array support for {$ourCommand->getName()} on argument {$ourOption->getName()}, should be " . var_export($theirOption->isArray(), true)); 73 | self::assertEquals($ourOption->isNegatable(), $theirOption->isNegatable(), "Mismatch 'negatable' for {$ourCommand->getName()} on argument {$ourOption->getName()}, should be " . var_export($theirOption->isNegatable(), true)); 74 | 75 | // Assert default values matches 76 | $theirDefaultValue = var_export($theirOption->getDefault(), true); 77 | $ourDefaultValue = var_export($ourOption->getDefault(), true); 78 | self::assertSame($ourOption->getDefault(), $theirOption->getDefault(), "Mismatch default value for {$ourCommand->getName()} {$ourOption->getName()}. Their: '{$theirDefaultValue}', our: '{$ourDefaultValue}'"); 79 | } 80 | } 81 | } 82 | 83 | // Assert argument configuration 84 | foreach ($ourCommand->getDefinition()->getArguments() as $ourArgument) { 85 | foreach ($theirCommand->getDefinition()->getArguments() as $theirArgument) { 86 | if ($ourArgument->getName() === $theirArgument->getName()) { 87 | self::assertEquals($ourArgument->isArray(), $theirArgument->isArray(), "Mismatch array support for {$ourCommand->getName()} on argument {$ourArgument->getName()}, should be " . var_export($theirArgument->isArray(), true)); 88 | self::assertEquals($ourArgument->isRequired(), $theirArgument->isRequired(), "Mismatch required state for {$ourCommand->getName()} on argument {$ourArgument->getName()}, should be " . var_export($theirArgument->isRequired(), true)); 89 | 90 | $theirDefaultValue = var_export($theirArgument->getDefault(), true); 91 | $ourDefaultValue = var_export($ourArgument->getDefault(), true); 92 | self::assertEquals($ourArgument->getDefault(), $theirArgument->getDefault(), "Mismatch default value for {$ourCommand->getName()} {$ourArgument->getName()}. Their: '{$theirDefaultValue}', our: '{$ourDefaultValue}'"); 93 | } 94 | } 95 | } 96 | } 97 | 98 | private static function assertDefinedSameOptions(Command $ourCommand, DoctrineCommand $theirCommand): void 99 | { 100 | // Options we do not support. They are defined on DoctrineCommand and applies for all their commands. 101 | $globalIgnoredOptions = [ 102 | 'configuration', 'conn', 'db-configuration', 'namespace' 103 | ]; 104 | 105 | $commandName = $ourCommand->getName(); 106 | 107 | $ourOptionNames = array_keys($ourCommand->getDefinition()->getOptions()); 108 | $theirOptionNames = array_keys($theirCommand->getDefinition()->getOptions()); 109 | 110 | $ourExtraOptions = []; 111 | $doctrineExtraOptions = []; 112 | 113 | foreach ($ourOptionNames as $ourOption) { 114 | if ($ourOption === 'connection') { 115 | // Our custom connection option 116 | continue; 117 | } 118 | 119 | if (in_array($ourOption, $theirOptionNames, true) === false) { 120 | $ourExtraOptions[] = $ourOption; 121 | } 122 | } 123 | 124 | 125 | foreach ($theirOptionNames as $theirOption) { 126 | if (in_array($theirOption, $globalIgnoredOptions, true)) { 127 | continue; 128 | } 129 | 130 | if (in_array($theirOption, $ourOptionNames, true) === false) { 131 | $doctrineExtraOptions[] = $theirOption; 132 | } 133 | } 134 | 135 | self::assertEmpty($ourExtraOptions, 'Command '.$commandName.' has options not supported by doctrine command: ' . implode(", ", $ourExtraOptions)); 136 | self::assertEmpty($doctrineExtraOptions, 'Command '.$commandName.' is missing options from doctrine command: ' . implode(", ", $doctrineExtraOptions)); 137 | 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/ConfigurationTest.php: -------------------------------------------------------------------------------- 1 | make(null)->getConfiguration(); 21 | 22 | self::assertSame($configRepository->get('migrations.default.table_storage.table_name'), $doctrineConfig->getMetadataStorageConfiguration()->getTableName()); 23 | self::assertSame(191, $doctrineConfig->getMetadataStorageConfiguration()->getVersionColumnLength()); 24 | self::assertSame($configRepository->get('migrations.default.migrations_paths'), $doctrineConfig->getMigrationDirectories()); 25 | self::assertSame(false, $doctrineConfig->areMigrationsOrganizedByYear()); 26 | self::assertSame(false, $doctrineConfig->areMigrationsOrganizedByYearAndMonth()); 27 | } 28 | 29 | public function testDefaultConfigurations2(): void 30 | { 31 | $configRepository =self::makeConfigRepository([ 32 | 'default' => [ 33 | 'table_storage' => [ 34 | 'table_name' => 'migrations2', 35 | 36 | 'version_column_length' => 192, 37 | 38 | 'schema_filter' => '/^(?!password_resets|failed_jobs).*$/' 39 | ], 40 | 41 | 'migrations_paths' => [ 42 | 'Database\\Migrations2' => database_path('migrations2') 43 | ], 44 | 45 | 'organize_migrations' => 'year_and_month', 46 | ], 47 | ]); 48 | 49 | $factory = new ConfigurationFactory($configRepository); 50 | $doctrineConfig = $factory->make(null)->getConfiguration(); 51 | 52 | self::assertSame($configRepository->get('migrations.default.table_storage.table_name'), $doctrineConfig->getMetadataStorageConfiguration()->getTableName()); 53 | self::assertSame($configRepository->get('migrations.default.table_storage.version_column_length'), $doctrineConfig->getMetadataStorageConfiguration()->getVersionColumnLength()); 54 | self::assertSame($configRepository->get('migrations.default.migrations_paths'), $doctrineConfig->getMigrationDirectories()); 55 | self::assertSame(true, $doctrineConfig->areMigrationsOrganizedByYear()); 56 | self::assertSame(true, $doctrineConfig->areMigrationsOrganizedByYearAndMonth()); 57 | } 58 | 59 | /** 60 | * This was the default configuration for laravel-doctrine/migrations before version 3.x 61 | */ 62 | public function testPreviousConfigurationStructure(): void 63 | { 64 | $configRepository = self::makeConfigRepository([ 65 | 'default' => [ 66 | 'table' => 'migrations', 67 | 'directory' => database_path('migrations'), 68 | 'organize_migrations' => false, 69 | 'namespace' => 'Database\\Migrations', 70 | 'schema' => [ 71 | 'filter' => '/^(?!password_resets|failed_jobs).*$/' 72 | ], 73 | 'version_column_length' => 191 74 | ] 75 | ]); 76 | $factory = new ConfigurationFactory($configRepository); 77 | $doctrineConfig = $factory->make(null)->getConfiguration(); 78 | 79 | self::assertSame($configRepository->get('migrations.default.table'), $doctrineConfig->getMetadataStorageConfiguration()->getTableName()); 80 | self::assertSame($configRepository->get('migrations.default.version_column_length'), $doctrineConfig->getMetadataStorageConfiguration()->getVersionColumnLength()); 81 | self::assertSame([$configRepository->get('migrations.default.namespace') => $configRepository->get('migrations.default.directory')], $doctrineConfig->getMigrationDirectories()); 82 | self::assertSame(false, $doctrineConfig->areMigrationsOrganizedByYear()); 83 | self::assertSame(false, $doctrineConfig->areMigrationsOrganizedByYearAndMonth()); 84 | } 85 | 86 | private static function makeConfigRepository(array $config) { 87 | return new Repository(['migrations' => $config]); 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /tests/Schema/SchemaBuilderTest.php: -------------------------------------------------------------------------------- 1 | schema = m::mock(Schema::class); 29 | $this->builder = new Builder($this->schema); 30 | } 31 | 32 | public function test_create() 33 | { 34 | $this->schema->shouldReceive('createTable') 35 | ->with('table_name')->once() 36 | ->andReturn(m::mock(\Doctrine\DBAL\Schema\Table::class)); 37 | 38 | $this->builder->create('table_name', function (Table $table) { 39 | $this->assertInstanceOf(Table::class, $table); 40 | }); 41 | } 42 | 43 | public function test_table() 44 | { 45 | $this->schema->shouldReceive('getTable') 46 | ->with('table_name')->once() 47 | ->andReturn(m::mock(\Doctrine\DBAL\Schema\Table::class)); 48 | 49 | $this->builder->table('table_name', function (Table $table) { 50 | $this->assertInstanceOf(Table::class, $table); 51 | }); 52 | } 53 | 54 | public function test_drop() 55 | { 56 | $this->schema->shouldReceive('dropTable') 57 | ->with('table_name')->once() 58 | ->andReturn('dropped'); 59 | 60 | $this->assertEquals('dropped', $this->builder->drop('table_name')); 61 | } 62 | 63 | public function test_dropIfExists() 64 | { 65 | $this->schema->shouldReceive('hasTable') 66 | ->with('table_name')->once() 67 | ->andReturn(true); 68 | 69 | $this->schema->shouldReceive('dropTable') 70 | ->with('table_name')->once() 71 | ->andReturn('dropped'); 72 | 73 | $this->assertEquals('dropped', $this->builder->dropIfExists('table_name')); 74 | } 75 | 76 | public function test_rename() 77 | { 78 | $this->schema->shouldReceive('renameTable') 79 | ->with('table_name', 'tablename')->once() 80 | ->andReturn('renamed'); 81 | 82 | $this->assertEquals('renamed', $this->builder->rename('table_name', 'tablename')); 83 | } 84 | 85 | public function test_hasTable() 86 | { 87 | $this->schema->shouldReceive('hasTable') 88 | ->with('table_name')->once() 89 | ->andReturn(true); 90 | 91 | $this->assertTrue($this->builder->hasTable('table_name')); 92 | } 93 | 94 | public function test_hasColumn() 95 | { 96 | $table = m::mock(\Doctrine\DBAL\Schema\Table::class); 97 | 98 | $this->schema->shouldReceive('getTable') 99 | ->with('table_name')->once() 100 | ->andReturn($table); 101 | 102 | $table->shouldReceive('hasColumn')->once()->with('column_name')->andReturn(true); 103 | 104 | $this->assertTrue($this->builder->hasColumn('table_name', 'column_name')); 105 | } 106 | 107 | public function test_getColumnListing() 108 | { 109 | $table = m::mock(\Doctrine\DBAL\Schema\Table::class); 110 | 111 | $this->schema->shouldReceive('getTable') 112 | ->with('table_name')->once() 113 | ->andReturn($table); 114 | 115 | $table->shouldReceive('getColumns')->once()->andReturn(['column']); 116 | 117 | $this->assertContains('column', $this->builder->getColumnListing('table_name')); 118 | } 119 | 120 | public function test_has_columns() 121 | { 122 | $table = m::mock(\Doctrine\DBAL\Schema\Table::class); 123 | 124 | $this->schema->shouldReceive('getTable') 125 | ->with('table_name')->once() 126 | ->andReturn($table); 127 | 128 | $table->shouldReceive('getColumns')->once()->andReturn(['column' => 'instance']); 129 | 130 | $this->assertTrue($this->builder->hasColumns('table_name', ['column'])); 131 | } 132 | 133 | public function test_doesnt_have_column() 134 | { 135 | $table = m::mock(\Doctrine\DBAL\Schema\Table::class); 136 | 137 | $this->schema->shouldReceive('getTable') 138 | ->with('table_name')->once() 139 | ->andReturn($table); 140 | 141 | $table->shouldReceive('getColumns')->once()->andReturn(['column' => 'instance']); 142 | 143 | $this->assertFalse($this->builder->hasColumns('table_name', ['column2'])); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tests/Schema/SchemaTableTest.php: -------------------------------------------------------------------------------- 1 | dbal = m::mock(\Doctrine\DBAL\Schema\Table::class); 28 | $this->table = new Table($this->dbal); 29 | } 30 | 31 | public function test_guid() 32 | { 33 | $this->dbal->shouldReceive('addColumn')->with('guid', 'guid')->once(); 34 | 35 | $this->table->guid('guid'); 36 | } 37 | 38 | public function test_primary() 39 | { 40 | $this->dbal->shouldReceive('setPrimaryKey')->with(['id'], null)->once(); 41 | 42 | $this->table->primary('id'); 43 | } 44 | 45 | public function test_unique() 46 | { 47 | $this->dbal->shouldReceive('addUniqueIndex')->with(['email'], null, [])->once(); 48 | 49 | $this->table->unique('email'); 50 | } 51 | 52 | public function test_index() 53 | { 54 | $this->dbal->shouldReceive('addIndex')->with(['email'], null, [], [])->once(); 55 | 56 | $this->table->index('email'); 57 | } 58 | 59 | public function test_foreign() 60 | { 61 | $this->dbal->shouldReceive('addForeignKeyConstraint')->with('users', ['user_id'], ['id'], [], null)->once(); 62 | 63 | $this->table->foreign('users', 'user_id', 'id'); 64 | } 65 | 66 | public function test_increments() 67 | { 68 | $this->dbal->shouldReceive('addColumn')->with( 69 | 'id', 70 | 'integer', 71 | [ 72 | 'autoIncrement' => true, 73 | 'unsigned' => true 74 | ] 75 | )->once(); 76 | $this->dbal->shouldReceive('setPrimaryKey')->with(['id'], null)->once(); 77 | 78 | $this->table->increments('id'); 79 | } 80 | 81 | public function test_small_increments() 82 | { 83 | $this->dbal->shouldReceive('addColumn')->with( 84 | 'id', 85 | 'smallint', 86 | [ 87 | 'autoIncrement' => true, 88 | 'unsigned' => true 89 | ] 90 | )->once(); 91 | $this->dbal->shouldReceive('setPrimaryKey')->with(['id'], null)->once(); 92 | 93 | $this->table->smallIncrements('id'); 94 | } 95 | 96 | public function test_big_increments() 97 | { 98 | $this->dbal->shouldReceive('addColumn')->with( 99 | 'id', 100 | 'bigint', 101 | [ 102 | 'autoIncrement' => true, 103 | 'unsigned' => true 104 | ] 105 | )->once(); 106 | $this->dbal->shouldReceive('setPrimaryKey')->with(['id'], null)->once(); 107 | 108 | $this->table->bigIncrements('id'); 109 | } 110 | 111 | public function test_string() 112 | { 113 | $this->dbal->shouldReceive('addColumn')->with('string', 'string', ['length' => 255])->once(); 114 | $this->table->string('string'); 115 | } 116 | 117 | public function test_text() 118 | { 119 | $this->dbal->shouldReceive('addColumn')->with('text', 'text')->once(); 120 | $this->table->text('text'); 121 | } 122 | 123 | public function test_integer() 124 | { 125 | $this->dbal->shouldReceive('addColumn')->with('column', 'integer', [ 126 | 'autoIncrement' => false, 127 | 'unsigned' => false 128 | ])->once(); 129 | $this->table->integer('column'); 130 | 131 | $this->dbal->shouldReceive('addColumn')->with('column', 'integer', [ 132 | 'autoIncrement' => true, 133 | 'unsigned' => true 134 | ])->once(); 135 | $this->table->integer('column', true, true); 136 | } 137 | 138 | public function test_small_integer() 139 | { 140 | $this->dbal->shouldReceive('addColumn')->with('column', 'smallint', [ 141 | 'autoIncrement' => false, 142 | 'unsigned' => false 143 | ])->once(); 144 | $this->table->smallInteger('column'); 145 | 146 | $this->dbal->shouldReceive('addColumn')->with('column', 'smallint', [ 147 | 'autoIncrement' => true, 148 | 'unsigned' => true 149 | ])->once(); 150 | $this->table->smallInteger('column', true, true); 151 | } 152 | 153 | public function test_big_integer() 154 | { 155 | $this->dbal->shouldReceive('addColumn')->with('column', 'bigint', [ 156 | 'autoIncrement' => false, 157 | 'unsigned' => false 158 | ])->once(); 159 | $this->table->bigInteger('column'); 160 | 161 | $this->dbal->shouldReceive('addColumn')->with('column', 'bigint', [ 162 | 'autoIncrement' => true, 163 | 'unsigned' => true 164 | ])->once(); 165 | $this->table->bigInteger('column', true, true); 166 | } 167 | 168 | public function test_unsigned_smallint() 169 | { 170 | $this->dbal->shouldReceive('addColumn')->with('column', 'smallint', [ 171 | 'autoIncrement' => false, 172 | 'unsigned' => true 173 | ])->once(); 174 | $this->table->unsignedSmallInteger('column'); 175 | } 176 | 177 | public function test_unsigned_integer() 178 | { 179 | $this->dbal->shouldReceive('addColumn')->with('column', 'integer', [ 180 | 'autoIncrement' => false, 181 | 'unsigned' => true 182 | ])->once(); 183 | $this->table->unsignedInteger('column'); 184 | } 185 | 186 | public function test_unsigned_bigint() 187 | { 188 | $this->dbal->shouldReceive('addColumn')->with('column', 'bigint', [ 189 | 'autoIncrement' => false, 190 | 'unsigned' => true 191 | ])->once(); 192 | $this->table->unsignedBigInteger('column'); 193 | } 194 | 195 | public function test_float() 196 | { 197 | $this->dbal->shouldReceive('addColumn')->with('column', 'float', [ 198 | 'precision' => 8, 199 | 'scale' => 2 200 | ])->once(); 201 | $this->table->float('column'); 202 | } 203 | 204 | public function test_decimal() 205 | { 206 | $this->dbal->shouldReceive('addColumn')->with('column', 'decimal', [ 207 | 'precision' => 8, 208 | 'scale' => 2 209 | ])->once(); 210 | $this->table->decimal('column'); 211 | } 212 | 213 | public function test_boolean() 214 | { 215 | $this->dbal->shouldReceive('addColumn')->with('column', 'boolean')->once(); 216 | $this->table->boolean('column'); 217 | } 218 | 219 | public function test_json() 220 | { 221 | $this->dbal->shouldReceive('addColumn')->with('column', 'json')->once(); 222 | $this->table->json('column'); 223 | } 224 | 225 | public function test_date() 226 | { 227 | $this->dbal->shouldReceive('addColumn')->with('column', 'date')->once(); 228 | $this->table->date('column'); 229 | } 230 | 231 | public function test_dateTime() 232 | { 233 | $this->dbal->shouldReceive('addColumn')->with('column', 'datetime')->once(); 234 | $this->table->dateTime('column'); 235 | } 236 | 237 | public function test_dateTimeTz() 238 | { 239 | $this->dbal->shouldReceive('addColumn')->with('column', 'datetimetz')->once(); 240 | $this->table->dateTimeTz('column'); 241 | } 242 | 243 | public function test_timestamp() 244 | { 245 | $this->dbal->shouldReceive('addColumn')->with('column', 'datetime')->once(); 246 | $this->table->timestamp('column'); 247 | } 248 | 249 | public function test_timestampTz() 250 | { 251 | $this->dbal->shouldReceive('addColumn')->with('column', 'datetimetz')->once(); 252 | $this->table->timestampTz('column'); 253 | } 254 | 255 | public function test_nullable_timestamps() 256 | { 257 | $column = m::mock(Column::class); 258 | $this->dbal->shouldReceive('addColumn')->with('created_at', 'datetime')->andReturn($column)->once(); 259 | $this->dbal->shouldReceive('addColumn')->with('updated_at', 'datetime')->andReturn($column)->once(); 260 | $column->shouldReceive('setNotnull')->with(false)->twice(); 261 | $this->table->nullableTimestamps(); 262 | } 263 | 264 | public function test_timestamps() 265 | { 266 | $this->dbal->shouldReceive('addColumn')->with('created_at', 'datetime')->once(); 267 | $this->dbal->shouldReceive('addColumn')->with('updated_at', 'datetime')->once(); 268 | $this->table->timestamps(); 269 | } 270 | 271 | public function test_timestampsTz() 272 | { 273 | $this->dbal->shouldReceive('addColumn')->with('created_at', 'datetimetz')->once(); 274 | $this->dbal->shouldReceive('addColumn')->with('updated_at', 'datetimetz')->once(); 275 | $this->table->timestampsTz(); 276 | } 277 | 278 | public function test_softdeletes() 279 | { 280 | $column = m::mock(Column::class); 281 | $this->dbal->shouldReceive('addColumn')->with('deleted_at', 'datetime')->andReturn($column)->once(); 282 | $column->shouldReceive('setNotnull')->with(false)->once(); 283 | $this->table->softDeletes(); 284 | } 285 | 286 | public function test_binary() 287 | { 288 | $column = m::mock(Column::class); 289 | $this->dbal->shouldReceive('addColumn')->with('column', 'binary', ['length' => 255])->andReturn($column)->once(); 290 | $column->shouldReceive('setNotnull')->with(false)->once(); 291 | $this->table->binary('column'); 292 | } 293 | 294 | public function test_remember_token() 295 | { 296 | $column = m::mock(Column::class); 297 | $this->dbal->shouldReceive('addColumn')->with('remember_token', 'string', ['length' => 100]) 298 | ->andReturn($column)->once(); 299 | $column->shouldReceive('setNotnull')->with(false)->once(); 300 | $this->table->rememberToken('column'); 301 | } 302 | 303 | public function test_get_dbal_table() 304 | { 305 | $this->assertInstanceOf(\Doctrine\DBAL\Schema\Table::class, $this->table->getTable()); 306 | } 307 | 308 | public function test_drop_column() 309 | { 310 | $this->dbal->shouldReceive('dropColumn')->with('column')->once(); 311 | 312 | $this->table->dropColumn('column'); 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |