├── .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 | [](https://packagist.org/packages/laravel-doctrine/migrations)
6 | [](https://github.com/laravel-doctrine/migrations/actions?query=workflow%3ACI+branch%3A2.0)
7 | [](https://github.com/laravel-doctrine/migrations)
8 | [](https://packagist.org/packages/laravel-doctrine/migrations)
9 | [](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 |