diff --git a/composer.json b/composer.json index fd74e986..2f456c8d 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ }, "require": { "php": ">=5.5.9", - "robmorgan/phinx": "0.6.6", + "robmorgan/phinx": "0.8.1", "cakephp/orm": "~3.2", "cakephp/cache": "~3.2" }, diff --git a/src/AbstractSeed.php b/src/AbstractSeed.php index 31b8f22a..8372c72a 100644 --- a/src/AbstractSeed.php +++ b/src/AbstractSeed.php @@ -102,7 +102,8 @@ protected function runCall($seeder) $seedCommand->setInput($input); $config = $seedCommand->getConfig(); - require_once($config->getSeedPath() . DS . $seeder . '.php'); + $seedPaths = $config->getSeedPaths(); + require_once(array_pop($seedPaths) . DS . $seeder . '.php'); $seeder = new $seeder(); $seeder->setOutput($this->getOutput()); $seeder->setAdapter($this->getAdapter()); diff --git a/src/CakeAdapter.php b/src/CakeAdapter.php index a13b9289..9379515a 100644 --- a/src/CakeAdapter.php +++ b/src/CakeAdapter.php @@ -79,6 +79,17 @@ public function getConnection() return $this->adapter->getConnection(); } + /** + * Gets the database PDO connection object. + * + * @param \PDO $connection Connection + * @return AdapterInterface + */ + public function setConnection($connection) + { + return $this->adapter->setConnection($connection); + } + /** * Gets the CakePHP Connection object. * @@ -456,6 +467,17 @@ public function dropTable($tableName) $this->adapter->dropTable($tableName); } + /** + * Truncates the specified table + * + * @param string $tableName + * @return void + */ + public function truncateTable($tableName) + { + $this->adapter->truncateTable($tableName); + } + /** * Returns table columns * @@ -697,6 +719,18 @@ public function dropDatabase($name) $this->adapter->dropDatabase($name); } + /** + * Cast a value to a boolean appropriate for the adapter. + * + * @param mixed $value The value to be cast + * + * @return mixed + */ + public function castToBool($value) + { + return $this->adapter->castToBool($value); + } + /** * Sets the console input. * diff --git a/src/Command/Create.php b/src/Command/Create.php index d8c9b3b2..3b868d3d 100644 --- a/src/Command/Create.php +++ b/src/Command/Create.php @@ -50,6 +50,12 @@ protected function configure() 'l', InputOption::VALUE_REQUIRED, 'Use a class implementing "' . parent::CREATION_INTERFACE . '" to generate the template' + ) + ->addOption( + 'path', + null, + InputOption::VALUE_REQUIRED, + 'Specify the path in which to create this migration' ); } @@ -67,7 +73,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln('renaming file in CamelCase to follow CakePHP convention...'); - $migrationPath = $this->getConfig()->getMigrationPath() . DS; + $migrationPaths = $this->getConfig()->getMigrationPaths(); + $migrationPath = array_pop($migrationPaths) . DS; $name = $input->getArgument('name'); list($phinxTimestamp, $phinxName) = explode('_', Util::mapClassNameToFileName($name), 2); $migrationFilename = glob($migrationPath . '*' . $phinxName); diff --git a/src/Command/MarkMigrated.php b/src/Command/MarkMigrated.php index f17a8679..fb1f1e1e 100644 --- a/src/Command/MarkMigrated.php +++ b/src/Command/MarkMigrated.php @@ -105,7 +105,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->bootstrap($input, $output); $this->output($output); - $path = $this->getConfig()->getMigrationPath(); + $migrationPaths = $this->getConfig()->getMigrationPaths(); + $path = array_pop($migrationPaths); if ($this->invalidOnlyOrExclude()) { $output->writeln( diff --git a/src/Command/Seed.php b/src/Command/Seed.php index 90360399..31f063ec 100644 --- a/src/Command/Seed.php +++ b/src/Command/Seed.php @@ -21,10 +21,10 @@ class Seed extends SeedRun { - use CommandTrait; - use ConfigurationTrait { + use CommandTrait { execute as parentExecute; } + use ConfigurationTrait; use EventDispatcherTrait; /** diff --git a/src/Migrations.php b/src/Migrations.php index afc285af..a37d1f16 100644 --- a/src/Migrations.php +++ b/src/Migrations.php @@ -14,6 +14,7 @@ use Cake\Datasource\ConnectionManager; use Phinx\Config\Config; use Phinx\Config\ConfigInterface; +use Phinx\Migration\Manager; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\NullOutput; @@ -230,8 +231,9 @@ public function markMigrated($version = null, $options = []) $input = $this->getInput('MarkMigrated', ['version' => $version], $options); $this->setInput($input); + $migrationPaths = $this->getConfig()->getMigrationPaths(); $params = [ - $this->getConfig()->getMigrationPath(), + array_pop($migrationPaths), $this->getManager()->getVersionsToMark($input), $this->output ]; @@ -281,8 +283,16 @@ public function seed($options = []) protected function run($method, $params, $input) { if ($this->configuration instanceof Config) { - $migrationPath = $this->getConfig()->getMigrationPath(); - $seedPath = $this->getConfig()->getSeedPath(); + $migrationPaths = $this->getConfig()->getMigrationPaths(); + $migrationPath = array_pop($migrationPaths); + $seedPaths = $this->getConfig()->getSeedPaths(); + $seedPath = array_pop($seedPaths); + } + + if ($this->manager instanceof Manager) { + $pdo = $this->manager->getEnvironment('default') + ->getAdapter() + ->getConnection(); } $this->setInput($input); @@ -290,10 +300,19 @@ protected function run($method, $params, $input) $manager = $this->getManager($newConfig); $manager->setInput($input); - if (isset($migrationPath) && $newConfig->getMigrationPath() !== $migrationPath) { + + if (isset($pdo)) { + $this->manager->getEnvironment('default') + ->getAdapter() + ->setConnection($pdo); + } + + $newMigrationPaths = $newConfig->getMigrationPaths(); + if (isset($migrationPath) && array_pop($newMigrationPaths) !== $migrationPath) { $manager->resetMigrations(); } - if (isset($seedPath) && $newConfig->getSeedPath() !== $seedPath) { + $newSeedPaths = $newConfig->getSeedPaths(); + if (isset($seedPath) && array_pop($newSeedPaths) !== $seedPath) { $manager->resetSeeds(); } diff --git a/src/Table.php b/src/Table.php index c7708be8..7eeccf10 100644 --- a/src/Table.php +++ b/src/Table.php @@ -98,10 +98,30 @@ public function create() */ public function update() { + if ($this->getAdapter()->getAdapterType() == 'sqlite') { + $this->foreignKeys = []; + } + parent::update(); TableRegistry::clear(); } + /** + * {@inheritDoc} + * + * We disable foreign key deletion for the SQLite adapter as SQLite does not support the feature natively and the + * process implemented by Phinx has serious side-effects (for instance it rename FK references in existing tables + * which breaks the database schema cohesion). + */ + public function dropForeignKey($columns, $constraint = null) + { + if ($this->getAdapter()->getAdapterType() == 'sqlite') { + return $this; + } + + return parent::dropForeignKey($columns, $constraint); + } + /** * This method is called in case a primary key was defined using the addPrimaryKey() method. * It currently does something only if using SQLite. diff --git a/tests/CommandTester.php b/tests/CommandTester.php new file mode 100644 index 00000000..6a7f2df8 --- /dev/null +++ b/tests/CommandTester.php @@ -0,0 +1,160 @@ +command = $command; + } + + /** + * Executes the command. + * + * Available execution options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * + * @param array $input An array of command arguments and options + * @param array $options An array of execution options + * + * @return int The command exit code + */ + public function execute(array $input, array $options = array()) + { + // set the command name automatically if the application requires + // this argument and no command name was passed + if (!isset($input['command']) + && (null !== $application = $this->command->getApplication()) + && $application->getDefinition()->hasArgument('command') + ) { + $input = array_merge(array('command' => $this->command->getName()), $input); + } + + $this->input = new ArrayInput($input); + if ($this->inputs) { + $this->input->setStream(self::createStream($this->inputs)); + } + + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + // This is where the magic does its magic : we use the output object of the command. + $this->output = $this->command->getManager()->getOutput(); + $this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false); + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + + return $this->statusCode = $this->command->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the command. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the command. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the command. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the application. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Sets the user inputs. + * + * @param array An array of strings representing each input + * passed to the command input stream. + * + * @return CommandTester + */ + public function setInputs(array $inputs) + { + $this->inputs = $inputs; + + return $this; + } + + private static function createStream(array $inputs) + { + $stream = fopen('php://memory', 'r+', false); + + fwrite($stream, implode(PHP_EOL, $inputs)); + rewind($stream); + + return $stream; + } +} diff --git a/tests/TestCase/Command/CreateTest.php b/tests/TestCase/Command/CreateTest.php index 0973db46..1bdfe155 100644 --- a/tests/TestCase/Command/CreateTest.php +++ b/tests/TestCase/Command/CreateTest.php @@ -119,7 +119,7 @@ protected function getCommandTester($params) $manager = new CakeManager($this->command->getConfig(), $input, $this->streamOutput); $manager->getEnvironment('default')->getAdapter()->setConnection($this->Connection->driver()->connection()); $this->command->setManager($manager); - $commandTester = new CommandTester($this->command); + $commandTester = new \Migrations\Test\CommandTester($this->command); return $commandTester; } diff --git a/tests/TestCase/Command/DumpTest.php b/tests/TestCase/Command/DumpTest.php index e6b3bfef..03971614 100644 --- a/tests/TestCase/Command/DumpTest.php +++ b/tests/TestCase/Command/DumpTest.php @@ -71,6 +71,7 @@ public function setUp() parent::setUp(); $this->Connection = ConnectionManager::get('test'); + $this->pdo = $this->Connection->driver()->connection(); $application = new MigrationsDispatcher('testing'); $this->command = $application->find('dump'); $this->streamOutput = new StreamOutput(fopen('php://memory', 'w', false)); @@ -85,6 +86,9 @@ public function setUp() public function tearDown() { parent::tearDown(); + $this->Connection->driver()->connection($this->pdo); + $this->Connection->execute('DROP TABLE IF EXISTS phinxlog'); + $this->Connection->execute('DROP TABLE IF EXISTS numbers'); unset($this->Connection, $this->command, $this->streamOutput); } @@ -159,16 +163,12 @@ public function testExecuteTables() */ protected function getCommandTester($params) { - if (!$this->Connection->driver()->isConnected()) { - $this->Connection->driver()->connect(); - } - $input = new ArrayInput($params, $this->command->getDefinition()); $this->command->setInput($input); $manager = new CakeManager($this->command->getConfig(), $input, $this->streamOutput); - $manager->getEnvironment('default')->getAdapter()->setConnection($this->Connection->driver()->connection()); + $manager->getEnvironment('default')->getAdapter()->setConnection($this->pdo); $this->command->setManager($manager); - $commandTester = new CommandTester($this->command); + $commandTester = new \Migrations\Test\CommandTester($this->command); return $commandTester; } @@ -190,7 +190,7 @@ protected function getMigrations() ->getManager($this->command->getConfig()) ->getEnvironment('default') ->getAdapter() - ->setConnection($this->Connection->driver()->connection()); + ->setConnection($this->pdo); $tables = (new Collection($this->Connection))->listTables(); if (in_array('phinxlog', $tables)) { diff --git a/tests/TestCase/Command/MarkMigratedTest.php b/tests/TestCase/Command/MarkMigratedTest.php index b626f187..ac844b5c 100644 --- a/tests/TestCase/Command/MarkMigratedTest.php +++ b/tests/TestCase/Command/MarkMigratedTest.php @@ -62,6 +62,7 @@ public function setUp() parent::setUp(); $this->Connection = ConnectionManager::get('test'); + $this->pdo = $this->Connection->driver()->connection(); $this->Connection->execute('DROP TABLE IF EXISTS phinxlog'); $this->Connection->execute('DROP TABLE IF EXISTS numbers'); @@ -78,6 +79,7 @@ public function setUp() public function tearDown() { parent::tearDown(); + $this->Connection->driver()->connection($this->pdo); $this->Connection->execute('DROP TABLE IF EXISTS phinxlog'); $this->Connection->execute('DROP TABLE IF EXISTS numbers'); unset($this->Connection, $this->commandTester, $this->command); @@ -113,7 +115,7 @@ public function testExecute() $this->assertEquals('20150704160200', $result[0]['version']); $this->assertEquals('20150724233100', $result[1]['version']); $this->assertEquals('20150826191400', $result[2]['version']); - + $this->commandTester->execute([ 'command' => $this->command->getName(), '--connection' => 'test', @@ -157,7 +159,7 @@ public function testExecute() $application = new MigrationsDispatcher('testing'); $buggyCommand = $application->find('mark_migrated'); $buggyCommand->setManager($manager); - $buggyCommandTester = new CommandTester($buggyCommand); + $buggyCommandTester = new \Migrations\Test\CommandTester($buggyCommand); $buggyCommandTester->execute([ 'command' => $this->command->getName(), '--connection' => 'test', diff --git a/tests/TestCase/Command/SeedTest.php b/tests/TestCase/Command/SeedTest.php index cfa0458e..f1795082 100644 --- a/tests/TestCase/Command/SeedTest.php +++ b/tests/TestCase/Command/SeedTest.php @@ -20,7 +20,6 @@ use Migrations\MigrationsDispatcher; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\StreamOutput; -use Symfony\Component\Console\Tester\CommandTester; /** * SeedTest class @@ -67,6 +66,7 @@ public function setUp() parent::setUp(); $this->Connection = ConnectionManager::get('test'); + $this->pdo = $this->Connection->driver()->connection(); $application = new MigrationsDispatcher('testing'); $this->command = $application->find('seed'); $this->streamOutput = new StreamOutput(fopen('php://memory', 'w', false)); @@ -80,6 +80,9 @@ public function setUp() public function tearDown() { parent::tearDown(); + $this->Connection->driver()->connection($this->pdo); + $this->Connection->execute('DROP TABLE IF EXISTS phinxlog'); + $this->Connection->execute('DROP TABLE IF EXISTS numbers'); unset($this->Connection, $this->command, $this->streamOutput); } @@ -187,7 +190,7 @@ public function testExecuteWrongCustomParams() ]); $display = $this->getDisplayFromOutput(); - $this->assertEmpty($display); + $this->assertTextNotContains('seeded', $display); $migrations->rollback(['target' => 0]); } @@ -230,16 +233,12 @@ public function testExecuteSeedCallingOtherSeeders() */ protected function getCommandTester($params) { - if (!$this->Connection->driver()->isConnected()) { - $this->Connection->driver()->connect(); - } - $input = new ArrayInput($params, $this->command->getDefinition()); $this->command->setInput($input); $manager = new CakeManager($this->command->getConfig(), $input, $this->streamOutput); - $manager->getEnvironment('default')->getAdapter()->setConnection($this->Connection->driver()->connection()); + $manager->getEnvironment('default')->getAdapter()->setConnection($this->pdo); $this->command->setManager($manager); - $commandTester = new CommandTester($this->command); + $commandTester = new \Migrations\Test\CommandTester($this->command); return $commandTester; } @@ -261,22 +260,13 @@ protected function getMigrations() ->getManager($this->command->getConfig()) ->getEnvironment('default') ->getAdapter() - ->setConnection($this->Connection->driver()->connection()); - - $tables = (new Collection($this->Connection))->listTables(); - if (in_array('phinxlog', $tables)) { - $ormTable = TableRegistry::get('phinxlog', ['connection' => $this->Connection]); - $query = $this->Connection->driver()->schemaDialect()->truncateTableSql($ormTable->schema()); - foreach ($query as $stmt) { - $this->Connection->execute($stmt); - } - } + ->setConnection($this->pdo); return $migrations; } /** - * Extract the content that was stored in self::$streamOutput. + * Extract the content that was stored in self::$output. * * @return string */ diff --git a/tests/TestCase/Command/StatusTest.php b/tests/TestCase/Command/StatusTest.php index 0826dffb..c9f33501 100644 --- a/tests/TestCase/Command/StatusTest.php +++ b/tests/TestCase/Command/StatusTest.php @@ -74,6 +74,7 @@ public function setUp() parent::setUp(); $this->Connection = ConnectionManager::get('test'); + $this->pdo = $this->Connection->driver()->connection(); $this->Connection->execute('DROP TABLE IF EXISTS phinxlog'); $this->Connection->execute('DROP TABLE IF EXISTS numbers'); @@ -90,6 +91,7 @@ public function setUp() public function tearDown() { parent::tearDown(); + $this->Connection->driver()->connection($this->pdo); $this->Connection->execute('DROP TABLE IF EXISTS phinxlog'); $this->Connection->execute('DROP TABLE IF EXISTS numbers'); unset($this->Connection, $this->command, $this->streamOutput); @@ -183,8 +185,10 @@ public function testExecuteWithInconsistency() $migrations = $this->getMigrations(); $migrations->migrate(); - $origin = $migrations->getConfig()->getMigrationPath() . DS . '20150724233100_update_numbers_table.php'; - $destination = $migrations->getConfig()->getMigrationPath() . DS . '_20150724233100_update_numbers_table.php'; + $migrationPaths = $migrations->getConfig()->getMigrationPaths(); + $migrationPath = array_pop($migrationPaths); + $origin = $migrationPath . DS . '20150724233100_update_numbers_table.php'; + $destination = $migrationPath . DS . '_20150724233100_update_numbers_table.php'; rename($origin, $destination); $params = [ @@ -218,9 +222,9 @@ protected function getCommandTester($params) $input = new ArrayInput($params, $this->command->getDefinition()); $this->command->setInput($input); $manager = new CakeManager($this->command->getConfig(), $input, $this->streamOutput); - $manager->getEnvironment('default')->getAdapter()->setConnection($this->Connection->driver()->connection()); + $manager->getEnvironment('default')->getAdapter()->setConnection($this->pdo); $this->command->setManager($manager); - $commandTester = new CommandTester($this->command); + $commandTester = new \Migrations\Test\CommandTester($this->command); return $commandTester; } @@ -242,16 +246,7 @@ protected function getMigrations() ->getManager($this->command->getConfig()) ->getEnvironment('default') ->getAdapter() - ->setConnection($this->Connection->driver()->connection()); - - $tables = (new Collection($this->Connection))->listTables(); - if (in_array('phinxlog', $tables)) { - $ormTable = TableRegistry::get('phinxlog', ['connection' => $this->Connection]); - $query = $this->Connection->driver()->schemaDialect()->truncateTableSql($ormTable->schema()); - foreach ($query as $stmt) { - $this->Connection->execute($stmt); - } - } + ->setConnection($this->pdo); return $migrations; } diff --git a/tests/TestCase/ConfigurationTraitTest.php b/tests/TestCase/ConfigurationTraitTest.php index 363e7183..2ec4f1bb 100644 --- a/tests/TestCase/ConfigurationTraitTest.php +++ b/tests/TestCase/ConfigurationTraitTest.php @@ -92,7 +92,8 @@ public function testGetConfig() $this->assertInstanceOf('Phinx\Config\Config', $config); $expected = ROOT . DS . 'config' . DS . 'Migrations'; - $this->assertEquals($expected, $config->getMigrationPath()); + $migrationPaths = $config->getMigrationPaths(); + $this->assertEquals($expected, array_pop($migrationPaths)); $this->assertEquals( 'phinxlog', @@ -199,7 +200,8 @@ public function testGetConfigWithConnectionName() $this->assertInstanceOf('Phinx\Config\Config', $config); $expected = ROOT . DS . 'config' . DS . 'Migrations'; - $this->assertEquals($expected, $config->getMigrationPath()); + $migrationPaths = $config->getMigrationPaths(); + $this->assertEquals($expected, array_pop($migrationPaths)); $this->assertEquals( 'phinxlog',