Skip to content

Commit

Permalink
Merge pull request #97 from HavokInspiration/app-migrations
Browse files Browse the repository at this point in the history
Implements a way to run Migrations in a non-shell environment
  • Loading branch information
lorenzo committed Jul 31, 2015
2 parents 7ef12e5 + f034976 commit 2c0bec3
Show file tree
Hide file tree
Showing 8 changed files with 777 additions and 58 deletions.
136 changes: 136 additions & 0 deletions src/CakeManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php
/**
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Migrations;

use Phinx\Migration\Manager;

/**
* Overrides Phinx Manager class in order to provide an interface
* for running migrations within an app
*/
class CakeManager extends Manager
{

/**
* Reset the migrations stored in the object
*
* @return void
*/
public function resetMigrations()
{
$this->migrations = null;
}

/**
* Prints the specified environment's migration status.
*
* @param string $environment
* @param null|string $format
* @return array Array of migrations
*/
public function printStatus($environment, $format = null)
{
$migrations = [];
if (count($this->getMigrations())) {
$env = $this->getEnvironment($environment);
$versions = $env->getVersions();

foreach ($this->getMigrations() as $migration) {
if (in_array($migration->getVersion(), $versions)) {
$status = 'up';
unset($versions[array_search($migration->getVersion(), $versions)]);
} else {
$status = 'down';
}

$migrations[] = [
'status' => $status,
'id' => $migration->getVersion(),
'name' => $migration->getName()
];
}

foreach ($versions as $missing) {
$migrations[] = ['status' => 'up', 'id' => $missing, 'name' => false];
}
}

if ($format === 'json') {
$migrations = json_encode($migrations);
}

return $migrations;
}

/**
* Checks if the migration with version number $version as already been mark migrated
*
* @param int|string $version Version number of the migration to check
* @return bool
*/
public function isMigrated($version)
{
$adapter = $this->getEnvironment('default')->getAdapter();
$versions = array_flip($adapter->getVersions());

return isset($versions[$version]);
}

/**
* Marks migration with version number $version migrated
*
* @param int|string $version Version number of the migration to check
* @param string $path Path where the migration file is located
* @return bool True if success
*/
public function markMigrated($version, $path)
{
$adapter = $this->getEnvironment('default')->getAdapter();

$migrationFile = glob($path . DS . $version . '*');

if (empty($migrationFile)) {
throw new \RuntimeException(
sprintf('A migration file matching version number `%s` could not be found', $version)
);
}

$migrationFile = $migrationFile[0];
$className = $this->getMigrationClassName($migrationFile);
require_once $migrationFile;
$Migration = new $className($version);

$time = date('Y-m-d H:i:s', time());

$adapter->migrated($Migration, 'up', $time, $time);
return true;
}

/**
* Resolves a migration class name based on $path
*
* @param string $path Path to the migration file of which we want the class name
* @return string Migration class name
*/
protected function getMigrationClassName($path)
{
$class = preg_replace('/^[0-9]+_/', '', basename($path));
$class = str_replace('_', ' ', $class);
$class = ucwords($class);
$class = str_replace(' ', '', $class);
if (strpos($class, '.') !== false) {
$class = substr($class, 0, strpos($class, '.'));
}

return $class;
}
}
51 changes: 8 additions & 43 deletions src/Command/MarkMigrated.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,19 @@ protected function configure()
/**
* Mark a migration migrated
*
* @param Symfony\Component\Console\Input\Inputnterface $input the input object
* @param Symfony\Component\Console\Input\OutputInterface $output the output object
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
* @param \Symfony\Component\Console\Output\OutputInterface $output the output object
* @return void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setInput($input);
$this->bootstrap($input, $output);
$adapter = $this->getManager()->getEnvironment('default')->getAdapter();

$path = $this->getConfig()->getMigrationPath();
$version = $input->getArgument('version');

$versions = array_flip($adapter->getVersions());
if (isset($versions[$version])) {
if ($this->getManager()->isMigrated($version)) {
$output->writeln(
sprintf(
'<info>The migration with version number `%s` has already been marked as migrated.</info>',
Expand All @@ -69,44 +67,11 @@ protected function execute(InputInterface $input, OutputInterface $output)
return;
}

$migrationFile = glob($path . DS . $version . '*');
if (!empty($migrationFile)) {
$migrationFile = $migrationFile[0];
$className = $this->getMigrationClassName($migrationFile);
require_once $migrationFile;
$Migration = new $className($version);

$time = date('Y-m-d H:i:s', time());

try {
$adapter->migrated($Migration, 'up', $time, $time);
$output->writeln('<info>Migration successfully marked migrated !</info>');
} catch (Exception $e) {
$output->writeln(sprintf('<error>An error occurred : %s</error>', $e->getMessage()));
}
} else {
$output->writeln(
sprintf('<error>A migration file matching version number `%s` could not be found</error>', $version)
);
}
}

/**
* Resolves a migration class name based on $path
*
* @param string $path Path to the migration file of which we want the class name
* @return string Migration class name
*/
protected function getMigrationClassName($path)
{
$class = preg_replace('/^[0-9]+_/', '', basename($path));
$class = str_replace('_', ' ', $class);
$class = ucwords($class);
$class = str_replace(' ', '', $class);
if (strpos($class, '.') !== false) {
$class = substr($class, 0, strpos($class, '.'));
try {
$this->getManager()->markMigrated($version, $path);
$output->writeln('<info>Migration successfully marked migrated !</info>');
} catch (\Exception $e) {
$output->writeln(sprintf('<error>An error occurred : %s</error>', $e->getMessage()));
}

return $class;
}
}
77 changes: 77 additions & 0 deletions src/Command/Status.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Migrations\ConfigurationTrait;
use Phinx\Console\Command\Status as StatusCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Status extends StatusCommand
{
Expand All @@ -33,4 +35,79 @@ protected function configure()
->addOption('--connection', '-c', InputArgument::OPTIONAL, 'The datasource connection to use')
->addOption('--source', '-s', InputArgument::OPTIONAL, 'The folder where migrations are in');
}

/**
* Show the migration status.
*
* @param InputInterface $input
* @param OutputInterface $output
* @return void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->beforeExecute($input, $output);
$this->bootstrap($input, $output);

$environment = $input->getOption('environment');
$format = $input->getOption('format');

if (null === $environment) {
$environment = $this->getManager()->getConfig()->getDefaultEnvironment();
$output->writeln('<comment>warning</comment> no environment specified, defaulting to: ' . $environment);
} else {
$output->writeln('<info>using environment</info> ' . $environment);
}
if (null !== $format) {
$output->writeln('<info>using format</info> ' . $format);
}

// print the status
$migrations = $this->getManager()->printStatus($environment, $format);

switch ($format) {
case 'json':
$output->writeln($migrations);
break;
default:
$this->display($migrations);
break;
}
}

/**
* Will output the status of the migrations
*
* @param array $migrations
* @return void
*/
protected function display(array $migrations)
{
$output = $this->getManager()->getOutput();

if (!empty($migrations)) {
$output->writeln('');
$output->writeln(' Status Migration ID Migration Name ');
$output->writeln('-----------------------------------------');

foreach ($migrations as $migration) {
$status = $migration['status'] === 'up' ? ' <info>up</info> ' : ' <error>down</error> ';
$name = $migration['name'] !== false ?
' <comment>' . $migration['name'] . ' </comment>' :
' <error>** MISSING **</error>';

$output->writeln(
$status
. sprintf(' %14.0f ', $migration['id'])
. $name
);
}

$output->writeln('');
} else {
$msg = 'There are no available migrations. Try creating one using the <info>create</info> command.';
$output->writeln('');
$output->writeln($msg);
$output->writeln('');
}
}
}
Loading

0 comments on commit 2c0bec3

Please sign in to comment.