From d7c4a9a631a25ba1e349e1d445890d1030b5a6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Zl=C3=A1mal?= Date: Mon, 13 Mar 2017 14:57:21 +0100 Subject: [PATCH 01/27] Printers: fix duration time unit Time in printers is printed in milliseconds but it's actually measured in seconds. This change fixes this incompatibility. --- src/Printers/Console.php | 2 +- src/Printers/HtmlDump.php | 2 +- tests/cases/integration/Runner.FirstRun.phpt | 20 +++++++++---------- tests/cases/integration/Runner.SecondRun.phpt | 14 ++++++------- tests/inc/TestPrinter.php | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Printers/Console.php b/src/Printers/Console.php index 72d1698..1ecf23d 100644 --- a/src/Printers/Console.php +++ b/src/Printers/Console.php @@ -59,7 +59,7 @@ public function printExecute(File $file, $count, $time) $this->output( '- ' . $file->group->name . '/' . $file->name . '; ' . $this->color($count, self::COLOR_INFO) . ' queries; ' - . $this->color(sprintf('%0.3f', $time), self::COLOR_INFO) . ' ms' + . $this->color(sprintf('%0.3f', $time), self::COLOR_INFO) . ' s' ); } diff --git a/src/Printers/HtmlDump.php b/src/Printers/HtmlDump.php index 99b0cf2..0557578 100644 --- a/src/Printers/HtmlDump.php +++ b/src/Printers/HtmlDump.php @@ -55,7 +55,7 @@ public function printExecute(File $file, $count, $time) $format = '%0' . strlen($this->count) . 'd'; $name = htmlspecialchars($file->group->name . '/' . $file->name); $this->output(sprintf( - $format . '/' . $format . ': %s (%d %s, %0.3f ms)', + $format . '/' . $format . ': %s (%d %s, %0.3f s)', ++$this->index, $this->count, $name, $count, ($count === 1 ? 'query' : 'queries'), $time )); } diff --git a/tests/cases/integration/Runner.FirstRun.phpt b/tests/cases/integration/Runner.FirstRun.phpt index 76c1546..c683ec9 100644 --- a/tests/cases/integration/Runner.FirstRun.phpt +++ b/tests/cases/integration/Runner.FirstRun.phpt @@ -24,11 +24,11 @@ class FirstRunTest extends IntegrationTestCase 'Nextras Migrations', 'RESET', '5 migrations need to be executed.', - '- structures/001.sql; 1 queries; XX ms', - '- structures/002.sql; 1 queries; XX ms', - '- basic-data/003.sql; 2 queries; XX ms', - '- dummy-data/004.sql; 1 queries; XX ms', - '- structures/005.sql; 1 queries; XX ms', + '- structures/001.sql; 1 queries; XX s', + '- structures/002.sql; 1 queries; XX s', + '- basic-data/003.sql; 2 queries; XX s', + '- dummy-data/004.sql; 1 queries; XX s', + '- structures/005.sql; 1 queries; XX s', 'OK', ], $this->printer->lines); @@ -62,11 +62,11 @@ class FirstRunTest extends IntegrationTestCase 'Nextras Migrations', 'CONTINUE', '5 migrations need to be executed.', - '- structures/001.sql; 1 queries; XX ms', - '- structures/002.sql; 1 queries; XX ms', - '- basic-data/003.sql; 2 queries; XX ms', - '- dummy-data/004.sql; 1 queries; XX ms', - '- structures/005.sql; 1 queries; XX ms', + '- structures/001.sql; 1 queries; XX s', + '- structures/002.sql; 1 queries; XX s', + '- basic-data/003.sql; 2 queries; XX s', + '- dummy-data/004.sql; 1 queries; XX s', + '- structures/005.sql; 1 queries; XX s', 'OK', ], $this->printer->lines); diff --git a/tests/cases/integration/Runner.SecondRun.phpt b/tests/cases/integration/Runner.SecondRun.phpt index 029d1c0..bacb130 100644 --- a/tests/cases/integration/Runner.SecondRun.phpt +++ b/tests/cases/integration/Runner.SecondRun.phpt @@ -27,11 +27,11 @@ class SecondRunTest extends IntegrationTestCase 'Nextras Migrations', 'RESET', '5 migrations need to be executed.', - '- structures/001.sql; 1 queries; XX ms', - '- structures/002.sql; 1 queries; XX ms', - '- basic-data/003.sql; 2 queries; XX ms', - '- dummy-data/004.sql; 1 queries; XX ms', - '- structures/005.sql; 1 queries; XX ms', + '- structures/001.sql; 1 queries; XX s', + '- structures/002.sql; 1 queries; XX s', + '- basic-data/003.sql; 2 queries; XX s', + '- dummy-data/004.sql; 1 queries; XX s', + '- structures/005.sql; 1 queries; XX s', 'OK', ], $this->printer->lines); @@ -49,8 +49,8 @@ class SecondRunTest extends IntegrationTestCase 'Nextras Migrations', 'CONTINUE', '2 migrations need to be executed.', - '- dummy-data/004.sql; 1 queries; XX ms', - '- structures/005.sql; 1 queries; XX ms', + '- dummy-data/004.sql; 1 queries; XX s', + '- structures/005.sql; 1 queries; XX s', 'OK', ], $this->printer->lines); diff --git a/tests/inc/TestPrinter.php b/tests/inc/TestPrinter.php index 7142372..b9760f7 100644 --- a/tests/inc/TestPrinter.php +++ b/tests/inc/TestPrinter.php @@ -23,7 +23,7 @@ public function __construct() protected function output($s, $color = NULL) { - $this->lines[] = preg_replace('#; \d+\.\d+ ms#', '; XX ms', $s); + $this->lines[] = preg_replace('#; \d+\.\d+ s#', '; XX s', $s); $this->out .= "$s\n"; } } From d65a60a1d83dc0cf43d8e1a65c9766698540d6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikul=C3=A1=C5=A1=20D=C3=ADt=C4=9B?= Date: Thu, 27 Jul 2017 18:54:37 +0200 Subject: [PATCH 02/27] fix lock released after it cannot be acquired Enginer\Runner first tries to Driver::lock() which throws LockException. However, this exception was caught and Driver::unlock() was called. Issue: - process A starts migrations, which creates a lock table - process B also attempts to start migrations, but fails to acquire the lock and incorrectly removes the lock table - process C can now also acquire the lock, even though process A did not yet release it --- src/Engine/Runner.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Engine/Runner.php b/src/Engine/Runner.php index 7e65a69..b8f56a3 100644 --- a/src/Engine/Runner.php +++ b/src/Engine/Runner.php @@ -19,6 +19,7 @@ use Nextras\Migrations\IDriver; use Nextras\Migrations\IExtensionHandler; use Nextras\Migrations\IPrinter; +use Nextras\Migrations\LockException; use Nextras\Migrations\LogicException; @@ -130,6 +131,9 @@ public function run($mode = self::MODE_CONTINUE, IConfiguration $config = NULL) $this->driver->unlock(); $this->printer->printDone(); + } catch (LockException $e) { + $this->printer->printError($e); + } catch (Exception $e) { $this->driver->unlock(); $this->printer->printError($e); From 70d72f846c9e2fd5707c955cc2b15d855b947ac7 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Fri, 28 Jul 2017 19:20:57 +0200 Subject: [PATCH 03/27] PgSqlDriver: used advisory locks --- src/Drivers/PgSqlDriver.php | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/Drivers/PgSqlDriver.php b/src/Drivers/PgSqlDriver.php index 268f1f3..1016d80 100644 --- a/src/Drivers/PgSqlDriver.php +++ b/src/Drivers/PgSqlDriver.php @@ -32,9 +32,6 @@ class PgSqlDriver extends BaseDriver implements IDriver /** @var string */ protected $primarySequence; - /** @var string */ - protected $lockTableName; - /** * @param IDbal $dbal @@ -47,7 +44,6 @@ public function __construct(IDbal $dbal, $tableName = 'migrations', $schema = 'p $this->schema = $dbal->escapeIdentifier($schema); $this->schemaStr = $dbal->escapeString($schema); $this->primarySequence = $this->dbal->escapeString($tableName . '_id_seq'); - $this->lockTableName = $dbal->escapeIdentifier($tableName . '_lock'); } @@ -84,18 +80,8 @@ public function rollbackTransaction() public function lock() { try { - $schemaExist = (bool) $this->dbal->query(" - SELECT schema_name - FROM information_schema.schemata - WHERE schema_name = {$this->schemaStr} - "); - - if (!$schemaExist) { - // CREATE SCHEMA IF NOT EXIST is not available in PostgreSQL < 9.3 - $this->dbal->exec("CREATE SCHEMA {$this->schema}"); - } - - $this->dbal->exec("CREATE TABLE {$this->schema}.{$this->lockTableName} (\"foo\" INT)"); + $this->dbal->exec('SELECT pg_advisory_lock(-2099128779216184107)'); + } catch (\Exception $e) { throw new LockException('Unable to acquire a lock.', NULL, $e); } @@ -105,7 +91,8 @@ public function lock() public function unlock() { try { - $this->dbal->exec("DROP TABLE IF EXISTS {$this->schema}.{$this->lockTableName}"); + $this->dbal->exec('SELECT pg_advisory_unlock(-2099128779216184107)'); + } catch (\Exception $e) { throw new LockException('Unable to release a lock.', NULL, $e); } From 9253a5dc6eafced3d647108f799b02507f1a2c43 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Fri, 28 Jul 2017 20:03:14 +0200 Subject: [PATCH 04/27] BaseDriver: fixed delimiter pattern in loadFile() [closes #73] ^ does not work with offset --- src/Drivers/BaseDriver.php | 2 +- tests/cases/unit/BaseDriverTest.phpt | 86 ++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 tests/cases/unit/BaseDriverTest.phpt diff --git a/src/Drivers/BaseDriver.php b/src/Drivers/BaseDriver.php index 6abac88..c618484 100644 --- a/src/Drivers/BaseDriver.php +++ b/src/Drivers/BaseDriver.php @@ -65,7 +65,7 @@ public function loadFile($path) $space = "(?:\\s|/\\*.*\\*/|(?:#|-- )[^\\n]*\\n|--\\n)"; $spacesRe = "~^{$space}*\\z~"; $delimiter = ';'; - $delimiterRe = "~^{$space}*DELIMITER\\s+(\\S+)~i"; + $delimiterRe = "~\\G{$space}*DELIMITER\\s+(\\S+)~i"; $openRe = $this instanceof PgSqlDriver ? '[\'"]|/\*|-- |\z|\$[^$]*\$' : '[\'"`#]|/\*|-- |\z'; $parseRe = "(;|$openRe)"; diff --git a/tests/cases/unit/BaseDriverTest.phpt b/tests/cases/unit/BaseDriverTest.phpt new file mode 100644 index 0000000..79a2540 --- /dev/null +++ b/tests/cases/unit/BaseDriverTest.phpt @@ -0,0 +1,86 @@ +shouldReceive('escapeIdentifier')->with('migrations')->andReturn('migrations'); + + $driver = Mockery::mock('Nextras\Migrations\Drivers\BaseDriver', array($dbal)); + $driver->shouldDeferMissing(); + + foreach ($expectedQueries as $expectedQuery) { + $dbal->shouldReceive('exec')->once()->ordered()->with($expectedQuery); + } + + $driver->loadFile(Tester\FileMock::create($content)); + + Mockery::close(); + Assert::true(TRUE); + } + + + protected function provideLoadFileData() + { + return [ + [ + 'SELECT 1', [ + 'SELECT 1', + ], + ], + [ + 'SELECT 1; ', [ + 'SELECT 1', + ], + ], + [ + 'SELECT 1; SELECT 2; SELECT 3; ', [ + 'SELECT 1', + ' SELECT 2', + ' SELECT 3', + ], + ], + [ + 'SELECT 1; SELECT 2; SELECT 3; ', [ + 'SELECT 1', + ' SELECT 2', + ' SELECT 3', + ], + ], + [ + implode("\n", [ + 'SELECT 1;', + 'DELIMITER //', + 'CREATE TRIGGER `users_bu` BEFORE UPDATE ON `users` FOR EACH ROW BEGIN SELECT 1; END; //', + 'DELIMITER ;', + 'SELECT 2;', + ]), + [ + 'SELECT 1', + "\nCREATE TRIGGER `users_bu` BEFORE UPDATE ON `users` FOR EACH ROW BEGIN SELECT 1; END; ", + "\nSELECT 2", + ] + ] + ]; + } +} + +$test = new BaseDriverTest(); +$test->run(); From 6bf5bd390ef8dc8e8930d4cdd6b5cfbfa2d005b9 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Fri, 28 Jul 2017 20:07:11 +0200 Subject: [PATCH 05/27] MigrationsExtensions: add command only when symfony/console exist [closes #67] --- src/Bridges/NetteDI/MigrationsExtension.php | 28 +++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Bridges/NetteDI/MigrationsExtension.php b/src/Bridges/NetteDI/MigrationsExtension.php index cf5384b..6c4a056 100644 --- a/src/Bridges/NetteDI/MigrationsExtension.php +++ b/src/Bridges/NetteDI/MigrationsExtension.php @@ -64,19 +64,21 @@ public function loadConfiguration() ->setClass('Nextras\Migrations\Configurations\DefaultConfiguration') ->setArguments([$config['dir'], $driver, $config['withDummyData'], $config['phpParams']]); - $builder->addExcludedClasses(['Nextras\Migrations\Bridges\SymfonyConsole\BaseCommand']); - $builder->addDefinition($this->prefix('continueCommand')) - ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\ContinueCommand') - ->setArguments([$driver, $configuration]) - ->addTag('kdyby.console.command'); - $builder->addDefinition($this->prefix('createCommand')) - ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\CreateCommand') - ->setArguments([$driver, $configuration]) - ->addTag('kdyby.console.command'); - $builder->addDefinition($this->prefix('resetCommand')) - ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\ResetCommand') - ->setArguments([$driver, $configuration]) - ->addTag('kdyby.console.command'); + if (class_exists('Symfony\Component\Console\Command\Command')) { + $builder->addExcludedClasses(['Nextras\Migrations\Bridges\SymfonyConsole\BaseCommand']); + $builder->addDefinition($this->prefix('continueCommand')) + ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\ContinueCommand') + ->setArguments([$driver, $configuration]) + ->addTag('kdyby.console.command'); + $builder->addDefinition($this->prefix('createCommand')) + ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\CreateCommand') + ->setArguments([$driver, $configuration]) + ->addTag('kdyby.console.command'); + $builder->addDefinition($this->prefix('resetCommand')) + ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\ResetCommand') + ->setArguments([$driver, $configuration]) + ->addTag('kdyby.console.command'); + } if ($config['diffGenerator'] !== FALSE) { $builder->addDefinition($this->prefix('structureDiffGenerator')) From 8849c0557dbcf9b83aff974c947693f78be3044d Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Fri, 28 Jul 2017 20:08:23 +0200 Subject: [PATCH 06/27] fixed escapeInt not returning string in some dbal adapters --- src/Bridges/Dibi/Dibi2Adapter.php | 2 +- src/Bridges/Dibi/Dibi3Adapter.php | 2 +- src/Bridges/NextrasDbal/NextrasAdapter.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Bridges/Dibi/Dibi2Adapter.php b/src/Bridges/Dibi/Dibi2Adapter.php index 5d2e2ea..ffccb06 100644 --- a/src/Bridges/Dibi/Dibi2Adapter.php +++ b/src/Bridges/Dibi/Dibi2Adapter.php @@ -49,7 +49,7 @@ public function escapeString($value) public function escapeInt($value) { - return (int) $value; + return (string) (int) $value; } diff --git a/src/Bridges/Dibi/Dibi3Adapter.php b/src/Bridges/Dibi/Dibi3Adapter.php index 1b043d4..350a081 100644 --- a/src/Bridges/Dibi/Dibi3Adapter.php +++ b/src/Bridges/Dibi/Dibi3Adapter.php @@ -48,7 +48,7 @@ public function escapeString($value) public function escapeInt($value) { - return (int) $value; + return (string) (int) $value; } diff --git a/src/Bridges/NextrasDbal/NextrasAdapter.php b/src/Bridges/NextrasDbal/NextrasAdapter.php index 9838d71..e6a127e 100644 --- a/src/Bridges/NextrasDbal/NextrasAdapter.php +++ b/src/Bridges/NextrasDbal/NextrasAdapter.php @@ -61,7 +61,7 @@ public function escapeString($value) public function escapeInt($value) { - return (int) $value; + return (string) (int) $value; } From b780e8dc6afa3f3a1ec81321c2c4c0a66dff1d8e Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Tue, 1 Aug 2017 10:29:18 +0200 Subject: [PATCH 07/27] BaseDriver: minor speedup of loadFile() --- src/Drivers/BaseDriver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Drivers/BaseDriver.php b/src/Drivers/BaseDriver.php index c618484..bd5e042 100644 --- a/src/Drivers/BaseDriver.php +++ b/src/Drivers/BaseDriver.php @@ -63,7 +63,7 @@ public function loadFile($path) $queries = 0; $space = "(?:\\s|/\\*.*\\*/|(?:#|-- )[^\\n]*\\n|--\\n)"; - $spacesRe = "~^{$space}*\\z~"; + $spacesRe = "~\\G{$space}*\\z~"; $delimiter = ';'; $delimiterRe = "~\\G{$space}*DELIMITER\\s+(\\S+)~i"; @@ -104,7 +104,7 @@ public function loadFile($path) } } else { // last query or EOF - if (preg_match($spacesRe, substr($content, $queryOffset))) { + if (preg_match($spacesRe, $content, $_, 0, $queryOffset)) { break 2; } else { From ba924c4ef84ec65d8f8ea23d5a047110b8b80e17 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Tue, 1 Aug 2017 10:29:44 +0200 Subject: [PATCH 08/27] CreateCommand: fixed error when target directory does not exist --- src/Bridges/SymfonyConsole/CreateCommand.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Bridges/SymfonyConsole/CreateCommand.php b/src/Bridges/SymfonyConsole/CreateCommand.php index 85e5069..ab41a20 100644 --- a/src/Bridges/SymfonyConsole/CreateCommand.php +++ b/src/Bridges/SymfonyConsole/CreateCommand.php @@ -135,10 +135,13 @@ protected function getFileName($label, $extension) protected function hasNumericSubdirectory($dir, & $found) { $items = @scandir($dir); // directory may not exist - foreach ($items as $item) { - if ($item !== '.' && $item !== '..' && is_dir($dir . '/' . $item)) { - $found = $dir . '/' . $item; - return TRUE; + + if ($items) { + foreach ($items as $item) { + if ($item !== '.' && $item !== '..' && is_dir($dir . '/' . $item)) { + $found = $dir . '/' . $item; + return TRUE; + } } } From b34ab7b58010992f5587e58364ec4b86be719118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20=C5=BDurek?= Date: Wed, 30 Aug 2017 09:41:30 +0200 Subject: [PATCH 09/27] doc: added sort notice (#76) doc: added information about migrations ordering --- doc/default.texy | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/default.texy b/doc/default.texy index cb3892c..16c863f 100644 --- a/doc/default.texy +++ b/doc/default.texy @@ -91,6 +91,7 @@ Open the script in your browser (`HttpController`) or in a terminal (`ConsoleCon Organizing Migrations ===================== +Migrations are executed in alphabetical order (by filename), e.g. `structures/2015-03-17-aaa.sql` is executed before `dummy-data/2015-03-17-zzz.sql`. The following structure is recommended and used by Symfony Console Commands by default: /-- From 568dcdcc8e5e61844b21dd490f806494784238a1 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Mon, 2 Oct 2017 15:32:38 +0200 Subject: [PATCH 10/27] added experimental NextrasMigrationsBundle for Symfony --- composer.json | 3 + .../DependencyInjection/Configuration.php | 48 ++++++++++ .../NextrasMigrationsExtension.php | 88 +++++++++++++++++++ .../SymfonyBundle/NextrasMigrationsBundle.php | 18 ++++ 4 files changed, 157 insertions(+) create mode 100644 src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php create mode 100644 src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php create mode 100644 src/Bridges/SymfonyBundle/NextrasMigrationsBundle.php diff --git a/composer.json b/composer.json index 0d2c59a..c05dd11 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,9 @@ "nette/utils": "~2.3", "nextras/dbal": "~1.0 | ~2.0", "mockery/mockery": "~0.9", + "symfony/http-kernel": "~2.6 | ~3.0", + "symfony/config": "~2.6 | ~3.0", + "symfony/dependency-injection": "~2.6 | ~3.0", "symfony/console": "~2.6 | ~3.0", "ext-openssl": "*" }, diff --git a/src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php b/src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000..dda280d --- /dev/null +++ b/src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php @@ -0,0 +1,48 @@ +root('nextras_migrations')->cannotBeEmpty()->children() + ->scalarNode('dir') + ->defaultValue('%kernel.root_dir%/NextrasMigrations') + ->cannotBeEmpty() + ->end() + ->enumNode('dbal') + ->values(['dibi', 'dibi2', 'dibi3', 'doctrine', 'nette', 'nextras']) + ->defaultValue('doctrine') + ->cannotBeEmpty() + ->end() + ->enumNode('driver') + ->values(['mysql', 'pgsql']) + ->cannotBeEmpty() + ->end() + ->scalarNode('diff_generator') + ->defaultValue('doctrine') + ->end() + ->booleanNode('with_dummy_data') + ->defaultFalse() + ->end() + ->scalarNode('ignored_queries_file') + ->defaultNull() + ->end() + ->end(); + + return $treeBuilder; + } +} diff --git a/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php b/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php new file mode 100644 index 0000000..4439171 --- /dev/null +++ b/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php @@ -0,0 +1,88 @@ + 'Nextras\Migrations\Bridges\Dibi\DibiAdapter', + 'dibi2' => 'Nextras\Migrations\Bridges\Dibi\Dibi2Adapter', + 'dibi3' => 'Nextras\Migrations\Bridges\Dibi\Dibi3Adapter', + 'doctrine' => 'Nextras\Migrations\Bridges\DoctrineDbal\DoctrineAdapter', + 'nette' => 'Nextras\Migrations\Bridges\NetteDatabase\NetteAdapter', + 'nextras' => 'Nextras\Migrations\Bridges\NextrasDbal\NextrasAdapter', + ]; + + /** @var array */ + protected $drivers = [ + 'mysql' => 'Nextras\Migrations\Drivers\MySqlDriver', + 'pgsql' => 'Nextras\Migrations\Drivers\PgSqlDriver', + ]; + + + public function load(array $configs, ContainerBuilder $container) + { + $config = $this->processConfiguration(new Configuration(), $configs); + + $dbalAlias = $config['dbal']; + $dbalDefinition = new Definition($this->dbals[$dbalAlias]); + $dbalDefinition->setAutowired(TRUE); + + $driverAlias = $config['driver']; + $driverDefinition = new Definition($this->drivers[$driverAlias]); + $driverDefinition->setAutowired(TRUE); + + if ($config['diff_generator'] === 'doctrine') { + $structureDiffGeneratorDefinition = new Definition('Nextras\Migrations\Bridges\DoctrineOrm\StructureDiffGenerator'); + $structureDiffGeneratorDefinition->setAutowired(TRUE); + $structureDiffGeneratorDefinition->setArgument('$ignoredQueriesFile', $config['ignored_queries_file']); + + } else { + $structureDiffGeneratorDefinition = NULL; + } + + $configurationDefinition = new Definition('Nextras\Migrations\Configurations\DefaultConfiguration'); + $configurationDefinition->setArguments([$config['dir'], $driverDefinition, $config['with_dummy_data']]); + $configurationDefinition->addMethodCall('setStructureDiffGenerator', [$structureDiffGeneratorDefinition]); + + $continueCommandDefinition = new Definition('Nextras\Migrations\Bridges\SymfonyConsole\ContinueCommand'); + $continueCommandDefinition->setAutowired(TRUE); + $continueCommandDefinition->addTag('console.command'); + + $createCommandDefinition = new Definition('Nextras\Migrations\Bridges\SymfonyConsole\CreateCommand'); + $createCommandDefinition->setAutowired(TRUE); + $createCommandDefinition->addTag('console.command'); + + $resetCommandDefinition = new Definition('Nextras\Migrations\Bridges\SymfonyConsole\ResetCommand'); + $resetCommandDefinition->setAutowired(TRUE); + $resetCommandDefinition->addTag('console.command'); + + $container->addDefinitions([ + 'nextras_migrations.dbal' => $dbalDefinition, + 'nextras_migrations.driver' => $driverDefinition, + 'nextras_migrations.configuration' => $configurationDefinition, + 'nextras_migrations.continue_command' => $continueCommandDefinition, + 'nextras_migrations.create_command' => $createCommandDefinition, + 'nextras_migrations.reset_command' => $resetCommandDefinition, + ]); + + if ($structureDiffGeneratorDefinition) { + $container->addDefinitions([ + 'nextras_migrations.structure_diff_generator' => $structureDiffGeneratorDefinition, + ]); + } + } +} diff --git a/src/Bridges/SymfonyBundle/NextrasMigrationsBundle.php b/src/Bridges/SymfonyBundle/NextrasMigrationsBundle.php new file mode 100644 index 0000000..6f3c61d --- /dev/null +++ b/src/Bridges/SymfonyBundle/NextrasMigrationsBundle.php @@ -0,0 +1,18 @@ + Date: Mon, 2 Oct 2017 16:44:09 +0200 Subject: [PATCH 11/27] PgSqlDriver: fetch id of inserted migration with RETURNING clause --- src/Drivers/PgSqlDriver.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Drivers/PgSqlDriver.php b/src/Drivers/PgSqlDriver.php index 1016d80..2c2e347 100644 --- a/src/Drivers/PgSqlDriver.php +++ b/src/Drivers/PgSqlDriver.php @@ -29,9 +29,6 @@ class PgSqlDriver extends BaseDriver implements IDriver /** @var string */ protected $schemaStr; - /** @var string */ - protected $primarySequence; - /** * @param IDbal $dbal @@ -43,7 +40,6 @@ public function __construct(IDbal $dbal, $tableName = 'migrations', $schema = 'p parent::__construct($dbal, $tableName); $this->schema = $dbal->escapeIdentifier($schema); $this->schemaStr = $dbal->escapeString($schema); - $this->primarySequence = $this->dbal->escapeString($tableName . '_id_seq'); } @@ -113,7 +109,7 @@ public function dropTable() public function insertMigration(Migration $migration) { - $this->dbal->exec(" + $rows = $this->dbal->query(" INSERT INTO {$this->schema}.{$this->tableName}" . ' ("group", "file", "checksum", "executed", "ready") VALUES (' . $this->dbal->escapeString($migration->group) . "," . @@ -122,9 +118,10 @@ public function insertMigration(Migration $migration) $this->dbal->escapeDateTime($migration->executedAt) . "," . $this->dbal->escapeBool(FALSE) . ") + RETURNING id "); - $migration->id = (int) $this->dbal->query('SELECT CURRVAL('. $this->primarySequence . ') AS id')[0]['id']; + $migration->id = (int) $rows[0]['id']; } From 9ad69f1f207f49a32a7e91de578de929bf7c8140 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Mon, 2 Oct 2017 16:46:56 +0200 Subject: [PATCH 12/27] {My,Pg}SqlDriver: do not connect to database in constructor [closes #72] --- src/Drivers/BaseDriver.php | 11 ++++++++++- src/Drivers/MySqlDriver.php | 13 +++++++------ src/Drivers/PgSqlDriver.php | 25 +++++++++++++------------ tests/inc/IntegrationTestCase.php | 2 ++ 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/Drivers/BaseDriver.php b/src/Drivers/BaseDriver.php index bd5e042..ff48771 100644 --- a/src/Drivers/BaseDriver.php +++ b/src/Drivers/BaseDriver.php @@ -27,6 +27,9 @@ abstract class BaseDriver implements IDriver /** @var string */ protected $tableName; + /** @var NULL|string */ + protected $tableNameQuoted; + /** * @param IDbal $dbal @@ -35,7 +38,13 @@ abstract class BaseDriver implements IDriver public function __construct(IDbal $dbal, $tableName = 'migrations') { $this->dbal = $dbal; - $this->tableName = $dbal->escapeIdentifier($tableName); + $this->tableName = $tableName; + } + + + public function setupConnection() + { + $this->tableNameQuoted = $this->dbal->escapeIdentifier($this->tableName); } diff --git a/src/Drivers/MySqlDriver.php b/src/Drivers/MySqlDriver.php index 7c961e7..d786e23 100644 --- a/src/Drivers/MySqlDriver.php +++ b/src/Drivers/MySqlDriver.php @@ -24,6 +24,7 @@ class MySqlDriver extends BaseDriver implements IDriver { public function setupConnection() { + parent::setupConnection(); $this->dbal->exec('SET NAMES "utf8"'); $this->dbal->exec('SET foreign_key_checks = 0'); $this->dbal->exec('SET time_zone = "SYSTEM"'); @@ -91,14 +92,14 @@ public function createTable() public function dropTable() { - $this->dbal->exec("DROP TABLE {$this->tableName}"); + $this->dbal->exec("DROP TABLE {$this->tableNameQuoted}"); } public function insertMigration(Migration $migration) { $this->dbal->exec(" - INSERT INTO {$this->tableName} + INSERT INTO {$this->tableNameQuoted} (`group`, `file`, `checksum`, `executed`, `ready`) VALUES (" . $this->dbal->escapeString($migration->group) . "," . $this->dbal->escapeString($migration->filename) . "," . @@ -115,7 +116,7 @@ public function insertMigration(Migration $migration) public function markMigrationAsReady(Migration $migration) { $this->dbal->exec(" - UPDATE {$this->tableName} + UPDATE {$this->tableNameQuoted} SET `ready` = 1 WHERE `id` = " . $this->dbal->escapeInt($migration->id) ); @@ -125,7 +126,7 @@ public function markMigrationAsReady(Migration $migration) public function getAllMigrations() { $migrations = array(); - $result = $this->dbal->query("SELECT * FROM {$this->tableName} ORDER BY `executed`"); + $result = $this->dbal->query("SELECT * FROM {$this->tableNameQuoted} ORDER BY `executed`"); foreach ($result as $row) { $migration = new Migration; $migration->id = (int) $row['id']; @@ -145,7 +146,7 @@ public function getAllMigrations() public function getInitTableSource() { return preg_replace('#^\t{3}#m', '', trim(" - CREATE TABLE IF NOT EXISTS {$this->tableName} ( + CREATE TABLE IF NOT EXISTS {$this->tableNameQuoted} ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `group` varchar(100) NOT NULL, `file` varchar(100) NOT NULL, @@ -163,7 +164,7 @@ public function getInitMigrationsSource(array $files) { $out = ''; foreach ($files as $file) { - $out .= "INSERT INTO {$this->tableName} "; + $out .= "INSERT INTO {$this->tableNameQuoted} "; $out .= "(`group`, `file`, `checksum`, `executed`, `ready`) VALUES (" . $this->dbal->escapeString($file->group->name) . ", " . $this->dbal->escapeString($file->name) . ", " . diff --git a/src/Drivers/PgSqlDriver.php b/src/Drivers/PgSqlDriver.php index 2c2e347..63ea8a9 100644 --- a/src/Drivers/PgSqlDriver.php +++ b/src/Drivers/PgSqlDriver.php @@ -26,8 +26,8 @@ class PgSqlDriver extends BaseDriver implements IDriver /** @var string */ protected $schema; - /** @var string */ - protected $schemaStr; + /** @var NULL|string */ + protected $schemaQuoted; /** @@ -38,20 +38,21 @@ class PgSqlDriver extends BaseDriver implements IDriver public function __construct(IDbal $dbal, $tableName = 'migrations', $schema = 'public') { parent::__construct($dbal, $tableName); - $this->schema = $dbal->escapeIdentifier($schema); - $this->schemaStr = $dbal->escapeString($schema); + $this->schema = $schema; } public function setupConnection() { + parent::setupConnection(); + $this->schemaQuoted = $this->dbal->escapeIdentifier($this->schema); } public function emptyDatabase() { - $this->dbal->exec("DROP SCHEMA IF EXISTS {$this->schema} CASCADE"); - $this->dbal->exec("CREATE SCHEMA {$this->schema}"); + $this->dbal->exec("DROP SCHEMA IF EXISTS {$this->schemaQuoted} CASCADE"); + $this->dbal->exec("CREATE SCHEMA {$this->schemaQuoted}"); } @@ -103,14 +104,14 @@ public function createTable() public function dropTable() { - $this->dbal->exec("DROP TABLE {$this->schema}.{$this->tableName}"); + $this->dbal->exec("DROP TABLE {$this->schemaQuoted}.{$this->tableNameQuoted}"); } public function insertMigration(Migration $migration) { $rows = $this->dbal->query(" - INSERT INTO {$this->schema}.{$this->tableName}" . ' + INSERT INTO {$this->schemaQuoted}.{$this->tableNameQuoted}" . ' ("group", "file", "checksum", "executed", "ready") VALUES (' . $this->dbal->escapeString($migration->group) . "," . $this->dbal->escapeString($migration->filename) . "," . @@ -128,7 +129,7 @@ public function insertMigration(Migration $migration) public function markMigrationAsReady(Migration $migration) { $this->dbal->exec(" - UPDATE {$this->schema}.{$this->tableName}" . ' + UPDATE {$this->schemaQuoted}.{$this->tableNameQuoted}" . ' SET "ready" = TRUE WHERE "id" = ' . $this->dbal->escapeInt($migration->id) ); @@ -138,7 +139,7 @@ public function markMigrationAsReady(Migration $migration) public function getAllMigrations() { $migrations = array(); - $result = $this->dbal->query("SELECT * FROM {$this->schema}.{$this->tableName} ORDER BY \"executed\""); + $result = $this->dbal->query("SELECT * FROM {$this->schemaQuoted}.{$this->tableNameQuoted} ORDER BY \"executed\""); foreach ($result as $row) { $migration = new Migration; $migration->id = (int) $row['id']; @@ -158,7 +159,7 @@ public function getAllMigrations() public function getInitTableSource() { return preg_replace('#^\t{3}#m', '', trim(" - CREATE TABLE IF NOT EXISTS {$this->schema}.{$this->tableName} (" . ' + CREATE TABLE IF NOT EXISTS {$this->schemaQuoted}.{$this->tableNameQuoted} (" . ' "id" serial4 NOT NULL, "group" varchar(100) NOT NULL, "file" varchar(100) NOT NULL, @@ -176,7 +177,7 @@ public function getInitMigrationsSource(array $files) { $out = ''; foreach ($files as $file) { - $out .= "INSERT INTO {$this->schema}.{$this->tableName} "; + $out .= "INSERT INTO {$this->schemaQuoted}.{$this->tableNameQuoted} "; $out .= '("group", "file", "checksum", "executed", "ready") VALUES (' . $this->dbal->escapeString($file->group->name) . ", " . $this->dbal->escapeString($file->name) . ", " . diff --git a/tests/inc/IntegrationTestCase.php b/tests/inc/IntegrationTestCase.php index 6fca3a9..b78d207 100644 --- a/tests/inc/IntegrationTestCase.php +++ b/tests/inc/IntegrationTestCase.php @@ -58,6 +58,8 @@ protected function setUp() $initDb(); $this->driver = $this->createDriver($options['driver'], $this->dbal); + $this->driver->setupConnection(); + $this->printer = $this->createPrinter(); $this->runner = new Runner($this->driver, $this->printer); From 1965a4c46887357f474dc01799391e5fc323059d Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Thu, 26 Oct 2017 16:35:02 +0200 Subject: [PATCH 13/27] SymfonyBundle: add support for referencing services from php_params --- .../DependencyInjection/Configuration.php | 4 ++++ .../NextrasMigrationsExtension.php | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php b/src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php index dda280d..f8e286e 100644 --- a/src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php +++ b/src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php @@ -38,6 +38,10 @@ public function getConfigTreeBuilder() ->booleanNode('with_dummy_data') ->defaultFalse() ->end() + ->arrayNode('php_params') + ->variablePrototype() + ->end() + ->end() ->scalarNode('ignored_queries_file') ->defaultNull() ->end() diff --git a/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php b/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php index 4439171..71a578b 100644 --- a/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php +++ b/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php @@ -45,6 +45,11 @@ public function load(array $configs, ContainerBuilder $container) $driverDefinition = new Definition($this->drivers[$driverAlias]); $driverDefinition->setAutowired(TRUE); + $container->addDefinitions([ + 'nextras_migrations.dbal' => $dbalDefinition, + 'nextras_migrations.driver' => $driverDefinition, + ]); + if ($config['diff_generator'] === 'doctrine') { $structureDiffGeneratorDefinition = new Definition('Nextras\Migrations\Bridges\DoctrineOrm\StructureDiffGenerator'); $structureDiffGeneratorDefinition->setAutowired(TRUE); @@ -54,8 +59,15 @@ public function load(array $configs, ContainerBuilder $container) $structureDiffGeneratorDefinition = NULL; } + foreach ($config['php_params'] as $phpParamKey => $phpParamValue) { + if (is_string($phpParamValue) && strlen($phpParamValue) > 1 && $phpParamValue[0] === '@') { + $serviceName = substr($phpParamValue, 1); + $config['php_params'][$phpParamKey] = $container->getDefinition($serviceName); + } + } + $configurationDefinition = new Definition('Nextras\Migrations\Configurations\DefaultConfiguration'); - $configurationDefinition->setArguments([$config['dir'], $driverDefinition, $config['with_dummy_data']]); + $configurationDefinition->setArguments([$config['dir'], $driverDefinition, $config['with_dummy_data'], $config['php_params']]); $configurationDefinition->addMethodCall('setStructureDiffGenerator', [$structureDiffGeneratorDefinition]); $continueCommandDefinition = new Definition('Nextras\Migrations\Bridges\SymfonyConsole\ContinueCommand'); @@ -71,8 +83,6 @@ public function load(array $configs, ContainerBuilder $container) $resetCommandDefinition->addTag('console.command'); $container->addDefinitions([ - 'nextras_migrations.dbal' => $dbalDefinition, - 'nextras_migrations.driver' => $driverDefinition, 'nextras_migrations.configuration' => $configurationDefinition, 'nextras_migrations.continue_command' => $continueCommandDefinition, 'nextras_migrations.create_command' => $createCommandDefinition, From 40073be1cd92e191a2f19362db5a47f6b426b87b Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Thu, 26 Oct 2017 16:35:18 +0200 Subject: [PATCH 14/27] composer.json: sort symfony dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index c05dd11..984f790 100644 --- a/composer.json +++ b/composer.json @@ -18,10 +18,10 @@ "nette/utils": "~2.3", "nextras/dbal": "~1.0 | ~2.0", "mockery/mockery": "~0.9", - "symfony/http-kernel": "~2.6 | ~3.0", "symfony/config": "~2.6 | ~3.0", - "symfony/dependency-injection": "~2.6 | ~3.0", "symfony/console": "~2.6 | ~3.0", + "symfony/dependency-injection": "~2.6 | ~3.0", + "symfony/http-kernel": "~2.6 | ~3.0", "ext-openssl": "*" }, "suggest": { From 77fc537fd203db84930085b055c2f57cfb1b1e04 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Sat, 25 Nov 2017 15:39:53 +0100 Subject: [PATCH 15/27] MigrationsExtension: add default value for migrations directory --- src/Bridges/NetteDI/MigrationsExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/NetteDI/MigrationsExtension.php b/src/Bridges/NetteDI/MigrationsExtension.php index 6c4a056..3a75a0d 100644 --- a/src/Bridges/NetteDI/MigrationsExtension.php +++ b/src/Bridges/NetteDI/MigrationsExtension.php @@ -18,7 +18,7 @@ class MigrationsExtension extends Nette\DI\CompilerExtension { /** @var array */ public $defaults = [ - 'dir' => NULL, + 'dir' => '%appDir%/../migrations', 'phpParams' => [], 'driver' => NULL, 'dbal' => NULL, From c32fa2b9f1c6d24bba7f958f962b8aacc67b41aa Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Sat, 25 Nov 2017 15:41:43 +0100 Subject: [PATCH 16/27] Finder: ignore missing directories [closes #46] --- src/Engine/Finder.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Engine/Finder.php b/src/Engine/Finder.php index 5944355..607da94 100644 --- a/src/Engine/Finder.php +++ b/src/Engine/Finder.php @@ -144,15 +144,10 @@ protected function getFilesRecursive($dir) /** * @param string $dir * @return array - * @throws IOException */ protected function getItems($dir) { - $items = @scandir($dir); // directory may not exist - if ($items === FALSE) { - throw new IOException(sprintf('Finder: Directory "%s" does not exist.', $dir)); - } - return $items; + return @scandir($dir) ?: []; // directory may not exist } } From 41a5f19c07419d4bef3628b0ccd63f6a25a577d4 Mon Sep 17 00:00:00 2001 From: Ondra Votava Date: Wed, 3 Jan 2018 09:16:43 +0100 Subject: [PATCH 17/27] SymfonyBundle: fix using deprecated cannotBeEmpty() --- src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php b/src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php index f8e286e..bdf88f1 100644 --- a/src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php +++ b/src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php @@ -18,7 +18,7 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); - $treeBuilder->root('nextras_migrations')->cannotBeEmpty()->children() + $treeBuilder->root('nextras_migrations')->requiresAtLeastOneElement()->children() ->scalarNode('dir') ->defaultValue('%kernel.root_dir%/NextrasMigrations') ->cannotBeEmpty() From 31f6ecb4372758b7cc85e9cf8699d5dc93fcdcf9 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Mon, 22 Jan 2018 09:32:22 +0100 Subject: [PATCH 18/27] Revert "MigrationsExtension: add default value for migrations directory" This reverts commit 1ddaa960f4e14588467592f0760f1cda62637bb8. --- src/Bridges/NetteDI/MigrationsExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/NetteDI/MigrationsExtension.php b/src/Bridges/NetteDI/MigrationsExtension.php index 3a75a0d..6c4a056 100644 --- a/src/Bridges/NetteDI/MigrationsExtension.php +++ b/src/Bridges/NetteDI/MigrationsExtension.php @@ -18,7 +18,7 @@ class MigrationsExtension extends Nette\DI\CompilerExtension { /** @var array */ public $defaults = [ - 'dir' => '%appDir%/../migrations', + 'dir' => NULL, 'phpParams' => [], 'driver' => NULL, 'dbal' => NULL, From a6fa7fa7d3fa6326ca82a7a5b50b7aa51fad10a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Barto=C5=A1?= Date: Sat, 6 Jan 2018 12:35:28 +0100 Subject: [PATCH 19/27] CreateCommand: improve exception message --- src/Bridges/SymfonyConsole/CreateCommand.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Bridges/SymfonyConsole/CreateCommand.php b/src/Bridges/SymfonyConsole/CreateCommand.php index ab41a20..b3da210 100644 --- a/src/Bridges/SymfonyConsole/CreateCommand.php +++ b/src/Bridges/SymfonyConsole/CreateCommand.php @@ -112,7 +112,8 @@ protected function getGroup($type) } } - throw new Nextras\Migrations\LogicException("Unknown type '$type' given, expected on of 's', 'b' or 'd'."); + $types = $this->getTypeArgDescription(); + throw new Nextras\Migrations\LogicException("Unknown type '$type' given, expected one of $types."); } From fb42cefba2ec42684c163e03db2f254f1297ec8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Tvrd=C3=ADk?= <_github.com@jantvrdik.com> Date: Mon, 22 Jan 2018 15:47:02 +0100 Subject: [PATCH 20/27] MigrationsExtension: refactoring to support group configuration (#81) --- src/Bridges/NetteDI/MigrationsExtension.php | 257 +++++++++++++----- src/Configurations/Configuration.php | 57 ++++ src/Configurations/DefaultConfiguration.php | 1 + .../integration/MigrationsExtension.phpt | 2 +- 4 files changed, 247 insertions(+), 70 deletions(-) create mode 100644 src/Configurations/Configuration.php diff --git a/src/Bridges/NetteDI/MigrationsExtension.php b/src/Bridges/NetteDI/MigrationsExtension.php index 6c4a056..7b961ad 100644 --- a/src/Bridges/NetteDI/MigrationsExtension.php +++ b/src/Bridges/NetteDI/MigrationsExtension.php @@ -16,15 +16,18 @@ class MigrationsExtension extends Nette\DI\CompilerExtension { + const TAG_GROUP = 'nextras.migrations.group'; + const TAG_EXTENSION_HANDLER = 'nextras.migrations.extensionHandler'; + /** @var array */ public $defaults = [ 'dir' => NULL, 'phpParams' => [], 'driver' => NULL, 'dbal' => NULL, + 'groups' => NULL, // null|array 'diffGenerator' => TRUE, // false|doctrine 'withDummyData' => FALSE, - 'contentSource' => NULL, // CreateCommand::CONTENT_SOURCE_* 'ignoredQueriesFile' => NULL, ]; @@ -44,50 +47,44 @@ class MigrationsExtension extends Nette\DI\CompilerExtension 'pgsql' => 'Nextras\Migrations\Drivers\PgSqlDriver', ]; - /** - * Processes configuration data. Intended to be overridden by descendant. - * @return void - */ public function loadConfiguration() { - $builder = $this->getContainerBuilder(); $config = $this->validateConfig($this->defaults); - Validators::assertField($config, 'dir', 'string|Nette\PhpGenerator\PhpLiteral'); - Validators::assertField($config, 'phpParams', 'array'); - Validators::assertField($config, 'contentSource', 'string|null'); - Validators::assertField($config, 'ignoredQueriesFile', 'string|null'); - $dbal = $this->getDbal($config['dbal']); - $driver = $this->getDriver($config['driver'], $dbal); + // dbal + Validators::assertField($config, 'dbal', 'null|string|Nette\DI\Statement'); + $dbal = $this->getDbalDefinition($config['dbal']); - $configuration = $builder->addDefinition($this->prefix('configuration')) - ->setClass('Nextras\Migrations\Configurations\DefaultConfiguration') - ->setArguments([$config['dir'], $driver, $config['withDummyData'], $config['phpParams']]); + // driver + Validators::assertField($config, 'driver', 'null|string|Nette\DI\Statement'); + $driver = $this->getDriverDefinition($config['driver'], $dbal); - if (class_exists('Symfony\Component\Console\Command\Command')) { - $builder->addExcludedClasses(['Nextras\Migrations\Bridges\SymfonyConsole\BaseCommand']); - $builder->addDefinition($this->prefix('continueCommand')) - ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\ContinueCommand') - ->setArguments([$driver, $configuration]) - ->addTag('kdyby.console.command'); - $builder->addDefinition($this->prefix('createCommand')) - ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\CreateCommand') - ->setArguments([$driver, $configuration]) - ->addTag('kdyby.console.command'); - $builder->addDefinition($this->prefix('resetCommand')) - ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\ResetCommand') - ->setArguments([$driver, $configuration]) - ->addTag('kdyby.console.command'); + // diffGenerator + if ($config['diffGenerator'] === 'doctrine') { + Validators::assertField($config, 'ignoredQueriesFile', 'null|string'); + $this->createDoctrineStructureDiffGeneratorDefinition($config['ignoredQueriesFile']); } - if ($config['diffGenerator'] !== FALSE) { - $builder->addDefinition($this->prefix('structureDiffGenerator')) - ->setClass('Nextras\Migrations\IDiffGenerator') - ->setDynamic(); // hack to suppress "Class Nextras\Migrations\IDiffGenerator (...) not found" + // groups + if ($config['groups'] === NULL) { + Validators::assertField($config, 'dir', 'string|Nette\PhpGenerator\PhpLiteral'); + Validators::assertField($config, 'withDummyData', 'bool'); + $config['groups'] = $this->createDefaultGroupConfiguration($config['dir'], $config['withDummyData']); + } - if ($config['diffGenerator'] === 'doctrine') { - $this->configureDoctrineStructureDiffGenerator(); - } + Validators::assertField($config, 'groups', 'array'); + $groups = $this->createGroupDefinitions($config['groups']); + + // extensionHandlers + Validators::assertField($config, 'phpParams', 'array'); + $extensionHandlers = $this->createExtensionHandlerDefinitions($driver, $config['phpParams']); + + // configuration + $configuration = $this->createConfigurationDefinition(); + + // commands + if (class_exists('Symfony\Component\Console\Command\Command')) { + $this->createSymfonyCommandDefinitions($driver, $configuration); } } @@ -112,14 +109,66 @@ public function beforeCompile() $factory->arguments = ["@$conn"]; } - // diff generators - if ($config['diffGenerator'] === TRUE && $builder->findByType('Doctrine\ORM\EntityManager')) { - $this->configureDoctrineStructureDiffGenerator(); + // diff generator + if ($config['diffGenerator'] === TRUE) { + if ($builder->findByType('Doctrine\ORM\EntityManager') && $builder->hasDefinition($this->prefix('group.structures'))) { + Validators::assertField($config, 'ignoredQueriesFile', 'null|string'); + $diffGenerator = $this->createDoctrineStructureDiffGeneratorDefinition($config['ignoredQueriesFile']); + $builder->getDefinition($this->prefix('group.structures')) + ->addSetup('$generator', [$diffGenerator]); + } + } + + // configuration + $groups = []; + foreach ($builder->findByTag(self::TAG_GROUP) as $serviceName => $_) { + $groups[] = $builder->getDefinition($serviceName); + } + + $extensionHandlers = []; + foreach ($builder->findByTag(self::TAG_EXTENSION_HANDLER) as $serviceName => $extensionName) { + $extensionHandlers[$extensionName] = $builder->getDefinition($serviceName); + } + + $builder->getDefinition($this->prefix('configuration')) + ->setArguments([$groups, $extensionHandlers]); + } + + + private function getDbalDefinition($dbal) + { + $factory = $this->getDbalFactory($dbal); + + if ($factory) { + return $this->getContainerBuilder() + ->addDefinition($this->prefix('dbal')) + ->setClass('Nextras\Migrations\IDbal') + ->setFactory($factory); + + } elseif ($dbal === NULL) { + return '@Nextras\Migrations\IDbal'; + + } else { + throw new Nextras\Migrations\LogicException('Invalid dbal value'); + } + } + + + private function getDbalFactory($dbal) + { + if ($dbal instanceof Nette\DI\Statement) { + return Nette\DI\Compiler::filterArguments([$dbal])[0]; + + } elseif (is_string($dbal) && isset($this->dbals[$dbal])) { + return $this->dbals[$dbal]; + + } else { + return NULL; } } - private function getDriver($driver, $dbal) + private function getDriverDefinition($driver, $dbal) { $factory = $this->getDriverFactory($driver, $dbal); @@ -152,54 +201,124 @@ private function getDriverFactory($driver, $dbal) } - private function getDbal($dbal) + private function createDefaultGroupConfiguration($dir, $withDummyData) { - $factory = $this->getDbalFactory($dbal); + $builder = $this->getContainerBuilder(); - if ($factory) { - return $this->getContainerBuilder() - ->addDefinition($this->prefix('dbal')) - ->setClass('Nextras\Migrations\IDbal') - ->setFactory($factory); + $groups = [ + 'structures' => [ + 'directory' => "$dir/structures", + ], + 'basic-data' => [ + 'directory' => "$dir/basic-data", + 'dependencies' => ['structures'], + ], + 'dummy-data' => [ + 'enabled' => $withDummyData, + 'directory' => "$dir/dummy-data", + 'dependencies' => ['structures', 'basic-data'], + ], + ]; + + foreach ($groups as $groupName => $groupConfig) { + $serviceName = $this->prefix("diffGenerator.$groupName"); + $diffGenerator = $builder->hasDefinition($serviceName) ? $builder->getDefinition($serviceName) : NULL; + $groups[$groupName]['generator'] = $diffGenerator; + } - } elseif ($dbal === NULL) { - return '@Nextras\Migrations\IDbal'; + return $groups; + } - } else { - throw new Nextras\Migrations\LogicException('Invalid dbal value'); + + private function createGroupDefinitions(array $groups) + { + $builder = $this->getContainerBuilder(); + $groupDefinitions = []; + + foreach ($groups as $groupName => $groupConfig) { + Validators::assertField($groupConfig, 'directory', 'string'); + + $enabled = isset($groupConfig['enabled']) ? $groupConfig['enabled'] : true; + $directory = $groupConfig['directory']; + $dependencies = isset($groupConfig['dependencies']) ? $groupConfig['dependencies'] : []; + $generator = isset($groupConfig['generator']) ? $groupConfig['generator'] : null; + + $serviceName = lcfirst(str_replace('-', '', ucwords($groupName, '-'))); + $groupDefinitions[] = $builder->addDefinition($this->prefix("group.$serviceName")) + ->addTag(self::TAG_GROUP) + ->setAutowired(FALSE) + ->setClass('Nextras\Migrations\Entities\Group') + ->addSetup('$name', [$groupName]) + ->addSetup('$enabled', [$enabled]) + ->addSetup('$directory', [$directory]) + ->addSetup('$dependencies', [$dependencies]) + ->addSetup('$generator', [$generator]); } + + return $groupDefinitions; } - private function getDbalFactory($dbal) + private function createExtensionHandlerDefinitions($driver, $phpParams) { - if ($dbal instanceof Nette\DI\Statement) { - return Nette\DI\Compiler::filterArguments([$dbal])[0]; + $builder = $this->getContainerBuilder(); - } elseif (is_string($dbal) && isset($this->dbals[$dbal])) { - return $this->dbals[$dbal]; + $sqlHandler = $builder->addDefinition($this->prefix('extensionHandler.sql')) + ->addTag(self::TAG_EXTENSION_HANDLER, 'sql') + ->setAutowired(FALSE) + ->setClass('Nextras\Migrations\Extensions\SqlHandler') + ->setArguments([$driver]); - } else { - return NULL; - } + $phpHandler = $builder->addDefinition($this->prefix('extensionHandler.php')) + ->addTag(self::TAG_EXTENSION_HANDLER, 'php') + ->setClass('Nextras\Migrations\Extensions\PhpHandler') + ->setAutowired(FALSE) + ->setArguments([$phpParams]); + + return [$sqlHandler, $phpHandler]; + } + + + private function createConfigurationDefinition() + { + return $this->getContainerBuilder() + ->addDefinition($this->prefix('configuration')) + ->setClass('Nextras\Migrations\IConfiguration') + ->setFactory('Nextras\Migrations\Configurations\Configuration'); } - private function configureDoctrineStructureDiffGenerator() + private function createDoctrineStructureDiffGeneratorDefinition($ignoredQueriesFile) { $builder = $this->getContainerBuilder(); - $config = $this->validateConfig($this->defaults); - $structureDiffGenerator = $builder->getDefinition($this->prefix('structureDiffGenerator')) - ->setDynamic(FALSE) + return $builder->addDefinition($this->prefix('diffGenerator.structures')) + ->setAutowired(FALSE) + ->setClass('Nextras\Migrations\IDiffGenerator') ->setFactory('Nextras\Migrations\Bridges\DoctrineOrm\StructureDiffGenerator') - ->setArguments([ - '@Doctrine\ORM\EntityManager', - $config['ignoredQueriesFile'] - ]); + ->setArguments(['@Doctrine\ORM\EntityManager', $ignoredQueriesFile]); + } + - $configuration = $builder->getDefinition($this->prefix('configuration')); - $configuration->addSetup('setStructureDiffGenerator', [$structureDiffGenerator]); + private function createSymfonyCommandDefinitions($driver, $configuration) + { + $builder = $this->getContainerBuilder(); + $builder->addExcludedClasses(['Nextras\Migrations\Bridges\SymfonyConsole\BaseCommand']); + + $builder->addDefinition($this->prefix('continueCommand')) + ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\ContinueCommand') + ->setArguments([$driver, $configuration]) + ->addTag('kdyby.console.command'); + + $builder->addDefinition($this->prefix('createCommand')) + ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\CreateCommand') + ->setArguments([$driver, $configuration]) + ->addTag('kdyby.console.command'); + + $builder->addDefinition($this->prefix('resetCommand')) + ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\ResetCommand') + ->setArguments([$driver, $configuration]) + ->addTag('kdyby.console.command'); } } diff --git a/src/Configurations/Configuration.php b/src/Configurations/Configuration.php new file mode 100644 index 0000000..b963644 --- /dev/null +++ b/src/Configurations/Configuration.php @@ -0,0 +1,57 @@ + IExtensionHandler) */ + private $extensionHandlers; + + + /** + * @param Group[] $groups + * @param IExtensionHandler[] $extensionHandlers (extension => IExtensionHandler) + */ + public function __construct(array $groups, array $extensionHandlers) + { + $this->groups = $groups; + $this->extensionHandlers = $extensionHandlers; + } + + + /** + * @return Group[] + */ + public function getGroups() + { + return $this->groups; + } + + + /** + * @return IExtensionHandler[] (extension => IExtensionHandler) + */ + public function getExtensionHandlers() + { + return $this->extensionHandlers; + } +} diff --git a/src/Configurations/DefaultConfiguration.php b/src/Configurations/DefaultConfiguration.php index d8060fe..c38819b 100644 --- a/src/Configurations/DefaultConfiguration.php +++ b/src/Configurations/DefaultConfiguration.php @@ -20,6 +20,7 @@ /** * @author Jan Tvrdík + * @deprecated */ class DefaultConfiguration implements IConfiguration { diff --git a/tests/cases/integration/MigrationsExtension.phpt b/tests/cases/integration/MigrationsExtension.phpt index 02074b8..964b6e4 100644 --- a/tests/cases/integration/MigrationsExtension.phpt +++ b/tests/cases/integration/MigrationsExtension.phpt @@ -51,7 +51,7 @@ class MigrationsExtensionTest extends TestCase $dic = $this->createContainer($config); $configuration = $dic->getByType('Nextras\Migrations\IConfiguration'); - Assert::type('Nextras\Migrations\Configurations\DefaultConfiguration', $configuration); + Assert::type('Nextras\Migrations\Configurations\Configuration', $configuration); $groups = $configuration->getGroups(); Assert::count(3, $groups); From 11e48e62c3bd30669360baa89b3ade26624a420e Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Mon, 22 Jan 2018 16:12:14 +0100 Subject: [PATCH 21/27] add IMigrationGroupsProvider --- .../NetteDI/IMigrationGroupsProvider.php | 22 ++++++ src/Bridges/NetteDI/MigrationsExtension.php | 12 ++++ src/Entities/Group.php | 69 ++++++++++++++++++- 3 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/Bridges/NetteDI/IMigrationGroupsProvider.php diff --git a/src/Bridges/NetteDI/IMigrationGroupsProvider.php b/src/Bridges/NetteDI/IMigrationGroupsProvider.php new file mode 100644 index 0000000..6bc911c --- /dev/null +++ b/src/Bridges/NetteDI/IMigrationGroupsProvider.php @@ -0,0 +1,22 @@ +compiler->getExtensions('Nextras\Migrations\Bridges\NetteDI\IMigrationGroupsProvider') as $provider) { + foreach ($provider->getMigrationGroups() as $group) { + $groups[$group->name] = [ + 'enabled' => $group->enabled, + 'directory' => $group->directory, + 'dependencies' => $group->dependencies, + 'generator' => $group->generator, + ]; + } + } + $builder = $this->getContainerBuilder(); $groupDefinitions = []; diff --git a/src/Entities/Group.php b/src/Entities/Group.php index a7655a9..aa66581 100644 --- a/src/Entities/Group.php +++ b/src/Entities/Group.php @@ -21,15 +21,80 @@ class Group public $name; /** @var bool */ - public $enabled; + public $enabled = true; /** @var string absolute path do directory */ public $directory; /** @var string[] */ - public $dependencies; + public $dependencies = []; /** @var IDiffGenerator|NULL */ public $generator; + + /** + * @param null|string $name + * @param null|string $directory + */ + public function __construct($name = null, $directory = null) + { + $this->name = $name; + $this->directory = $directory; + } + + + /** + * @param string $name + * @return static + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + + /** + * @param bool $enabled + * @return static + */ + public function setEnabled($enabled) + { + $this->enabled = $enabled; + return $this; + } + + + /** + * @param string $directory + * @return static + */ + public function setDirectory($directory) + { + $this->directory = $directory; + return $this; + } + + + /** + * @param string[] $dependencies + * @return static + */ + public function setDependencies(array $dependencies) + { + $this->dependencies = $dependencies; + return $this; + } + + + /** + * @param IDiffGenerator|NULL $generator + * @return static + */ + public function setGenerator(IDiffGenerator $generator = NULL) + { + $this->generator = $generator; + return $this; + } } From 87f5d43502e40cc49121be039eb6dc5e91a47c6e Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Tue, 23 Jan 2018 11:23:43 +0100 Subject: [PATCH 22/27] MySqlDriver: set names to utf8mb4 instead of utf8 --- src/Drivers/MySqlDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Drivers/MySqlDriver.php b/src/Drivers/MySqlDriver.php index d786e23..11d69c1 100644 --- a/src/Drivers/MySqlDriver.php +++ b/src/Drivers/MySqlDriver.php @@ -25,7 +25,7 @@ class MySqlDriver extends BaseDriver implements IDriver public function setupConnection() { parent::setupConnection(); - $this->dbal->exec('SET NAMES "utf8"'); + $this->dbal->exec('SET NAMES "utf8mb4"'); $this->dbal->exec('SET foreign_key_checks = 0'); $this->dbal->exec('SET time_zone = "SYSTEM"'); $this->dbal->exec('SET sql_mode = "TRADITIONAL"'); From cc1d7f23ec1be41b6bf7a60ef959029e44509790 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Thu, 25 Jan 2018 11:11:16 +0100 Subject: [PATCH 23/27] SymfonyConsole: support lazy loading of commands --- src/Bridges/SymfonyConsole/ContinueCommand.php | 6 +++++- src/Bridges/SymfonyConsole/CreateCommand.php | 5 ++++- src/Bridges/SymfonyConsole/ResetCommand.php | 5 ++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Bridges/SymfonyConsole/ContinueCommand.php b/src/Bridges/SymfonyConsole/ContinueCommand.php index d1298e1..d4b2a18 100644 --- a/src/Bridges/SymfonyConsole/ContinueCommand.php +++ b/src/Bridges/SymfonyConsole/ContinueCommand.php @@ -16,9 +16,13 @@ class ContinueCommand extends BaseCommand { + /** @var string */ + protected static $defaultName = 'migrations:continue'; + + protected function configure() { - $this->setName('migrations:continue'); + $this->setName(self::$defaultName); $this->setDescription('Updates database schema by running all new migrations'); $this->setHelp("If table 'migrations' does not exist in current database, it is created automatically."); } diff --git a/src/Bridges/SymfonyConsole/CreateCommand.php b/src/Bridges/SymfonyConsole/CreateCommand.php index b3da210..096a0e5 100644 --- a/src/Bridges/SymfonyConsole/CreateCommand.php +++ b/src/Bridges/SymfonyConsole/CreateCommand.php @@ -25,6 +25,9 @@ class CreateCommand extends BaseCommand const CONTENT_SOURCE_STDIN = 'stdin'; const CONTENT_SOURCE_EMPTY = 'empty'; + /** @var string */ + protected static $defaultName = 'migrations:create'; + /** @var string */ protected $defaultContentSource = self::CONTENT_SOURCE_DIFF; @@ -44,7 +47,7 @@ public function setDefaultContentSource($defaultContentSource) */ protected function configure() { - $this->setName('migrations:create'); + $this->setName(self::$defaultName); $this->setDescription('Creates new migration file with proper name (e.g. 2015-03-14-130836-label.sql)'); $this->setHelp('Prints path of the created file to standard output.'); diff --git a/src/Bridges/SymfonyConsole/ResetCommand.php b/src/Bridges/SymfonyConsole/ResetCommand.php index 05b91a2..930cf0a 100644 --- a/src/Bridges/SymfonyConsole/ResetCommand.php +++ b/src/Bridges/SymfonyConsole/ResetCommand.php @@ -16,9 +16,12 @@ class ResetCommand extends BaseCommand { + /** @var string */ + protected static $defaultName = 'migrations:reset'; + protected function configure() { - $this->setName('migrations:reset'); + $this->setName(self::$defaultName); $this->setDescription('Drops current database and recreates it from scratch'); $this->setHelp("Drops current database and runs all migrations"); } From 314f64dd3e1975f1a769cf025683aa9e48329388 Mon Sep 17 00:00:00 2001 From: Ondra Votava Date: Wed, 19 Sep 2018 16:37:35 +0200 Subject: [PATCH 24/27] Console: add migrations:status command --- src/Bridges/NetteDI/MigrationsExtension.php | 4 + .../NextrasMigrationsExtension.php | 5 ++ src/Bridges/SymfonyConsole/StatusCommand.php | 35 +++++++++ src/Engine/OrderResolver.php | 22 +++++- src/Engine/Runner.php | 27 ++++--- src/IPrinter.php | 28 +++++-- src/Printers/Console.php | 61 ++++++++++++--- src/Printers/DevNull.php | 45 ++++++++--- src/Printers/HtmlDump.php | 74 +++++++++++++++---- .../integration/MigrationsExtension.phpt | 4 +- tests/cases/integration/Runner.FirstRun.phpt | 18 +++++ tests/cases/integration/Runner.SecondRun.phpt | 22 ++++++ 12 files changed, 284 insertions(+), 61 deletions(-) create mode 100644 src/Bridges/SymfonyConsole/StatusCommand.php diff --git a/src/Bridges/NetteDI/MigrationsExtension.php b/src/Bridges/NetteDI/MigrationsExtension.php index 3a75a0d..a829b12 100644 --- a/src/Bridges/NetteDI/MigrationsExtension.php +++ b/src/Bridges/NetteDI/MigrationsExtension.php @@ -78,6 +78,10 @@ public function loadConfiguration() ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\ResetCommand') ->setArguments([$driver, $configuration]) ->addTag('kdyby.console.command'); + $builder->addDefinition($this->prefix('statusCommand')) + ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\StatusCommand') + ->setArguments([$driver, $configuration]) + ->addTag('kdyby.console.command'); } if ($config['diffGenerator'] !== FALSE) { diff --git a/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php b/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php index 71a578b..71a4ae9 100644 --- a/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php +++ b/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php @@ -81,12 +81,17 @@ public function load(array $configs, ContainerBuilder $container) $resetCommandDefinition = new Definition('Nextras\Migrations\Bridges\SymfonyConsole\ResetCommand'); $resetCommandDefinition->setAutowired(TRUE); $resetCommandDefinition->addTag('console.command'); + + $statusCommandDefinition = new Definition('Nextras\Migrations\Bridges\SymfonyConsole\StatusCommand'); + $statusCommandDefinition->setAutowired(TRUE); + $statusCommandDefinition->addTag('console.command'); $container->addDefinitions([ 'nextras_migrations.configuration' => $configurationDefinition, 'nextras_migrations.continue_command' => $continueCommandDefinition, 'nextras_migrations.create_command' => $createCommandDefinition, 'nextras_migrations.reset_command' => $resetCommandDefinition, + 'nextras_migrations.status_command' => $statusCommandDefinition, ]); if ($structureDiffGeneratorDefinition) { diff --git a/src/Bridges/SymfonyConsole/StatusCommand.php b/src/Bridges/SymfonyConsole/StatusCommand.php new file mode 100644 index 0000000..ad81bb0 --- /dev/null +++ b/src/Bridges/SymfonyConsole/StatusCommand.php @@ -0,0 +1,35 @@ +setName(self::$defaultName); + $this->setDescription('Show lists of completed or waiting migrations'); + } + + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->runMigrations(Runner::MODE_STATUS, $this->config); + } + +} diff --git a/src/Engine/OrderResolver.php b/src/Engine/OrderResolver.php index 6e92656..d0a80b7 100644 --- a/src/Engine/OrderResolver.php +++ b/src/Engine/OrderResolver.php @@ -27,13 +27,13 @@ class OrderResolver */ public function resolve(array $migrations, array $groups, array $files, $mode) { + $this->checkModeSupport($mode); + $groups = $this->getAssocGroups($groups); $this->validateGroups($groups); if ($mode === Runner::MODE_RESET) { return $this->sortFiles($files, $groups); - } elseif ($mode !== Runner::MODE_CONTINUE) { - throw new LogicException('Unsupported mode.'); } $migrations = $this->getAssocMigrations($migrations); @@ -254,5 +254,21 @@ private function validateGroups(array $groups) } } } - + + /** + * @param string $mode + */ + private function checkModeSupport($mode) + { + $supportedModes = [ + Runner::MODE_CONTINUE, + Runner::MODE_RESET, + Runner::MODE_STATUS, + ]; + + if (!in_array($mode, $supportedModes, true)) { + throw new LogicException('Unsupported mode.'); + } + } + } diff --git a/src/Engine/Runner.php b/src/Engine/Runner.php index b8f56a3..b9c34b0 100644 --- a/src/Engine/Runner.php +++ b/src/Engine/Runner.php @@ -29,6 +29,8 @@ class Runner const MODE_CONTINUE = 'continue'; const MODE_RESET = 'reset'; const MODE_INIT = 'init'; + const MODE_STATUS = 'status'; + /** @var IPrinter */ private $printer; @@ -79,12 +81,14 @@ public function addExtensionHandler($extension, IExtensionHandler $handler) $this->extensionsHandlers[$extension] = $handler; return $this; } - - + + /** - * @param string $mode self::MODE_CONTINUE|self::MODE_RESET|self::MODE_INIT + * @param string $mode self::MODE_CONTINUE|self::MODE_RESET|self::MODE_INIT|self::MODE_STATUS * @param IConfiguration $config + * * @return void + * @throws Exception */ public function run($mode = self::MODE_CONTINUE, IConfiguration $config = NULL) { @@ -120,12 +124,17 @@ public function run($mode = self::MODE_CONTINUE, IConfiguration $config = NULL) $migrations = $this->driver->getAllMigrations(); $files = $this->finder->find($this->groups, array_keys($this->extensionsHandlers)); $toExecute = $this->orderResolver->resolve($migrations, $this->groups, $files, $mode); - $this->printer->printToExecute($toExecute); - - foreach ($toExecute as $file) { - $time = microtime(TRUE); - $queriesCount = $this->execute($file); - $this->printer->printExecute($file, $queriesCount, microtime(TRUE) - $time); + + if ($mode === self::MODE_STATUS) { + $this->printer->printExecutedMigrations($migrations); + $this->printer->printToExecute($toExecute, true); + } else { + $this->printer->printToExecute($toExecute, false); + foreach ($toExecute as $file) { + $time = microtime(TRUE); + $queriesCount = $this->execute($file); + $this->printer->printExecute($file, $queriesCount, microtime(TRUE) - $time); + } } $this->driver->unlock(); diff --git a/src/IPrinter.php b/src/IPrinter.php index 586bc3c..a64d8f6 100644 --- a/src/IPrinter.php +++ b/src/IPrinter.php @@ -10,6 +10,7 @@ namespace Nextras\Migrations; use Nextras\Migrations\Entities\File; +use Nextras\Migrations\Entities\Migration; /** @@ -23,14 +24,25 @@ interface IPrinter * - continue = Running new migrations. * @param string $mode */ - function printIntro($mode); - - + public function printIntro($mode); + + /** + * List of migrations which executed has been completed. + * @param Migration[] $migrations + * + * @return void + */ + public function printExecutedMigrations(array $migrations); + /** * List of migrations which should be executed has been completed. + * * @param File[] $toExecute + * @param bool $withFileList + * + * @return void */ - function printToExecute(array $toExecute); + public function printToExecute(array $toExecute, $withFileList = false); /** @@ -39,26 +51,26 @@ function printToExecute(array $toExecute); * @param int $count number of executed queries * @param float $time elapsed time in milliseconds */ - function printExecute(File $file, $count, $time); + public function printExecute(File $file, $count, $time); /** * All migrations have been successfully executed. */ - function printDone(); + public function printDone(); /** * An error has occurred during execution of a migration. * @param Exception $e */ - function printError(Exception $e); + public function printError(Exception $e); /** * Prints init source code. * @param string $code */ - function printSource($code); + public function printSource($code); } diff --git a/src/Printers/Console.php b/src/Printers/Console.php index 1ecf23d..b47d008 100644 --- a/src/Printers/Console.php +++ b/src/Printers/Console.php @@ -10,6 +10,7 @@ namespace Nextras\Migrations\Printers; use Nextras\Migrations\Entities\File; +use Nextras\Migrations\Entities\Migration; use Nextras\Migrations\Exception; use Nextras\Migrations\IPrinter; @@ -34,26 +35,58 @@ public function __construct() { $this->useColors = $this->detectColorSupport(); } - - + + /** + * @inheritdoc + */ public function printIntro($mode) { $this->output('Nextras Migrations'); $this->output(strtoupper($mode), self::COLOR_INTRO); } - - - public function printToExecute(array $toExecute) + + /** + * @inheritdoc + */ + public function printExecutedMigrations(array $migrations) + { + if ($migrations) { + $this->output('Executed migrations:'); + /** @var Migration $migration */ + foreach ($migrations as $migration) { + $this->output('- ' . $migration->group . '/' . $migration->filename . ' OK', self::COLOR_SUCCESS); + } + $this->output(' '); + } else { + $this->output('No migrations has executed yet.'); + } + } + + /** + * @inheritdoc + */ + public function printToExecute(array $toExecute, $withFileList = false) { if ($toExecute) { $count = count($toExecute); - $this->output($count . ' migration' . ($count > 1 ? 's' : '') . ' need' . ($count > 1 ? '' : 's') . ' to be executed.'); + $this->output(sprintf( + '%s migration%s need%s to be executed%s', + $count,$count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.')) + ); + if ($withFileList) { + /** @var File $file */ + foreach ($toExecute as $file) { + $this->output('- ' . $file->group->name . '/' . $file->name, self::COLOR_INFO); + } + } } else { $this->output('No migration needs to be executed.'); } } - - + + /** + * @inheritdoc + */ public function printExecute(File $file, $count, $time) { $this->output( @@ -62,14 +95,18 @@ public function printExecute(File $file, $count, $time) . $this->color(sprintf('%0.3f', $time), self::COLOR_INFO) . ' s' ); } - - + + /** + * @inheritdoc + */ public function printDone() { $this->output('OK', self::COLOR_SUCCESS); } - - + + /** + * @inheritdoc + */ public function printError(Exception $e) { $this->output('ERROR: ' . $e->getMessage(), self::COLOR_ERROR); diff --git a/src/Printers/DevNull.php b/src/Printers/DevNull.php index 5a26677..365b1e3 100644 --- a/src/Printers/DevNull.php +++ b/src/Printers/DevNull.php @@ -10,6 +10,7 @@ namespace Nextras\Migrations\Printers; use Nextras\Migrations\Entities\File; +use Nextras\Migrations\Entities\Migration; use Nextras\Migrations\Exception; use Nextras\Migrations\IPrinter; @@ -20,32 +21,54 @@ */ class DevNull implements IPrinter { + /** + * @inheritdoc + */ public function printIntro($mode) { } - - - public function printToExecute(array $toExecute) + + /** + * @inheritdoc + */ + public function printToExecute(array $toExecute, $withFileList = false) { } - - + + /** + * @inheritdoc + */ public function printExecute(File $file, $count, $time) { } - - + + /** + * @inheritdoc + */ public function printDone() { } - - + + /** + * @inheritdoc + */ public function printError(Exception $e) { } - - + + /** + * @inheritdoc + */ public function printSource($code) { } + + /** + * @inheritdoc + */ + public function printExecutedMigrations(array $migrations) + { + + } } + diff --git a/src/Printers/HtmlDump.php b/src/Printers/HtmlDump.php index 0557578..d2bcfef 100644 --- a/src/Printers/HtmlDump.php +++ b/src/Printers/HtmlDump.php @@ -11,6 +11,7 @@ use Nextras\Migrations\Engine\Runner; use Nextras\Migrations\Entities\File; +use Nextras\Migrations\Entities\Migration; use Nextras\Migrations\Exception; use Nextras\Migrations\IPrinter; @@ -25,31 +26,66 @@ class HtmlDump implements IPrinter /** @var int order of last executed migration */ private $index; - - + + /** + * @inheritdoc + */ public function printIntro($mode) { if ($mode === Runner::MODE_RESET) { $this->output(' RESET: All tables, views and data has been destroyed!'); - } else { + } + if ($mode === Runner::MODE_CONTINUE) { $this->output(' CONTINUE: Running only new migrations.'); } + if($mode === Runner::MODE_STATUS) { + $this->output(' STATUS: Show lists of completed or waiting migrations'); + } } - - - public function printToExecute(array $toExecute) + + public function printExecutedMigrations(array $migrations) + { + if ($migrations) { + $this->output('Executed migrations:'); + /** @var Migration $migration */ + foreach ($migrations as $migration) { + $this->output('- ' . $migration->group . '/' . $migration->filename . ' OK', 'success'); + } + $this->output(' '); + } else { + $this->output('No migrations has executed yet'); + } + } + + /** + * @inheritdoc + */ + public function printToExecute(array $toExecute, $withFileList = false) { + $count = 0; if ($toExecute) { - $this->output(' ' . count($toExecute) . ' migrations need to be executed.'); + $count = count($toExecute); + $this->output(sprintf( + '%s migration%s need%s to be executed%s', + $count,$$count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.')) + ); + if ($withFileList) { + /** @var File $file */ + foreach ($toExecute as $file) { + $this->output(' - ' . $file->group->name . '/' . $file->name); + } + } } else { $this->output('No migration needs to be executed.'); } - $this->count = count($toExecute); + $this->count = $count; $this->index = 0; } - - + + /** + * @inheritdoc + */ public function printExecute(File $file, $count, $time) { $format = '%0' . strlen($this->count) . 'd'; @@ -59,21 +95,27 @@ public function printExecute(File $file, $count, $time) ++$this->index, $this->count, $name, $count, ($count === 1 ? 'query' : 'queries'), $time )); } - - + + /** + * @inheritdoc + */ public function printDone() { $this->output('OK', 'success'); } - - + + /** + * @inheritdoc + */ public function printError(Exception $e) { $this->output('ERROR: ' . htmlspecialchars($e->getMessage()), 'error'); throw $e; } - - + + /** + * @inheritdoc + */ public function printSource($code) { $this->output($code); diff --git a/tests/cases/integration/MigrationsExtension.phpt b/tests/cases/integration/MigrationsExtension.phpt index 02074b8..4273454 100644 --- a/tests/cases/integration/MigrationsExtension.phpt +++ b/tests/cases/integration/MigrationsExtension.phpt @@ -25,8 +25,8 @@ class MigrationsExtensionTest extends TestCase $dic = $this->createContainer($config); Assert::type('Nextras\Migrations\Drivers\MySqlDriver', $dic->getByType('Nextras\Migrations\IDriver')); - Assert::count(3, $dic->findByType('Symfony\Component\Console\Command\Command')); - Assert::count(3, $dic->findByTag('kdyby.console.command')); + Assert::count(4, $dic->findByType('Symfony\Component\Console\Command\Command')); + Assert::count(4, $dic->findByTag('kdyby.console.command')); } diff --git a/tests/cases/integration/Runner.FirstRun.phpt b/tests/cases/integration/Runner.FirstRun.phpt index c683ec9..0edeec4 100644 --- a/tests/cases/integration/Runner.FirstRun.phpt +++ b/tests/cases/integration/Runner.FirstRun.phpt @@ -91,6 +91,24 @@ class FirstRunTest extends IntegrationTestCase Assert::type('DateTime', $migrations[2]->executedAt); Assert::same('basic-data', $migrations[2]->group); } + + public function testStatus() + { + $this->runner->run(Runner::MODE_STATUS); + Assert::same([ + 'Nextras Migrations', + 'STATUS', + 'No migrations has executed yet.', + '5 migrations need to be executed:', + '- structures/001.sql', + '- structures/002.sql', + '- basic-data/003.sql', + '- dummy-data/004.sql', + '- structures/005.sql', + 'OK', + ], $this->printer->lines); + + } public function testInit() diff --git a/tests/cases/integration/Runner.SecondRun.phpt b/tests/cases/integration/Runner.SecondRun.phpt index bacb130..be6d21f 100644 --- a/tests/cases/integration/Runner.SecondRun.phpt +++ b/tests/cases/integration/Runner.SecondRun.phpt @@ -95,6 +95,28 @@ class SecondRunTest extends IntegrationTestCase } } } + + public function testStatus() + { + $this->driver->loadFile($this->fixtureDir . '/3ok.sql'); + Assert::count(3, $this->driver->getAllMigrations()); + + $this->runner->run(Runner::MODE_STATUS); + Assert::same([ + 'Nextras Migrations', + 'STATUS', + 'Executed migrations:', + '- structures/001.sql OK', + '- structures/002.sql OK', + '- basic-data/003.sql OK', + ' ', + '2 migrations need to be executed:', + '- dummy-data/004.sql', + '- structures/005.sql', + 'OK', + ], $this->printer->lines); + + } } From dcb807ff3bb04f1a0de21570d7172662d4dadd5b Mon Sep 17 00:00:00 2001 From: Ondra Votava Date: Wed, 19 Sep 2018 17:06:32 +0200 Subject: [PATCH 25/27] Console: fix typo --- src/Printers/HtmlDump.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Printers/HtmlDump.php b/src/Printers/HtmlDump.php index d2bcfef..13f77bf 100644 --- a/src/Printers/HtmlDump.php +++ b/src/Printers/HtmlDump.php @@ -67,7 +67,7 @@ public function printToExecute(array $toExecute, $withFileList = false) $count = count($toExecute); $this->output(sprintf( '%s migration%s need%s to be executed%s', - $count,$$count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.')) + $count,$count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.')) ); if ($withFileList) { /** @var File $file */ From 1a8b551aa72d26bc42cc123329aeaed817f4d019 Mon Sep 17 00:00:00 2001 From: Ondra Votava Date: Wed, 19 Sep 2018 17:13:45 +0200 Subject: [PATCH 26/27] Code formating --- src/Printers/Console.php | 14 ++++++++------ src/Printers/HtmlDump.php | 31 ++++++++++++++++++------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/Printers/Console.php b/src/Printers/Console.php index b47d008..00e4deb 100644 --- a/src/Printers/Console.php +++ b/src/Printers/Console.php @@ -54,8 +54,8 @@ public function printExecutedMigrations(array $migrations) $this->output('Executed migrations:'); /** @var Migration $migration */ foreach ($migrations as $migration) { - $this->output('- ' . $migration->group . '/' . $migration->filename . ' OK', self::COLOR_SUCCESS); - } + $this->output('- ' . $migration->group . '/' . $migration->filename . ' OK', self::COLOR_SUCCESS); + } $this->output(' '); } else { $this->output('No migrations has executed yet.'); @@ -65,13 +65,15 @@ public function printExecutedMigrations(array $migrations) /** * @inheritdoc */ - public function printToExecute(array $toExecute, $withFileList = false) + public function printToExecute(array $toExecute, $withFileList = FALSE) { if ($toExecute) { $count = count($toExecute); - $this->output(sprintf( - '%s migration%s need%s to be executed%s', - $count,$count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.')) + $this->output( + sprintf( + '%s migration%s need%s to be executed%s', + $count, $count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.') + ) ); if ($withFileList) { /** @var File $file */ diff --git a/src/Printers/HtmlDump.php b/src/Printers/HtmlDump.php index 13f77bf..6f7a9a7 100644 --- a/src/Printers/HtmlDump.php +++ b/src/Printers/HtmlDump.php @@ -38,7 +38,7 @@ public function printIntro($mode) if ($mode === Runner::MODE_CONTINUE) { $this->output(' CONTINUE: Running only new migrations.'); } - if($mode === Runner::MODE_STATUS) { + if ($mode === Runner::MODE_STATUS) { $this->output(' STATUS: Show lists of completed or waiting migrations'); } } @@ -60,14 +60,16 @@ public function printExecutedMigrations(array $migrations) /** * @inheritdoc */ - public function printToExecute(array $toExecute, $withFileList = false) + public function printToExecute(array $toExecute, $withFileList = FALSE) { $count = 0; if ($toExecute) { $count = count($toExecute); - $this->output(sprintf( - '%s migration%s need%s to be executed%s', - $count,$count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.')) + $this->output( + sprintf( + '%s migration%s need%s to be executed%s', + $count, $count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.') + ) ); if ($withFileList) { /** @var File $file */ @@ -90,10 +92,12 @@ public function printExecute(File $file, $count, $time) { $format = '%0' . strlen($this->count) . 'd'; $name = htmlspecialchars($file->group->name . '/' . $file->name); - $this->output(sprintf( - $format . '/' . $format . ': %s (%d %s, %0.3f s)', - ++$this->index, $this->count, $name, $count, ($count === 1 ? 'query' : 'queries'), $time - )); + $this->output( + sprintf( + $format . '/' . $format . ': %s (%d %s, %0.3f s)', + ++$this->index, $this->count, $name, $count, ($count === 1 ? 'query' : 'queries'), $time + ) + ); } /** @@ -120,16 +124,17 @@ public function printSource($code) { $this->output($code); } - - + + /** - * @param string $s HTML string + * @param string $s HTML string * @param string $class + * * @return void */ protected function output($s, $class = 'info') { echo "
$s
\n"; } - + } From 5a25b950b790b449a445d8787da18233a264ebce Mon Sep 17 00:00:00 2001 From: Ondra Votava Date: Wed, 19 Sep 2018 18:52:29 +0200 Subject: [PATCH 27/27] Code formating --- src/Printers/Console.php | 14 ++++++++------ src/Printers/HtmlDump.php | 31 ++++++++++++++++++------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/Printers/Console.php b/src/Printers/Console.php index b47d008..00e4deb 100644 --- a/src/Printers/Console.php +++ b/src/Printers/Console.php @@ -54,8 +54,8 @@ public function printExecutedMigrations(array $migrations) $this->output('Executed migrations:'); /** @var Migration $migration */ foreach ($migrations as $migration) { - $this->output('- ' . $migration->group . '/' . $migration->filename . ' OK', self::COLOR_SUCCESS); - } + $this->output('- ' . $migration->group . '/' . $migration->filename . ' OK', self::COLOR_SUCCESS); + } $this->output(' '); } else { $this->output('No migrations has executed yet.'); @@ -65,13 +65,15 @@ public function printExecutedMigrations(array $migrations) /** * @inheritdoc */ - public function printToExecute(array $toExecute, $withFileList = false) + public function printToExecute(array $toExecute, $withFileList = FALSE) { if ($toExecute) { $count = count($toExecute); - $this->output(sprintf( - '%s migration%s need%s to be executed%s', - $count,$count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.')) + $this->output( + sprintf( + '%s migration%s need%s to be executed%s', + $count, $count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.') + ) ); if ($withFileList) { /** @var File $file */ diff --git a/src/Printers/HtmlDump.php b/src/Printers/HtmlDump.php index 13f77bf..6f7a9a7 100644 --- a/src/Printers/HtmlDump.php +++ b/src/Printers/HtmlDump.php @@ -38,7 +38,7 @@ public function printIntro($mode) if ($mode === Runner::MODE_CONTINUE) { $this->output(' CONTINUE: Running only new migrations.'); } - if($mode === Runner::MODE_STATUS) { + if ($mode === Runner::MODE_STATUS) { $this->output(' STATUS: Show lists of completed or waiting migrations'); } } @@ -60,14 +60,16 @@ public function printExecutedMigrations(array $migrations) /** * @inheritdoc */ - public function printToExecute(array $toExecute, $withFileList = false) + public function printToExecute(array $toExecute, $withFileList = FALSE) { $count = 0; if ($toExecute) { $count = count($toExecute); - $this->output(sprintf( - '%s migration%s need%s to be executed%s', - $count,$count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.')) + $this->output( + sprintf( + '%s migration%s need%s to be executed%s', + $count, $count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.') + ) ); if ($withFileList) { /** @var File $file */ @@ -90,10 +92,12 @@ public function printExecute(File $file, $count, $time) { $format = '%0' . strlen($this->count) . 'd'; $name = htmlspecialchars($file->group->name . '/' . $file->name); - $this->output(sprintf( - $format . '/' . $format . ': %s (%d %s, %0.3f s)', - ++$this->index, $this->count, $name, $count, ($count === 1 ? 'query' : 'queries'), $time - )); + $this->output( + sprintf( + $format . '/' . $format . ': %s (%d %s, %0.3f s)', + ++$this->index, $this->count, $name, $count, ($count === 1 ? 'query' : 'queries'), $time + ) + ); } /** @@ -120,16 +124,17 @@ public function printSource($code) { $this->output($code); } - - + + /** - * @param string $s HTML string + * @param string $s HTML string * @param string $class + * * @return void */ protected function output($s, $class = 'info') { echo "
$s
\n"; } - + }