diff --git a/src/CakeAdapter.php b/src/CakeAdapter.php
index 56f9c173..40ee2776 100644
--- a/src/CakeAdapter.php
+++ b/src/CakeAdapter.php
@@ -12,6 +12,7 @@
namespace Migrations;
use Cake\Database\Connection;
+use PDO;
use Phinx\Db\Adapter\AdapterInterface;
use Phinx\Db\Table;
use Phinx\Db\Table\Column;
@@ -52,6 +53,10 @@ public function __construct(AdapterInterface $adapter, Connection $connection)
$this->adapter = $adapter;
$this->connection = $connection;
$pdo = $adapter->getConnection();
+
+ if ($pdo->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_EXCEPTION) {
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ }
$connection->driver()->connection($pdo);
}
diff --git a/src/CakeManager.php b/src/CakeManager.php
index d61fe2d1..991156c3 100644
--- a/src/CakeManager.php
+++ b/src/CakeManager.php
@@ -186,6 +186,95 @@ public function markMigrated($version, $path)
return true;
}
+ /**
+ * Decides which versions it should mark as migrated
+ *
+ * @param \Symfony\Component\Console\Input\InputInterface $input Input interface from which argument and options
+ * will be extracted to determine which versions to be marked as migrated
+ * @return array Array of versions that should be marked as migrated
+ * @throws \InvalidArgumentException If the `--exclude` or `--only` options are used without `--target`
+ * or version not found
+ */
+ public function getVersionsToMark($input)
+ {
+ $migrations = $this->getMigrations();
+ $versions = array_keys($migrations);
+
+ $versionArg = $input->getArgument('version');
+ $targetArg = $input->getOption('target');
+ $hasAllVersion = in_array($versionArg, ['all', '*']);
+ if ((empty($versionArg) && empty($targetArg)) || $hasAllVersion) {
+ return $versions;
+ }
+
+ $version = $targetArg ?: $versionArg;
+
+ if ($input->getOption('only') || !empty($versionArg)) {
+ if (!in_array($version, $versions)) {
+ throw new \InvalidArgumentException("Migration `$version` was not found !");
+ }
+
+ return [$version];
+ }
+
+ $lengthIncrease = $input->getOption('exclude') ? 0 : 1;
+ $index = array_search($version, $versions);
+
+ if ($index === false) {
+ throw new \InvalidArgumentException("Migration `$version` was not found !");
+ }
+
+ return array_slice($versions, 0, $index + $lengthIncrease);
+ }
+
+ /**
+ * Mark all migrations in $versions array found in $path as migrated
+ *
+ * It will start a transaction and rollback in case one of the operation raises an exception
+ *
+ * @param string $path Path where to look for migrations
+ * @param array $versions Versions which should be marked
+ * @param \Symfony\Component\Console\Output\OutputInterface $output OutputInterface used to store
+ * the command output
+ * @return void
+ */
+ public function markVersionsAsMigrated($path, $versions, $output)
+ {
+ $adapter = $this->getEnvironment('default')->getAdapter();
+
+ if (empty($versions)) {
+ $output->writeln('No migrations were found. Nothing to mark as migrated.');
+ return;
+ }
+
+ $adapter->beginTransaction();
+ foreach ($versions as $version) {
+ if ($this->isMigrated($version)) {
+ $output->writeln(sprintf('Skipping migration `%s` (already migrated).', $version));
+ continue;
+ }
+
+ try {
+ $this->markMigrated($version, $path);
+ $output->writeln(
+ sprintf('Migration `%s` successfully marked migrated !', $version)
+ );
+ } catch (\Exception $e) {
+ $adapter->rollbackTransaction();
+ $output->writeln(
+ sprintf(
+ 'An error occurred while marking migration `%s` as migrated : %s',
+ $version,
+ $e->getMessage()
+ )
+ );
+ $output->writeln('All marked migrations during this process were unmarked.');
+ return;
+ }
+ }
+ $adapter->commitTransaction();
+ }
+
/**
* Resolves a migration class name based on $path
*
diff --git a/src/Command/MarkMigrated.php b/src/Command/MarkMigrated.php
index 5d7e54d2..ebb9ca3e 100644
--- a/src/Command/MarkMigrated.php
+++ b/src/Command/MarkMigrated.php
@@ -122,122 +122,13 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
try {
- $versions = $this->getVersionsToMark();
+ $versions = $this->getManager()->getVersionsToMark($input);
} catch (InvalidArgumentException $e) {
$output->writeln(sprintf("%s", $e->getMessage()));
return;
}
- $this->markVersionsAsMigrated($path, $versions);
- }
-
- /**
- * Mark all migrations in $versions array found in $path as migrated
- *
- * It will start a transaction and rollback in case one of the operation raises an exception
- *
- * @param string $path Path where to look for migrations
- * @param array $versions Versions which should be marked
- * @return void
- */
- protected function markVersionsAsMigrated($path, $versions)
- {
- $manager = $this->getManager();
- $adapter = $manager->getEnvironment('default')->getAdapter();
- $output = $this->output();
-
- if (empty($versions)) {
- $output->writeln('No migrations were found. Nothing to mark as migrated.');
- return;
- }
-
- $adapter->beginTransaction();
- foreach ($versions as $version) {
- if ($manager->isMigrated($version)) {
- $output->writeln(sprintf('Skipping migration `%s` (already migrated).', $version));
- continue;
- }
-
- try {
- $this->getManager()->markMigrated($version, $path);
- $output->writeln(
- sprintf('Migration `%s` successfully marked migrated !', $version)
- );
- } catch (\Exception $e) {
- $adapter->rollbackTransaction();
- $output->writeln(
- sprintf(
- 'An error occurred while marking migration `%s` as migrated : %s',
- $version,
- $e->getMessage()
- )
- );
- $output->writeln('All marked migrations during this process were unmarked.');
- return;
- }
- }
- $adapter->commitTransaction();
- }
-
- /**
- * Decides which versions it should mark as migrated
- *
- * @return array Array of versions that should be marked as migrated
- * @throws \InvalidArgumentException If the `--exclude` or `--only` options are used without `--target`
- * or version not found
- */
- protected function getVersionsToMark()
- {
- $migrations = $this->getManager()->getMigrations();
- $versions = array_keys($migrations);
-
- if ($this->isAllVersion()) {
- return $versions;
- }
-
- $version = $this->getTarget();
-
- if ($this->isOnly()) {
- if (!in_array($version, $versions)) {
- throw new InvalidArgumentException("Migration `$version` was not found !");
- }
-
- return [$version];
- }
-
- $lengthIncrease = $this->hasExclude() ? 0 : 1;
- $index = array_search($version, $versions);
-
- if ($index === false) {
- throw new \InvalidArgumentException("Migration `$version` was not found !");
- }
-
- return array_slice($versions, 0, $index + $lengthIncrease);
- }
-
- /**
- * Returns the target version from `--target` or from the deprecated version argument
- *
- * @return string Version found as target
- */
- protected function getTarget()
- {
- $target = $this->input->getOption('target');
- return $target ? $target : $this->input->getArgument('version');
- }
-
- /**
- * Checks if the $version is for all migrations
- *
- * @return bool Returns true if it should try to mark all versions
- */
- protected function isAllVersion()
- {
- if ($this->isUsingDeprecatedVersion()) {
- return false;
- }
-
- return $this->isUsingDeprecatedAll() || $this->input->getOption('target') === null;
+ $this->getManager()->markVersionsAsMigrated($path, $versions, $output);
}
/**
@@ -251,17 +142,6 @@ protected function isUsingDeprecatedAll()
return $version === 'all' || $version === '*';
}
- /**
- * Checks if the input has the `--only` option or it is using the deprecated version argument
- *
- * @return bool Returns true when it is trying to mark only one migration
- */
- protected function isOnly()
- {
-
- return $this->hasOnly() || $this->isUsingDeprecatedVersion();
- }
-
/**
* Checks if the input has the `--exclude` option
*
diff --git a/src/Migrations.php b/src/Migrations.php
index a4970030..b131880e 100644
--- a/src/Migrations.php
+++ b/src/Migrations.php
@@ -193,20 +193,28 @@ public function rollback($options = [])
*
* @return bool Success
*/
- public function markMigrated($version, $options = [])
+ public function markMigrated($version = null, $options = [])
{
$this->setCommand('mark_migrated');
- $input = $this->getInput('MarkMigrated', ['version' => $version], $options);
- $params = [$version];
- $isMigrated = $this->run('isMigrated', $params, $input);
- if ($isMigrated) {
- return true;
+ if (isset($options['target']) &&
+ isset($options['exclude']) &&
+ isset($options['only'])
+ ) {
+ $exceptionMessage = 'You should use `exclude` OR `only` (not both) along with a `target` argument';
+ throw new \InvalidArgumentException($exceptionMessage);
}
- $params[] = $this->getConfig()->getMigrationPath();
+ $input = $this->getInput('MarkMigrated', ['version' => $version], $options);
+ $this->setInput($input);
+
+ $params = [
+ $this->getConfig()->getMigrationPath(),
+ $this->getManager()->getVersionsToMark($input),
+ $this->output
+ ];
- $this->run('markMigrated', $params, $input);
+ $this->run('markVersionsAsMigrated', $params, $input);
return true;
}
diff --git a/tests/TestCase/MigrationsTest.php b/tests/TestCase/MigrationsTest.php
index 91f2de6b..8fb41d70 100644
--- a/tests/TestCase/MigrationsTest.php
+++ b/tests/TestCase/MigrationsTest.php
@@ -188,38 +188,240 @@ public function testMigrateAndRollback()
}
/**
- * Tests that migrate returns false in case of error
- * and can return a error message
+ * Tests calling Migrations::markMigrated without params marks everything
+ * as migrated
*
- * @expectedException Exception
+ * @return void
*/
- public function testMigrateErrors()
+ public function testMarkMigratedAll()
{
- $this->migrations->markMigrated(20150704160200);
- $this->migrations->migrate();
+ $markMigrated = $this->migrations->markMigrated();
+ $this->assertTrue($markMigrated);
+ $status = $this->migrations->status();
+ $expected = [
+ [
+ 'status' => 'up',
+ 'id' => '20150704160200',
+ 'name' => 'CreateNumbersTable'
+ ],
+ [
+ 'status' => 'up',
+ 'id' => '20150724233100',
+ 'name' => 'UpdateNumbersTable'
+ ],
+ [
+ 'status' => 'up',
+ 'id' => '20150826191400',
+ 'name' => 'CreateLettersTable'
+ ]
+ ];
+ $this->assertEquals($expected, $status);
}
/**
- * Tests that rollback returns false in case of error
- * and can return a error message
+ * Tests calling Migrations::markMigrated with the argument $version as the
+ * string 'all' marks everything
+ * as migrated
*
- * @expectedException Exception
+ * @return void
*/
- public function testRollbackErrors()
+ public function testMarkMigratedAllAsVersion()
{
- $this->migrations->markMigrated('all');
- $this->migrations->rollback();
+ $markMigrated = $this->migrations->markMigrated('all');
+ $this->assertTrue($markMigrated);
+ $status = $this->migrations->status();
+ $expected = [
+ [
+ 'status' => 'up',
+ 'id' => '20150704160200',
+ 'name' => 'CreateNumbersTable'
+ ],
+ [
+ 'status' => 'up',
+ 'id' => '20150724233100',
+ 'name' => 'UpdateNumbersTable'
+ ],
+ [
+ 'status' => 'up',
+ 'id' => '20150826191400',
+ 'name' => 'CreateLettersTable'
+ ]
+ ];
+ $this->assertEquals($expected, $status);
}
/**
- * Tests that marking migrated a non-existant migrations returns an error
- * and can return a error message
+ * Tests calling Migrations::markMigrated with the target option will mark
+ * only up to that one
*
- * @expectedException Exception
+ * @return void
*/
- public function testMarkMigratedErrors()
+ public function testMarkMigratedTarget()
{
- $this->migrations->markMigrated(20150704000000);
+ $markMigrated = $this->migrations->markMigrated(null, ['target' => '20150704160200']);
+ $this->assertTrue($markMigrated);
+ $status = $this->migrations->status();
+ $expected = [
+ [
+ 'status' => 'up',
+ 'id' => '20150704160200',
+ 'name' => 'CreateNumbersTable'
+ ],
+ [
+ 'status' => 'down',
+ 'id' => '20150724233100',
+ 'name' => 'UpdateNumbersTable'
+ ],
+ [
+ 'status' => 'down',
+ 'id' => '20150826191400',
+ 'name' => 'CreateLettersTable'
+ ]
+ ];
+ $this->assertEquals($expected, $status);
+
+ $markMigrated = $this->migrations->markMigrated(null, ['target' => '20150826191400']);
+ $this->assertTrue($markMigrated);
+ $status = $this->migrations->status();
+ $expected[1]['status'] = $expected[2]['status'] = 'up';
+ $this->assertEquals($expected, $status);
+ }
+
+ /**
+ * Tests calling Migrations::markMigrated with the target option set to a
+ * non-existent target will throw an exception
+ *
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Migration `20150704160610` was not found !
+ * @return void
+ */
+ public function testMarkMigratedTargetError()
+ {
+ $this->migrations->markMigrated(null, ['target' => '20150704160610']);
+ }
+
+ /**
+ * Tests calling Migrations::markMigrated with the target option with the exclude
+ * option will mark only up to that one, excluding it
+ *
+ * @return void
+ */
+ public function testMarkMigratedTargetExclude()
+ {
+ $markMigrated = $this->migrations->markMigrated(null, ['target' => '20150704160200', 'exclude' => true]);
+ $this->assertTrue($markMigrated);
+ $status = $this->migrations->status();
+ $expected = [
+ [
+ 'status' => 'down',
+ 'id' => '20150704160200',
+ 'name' => 'CreateNumbersTable'
+ ],
+ [
+ 'status' => 'down',
+ 'id' => '20150724233100',
+ 'name' => 'UpdateNumbersTable'
+ ],
+ [
+ 'status' => 'down',
+ 'id' => '20150826191400',
+ 'name' => 'CreateLettersTable'
+ ]
+ ];
+ $this->assertEquals($expected, $status);
+
+ $markMigrated = $this->migrations->markMigrated(null, ['target' => '20150826191400', 'exclude' => true]);
+ $this->assertTrue($markMigrated);
+ $status = $this->migrations->status();
+ $expected[0]['status'] = $expected[1]['status'] = 'up';
+ $this->assertEquals($expected, $status);
+ }
+
+ /**
+ * Tests calling Migrations::markMigrated with the target option with the only
+ * option will mark only that specific migrations
+ *
+ * @return void
+ */
+ public function testMarkMigratedTargetOnly()
+ {
+ $markMigrated = $this->migrations->markMigrated(null, ['target' => '20150724233100', 'only' => true]);
+ $this->assertTrue($markMigrated);
+ $status = $this->migrations->status();
+ $expected = [
+ [
+ 'status' => 'down',
+ 'id' => '20150704160200',
+ 'name' => 'CreateNumbersTable'
+ ],
+ [
+ 'status' => 'up',
+ 'id' => '20150724233100',
+ 'name' => 'UpdateNumbersTable'
+ ],
+ [
+ 'status' => 'down',
+ 'id' => '20150826191400',
+ 'name' => 'CreateLettersTable'
+ ]
+ ];
+ $this->assertEquals($expected, $status);
+
+ $markMigrated = $this->migrations->markMigrated(null, ['target' => '20150826191400', 'only' => true]);
+ $this->assertTrue($markMigrated);
+ $status = $this->migrations->status();
+ $expected[2]['status'] = 'up';
+ $this->assertEquals($expected, $status);
+ }
+
+ /**
+ * Tests calling Migrations::markMigrated with the target option, the only option
+ * and the exclude option will throw an exception
+ *
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage You should use `exclude` OR `only` (not both) along with a `target` argument
+ * @return void
+ */
+ public function testMarkMigratedTargetExcludeOnly()
+ {
+ $this->migrations->markMigrated(null, ['target' => '20150724233100', 'only' => true, 'exclude' => true]);
+ }
+
+ /**
+ * Tests calling Migrations::markMigrated with the target option with the exclude
+ * option will mark only up to that one, excluding it
+ *
+ * @return void
+ */
+ public function testMarkMigratedVersion()
+ {
+ $markMigrated = $this->migrations->markMigrated(20150704160200);
+ $this->assertTrue($markMigrated);
+ $status = $this->migrations->status();
+ $expected = [
+ [
+ 'status' => 'up',
+ 'id' => '20150704160200',
+ 'name' => 'CreateNumbersTable'
+ ],
+ [
+ 'status' => 'down',
+ 'id' => '20150724233100',
+ 'name' => 'UpdateNumbersTable'
+ ],
+ [
+ 'status' => 'down',
+ 'id' => '20150826191400',
+ 'name' => 'CreateLettersTable'
+ ]
+ ];
+ $this->assertEquals($expected, $status);
+
+ $markMigrated = $this->migrations->markMigrated(20150826191400);
+ $this->assertTrue($markMigrated);
+ $status = $this->migrations->status();
+ $expected[2]['status'] = 'up';
+ $this->assertEquals($expected, $status);
}
/**
@@ -580,10 +782,13 @@ public function testMigrateSnapshots($basePath, $files)
foreach ($files as $file) {
list($filename, $timestamp) = $file;
$copiedFileName = $timestamp . '_' . $filename . '.php';
- copy(
- $basePath . $filename . '.php',
- $destination . $copiedFileName
- );
+
+ if (!file_exists($destination . $copiedFileName)) {
+ copy(
+ $basePath . $filename . '.php',
+ $destination . $copiedFileName
+ );
+ }
$result = $this->migrations->migrate(['source' => 'SnapshotTests']);
$this->assertTrue($result);
@@ -593,6 +798,39 @@ public function testMigrateSnapshots($basePath, $files)
}
}
+ /**
+ * Tests that migrating in case of error throws an exception
+ *
+ * @expectedException \Exception
+ */
+ public function testMigrateErrors()
+ {
+ $this->migrations->markMigrated(20150704160200);
+ $this->migrations->migrate();
+ }
+
+ /**
+ * Tests that rolling back in case of error throws an exception
+ *
+ * @expectedException \Exception
+ */
+ public function testRollbackErrors()
+ {
+ $this->migrations->markMigrated('all');
+ $this->migrations->rollback();
+ }
+
+ /**
+ * Tests that marking migrated a non-existant migrations returns an error
+ * and can return a error message
+ *
+ * @expectedException \Exception
+ */
+ public function testMarkMigratedErrors()
+ {
+ $this->migrations->markMigrated(20150704000000);
+ }
+
/**
* provides the path to the baked migrations
*