Skip to content

Commit

Permalink
Merge pull request #697 from cakephp/migrate-command
Browse files Browse the repository at this point in the history
Add new Migrate command
  • Loading branch information
markstory authored Mar 13, 2024
2 parents decadf2 + 005ca4e commit 5508794
Show file tree
Hide file tree
Showing 14 changed files with 582 additions and 16 deletions.
5 changes: 4 additions & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,19 @@

<!-- SQLite
<env name="DB" value="sqlite"/>
<env name="DB_URL" value="sqlite://127.0.0.1/cakephp_test"/>
<env name="DB_URL" value="sqlite://127.0.0.1/tests.sqlite"/>
<env name="DB_URL_SNAPSHOT" value="sqlite://127.0.0.1/snapshot-tests.sqlite"/>
-->
<!-- Postgres
<env name="DB" value="pgsql"/>
<env name="DB_URL" value="postgres://localhost/cake_test?timezone=UTC"/>
<env name="DB_URL_SNAPSHOT" value="postgres://localhost/cake_snapshot_test?timezone=UTC"/>
-->
<!-- Mysql
<env name="DB" value="mysql"/>
<env name="DB_URL" value="mysql://localhost/cake_test?timezone=UTC"/>
<env name="DB_URL_COMPARE" value="mysql://localhost/cake_comparison"/>
<env name="DB_URL_SNAPSHOT" value="mysql://localhost/cake_snapshot"/>
-->
</php>
</phpunit>
245 changes: 245 additions & 0 deletions src/Command/MigrateCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
<?php
declare(strict_types=1);

/**
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Migrations\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\Console\Exception\StopException;
use Cake\Core\Plugin;
use Cake\Datasource\ConnectionManager;
use Cake\Event\EventDispatcherTrait;
use Cake\Utility\Inflector;
use DateTime;
use Exception;
use Migrations\Config\Config;
use Migrations\Config\ConfigInterface;
use Migrations\Migration\Manager;
use Throwable;

/**
* Migrate command runs migrations
*/
class MigrateCommand extends Command
{
/**
* @use \Cake\Event\EventDispatcherTrait<\Migrations\Command\MigrateCommand>
*/
use EventDispatcherTrait;

/**
* The default name added to the application command list
*
* @return string
*/
public static function defaultName(): string
{
return 'migrations migrate';
}

/**
* Configure the option parser
*
* @param \Cake\Console\ConsoleOptionParser $parser The option parser to configure
* @return \Cake\Console\ConsoleOptionParser
*/
public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
{
$parser->setDescription([
'Apply migrations to a SQL datasource',
'',
'Will run all available migrations, optionally up to a specific version',
'',
'<info>migrations migrate --connection secondary</info>',
'<info>migrations migrate --connection secondary --target 003</info>',
])->addOption('plugin', [
'short' => 'p',
'help' => 'The plugin to run migrations for',
])->addOption('connection', [
'short' => 'c',
'help' => 'The datasource connection to use',
'default' => 'default',
])->addOption('source', [
'short' => 's',
'default' => ConfigInterface::DEFAULT_MIGRATION_FOLDER,
'help' => 'The folder where your migrations are',
])->addOption('target', [
'short' => 't',
'help' => 'The target version to migrate to.',
])->addOption('date', [
'short' => 'd',
'help' => 'The date to migrate to',
])->addOption('fake', [
'help' => "Mark any migrations selected as run, but don't actually execute them",
'boolean' => true,
])->addOption('no-lock', [
'help' => 'If present, no lock file will be generated after migrating',
'boolean' => true,
]);

return $parser;
}

/**
* Generate a configuration object for the migrations operation.
*
* @param \Cake\Console\Arguments $args The console arguments
* @return \Migrations\Config\Config The generated config instance.
*/
protected function getConfig(Arguments $args): Config
{
$folder = (string)$args->getOption('source');

// Get the filepath for migrations and seeds(not implemented yet)
$dir = ROOT . DS . 'config' . DS . $folder;
if (defined('CONFIG')) {
$dir = CONFIG . $folder;
}
$plugin = $args->getOption('plugin');
if ($plugin && is_string($plugin)) {
$dir = Plugin::path($plugin) . 'config' . DS . $folder;
}

// Get the phinxlog table name. Plugins have separate migration history.
// The names and separate table history is something we could change in the future.
$table = 'phinxlog';
if ($plugin && is_string($plugin)) {
$prefix = Inflector::underscore($plugin) . '_';
$prefix = str_replace(['\\', '/', '.'], '_', $prefix);
$table = $prefix . $table;
}
$templatePath = dirname(__DIR__) . DS . 'templates' . DS;
$connectionName = (string)$args->getOption('connection');

// TODO this all needs to go away. But first Environment and Manager need to work
// with Cake's ConnectionManager.
$connectionConfig = ConnectionManager::getConfig($connectionName);
if (!$connectionConfig) {
throw new StopException("Could not find connection `{$connectionName}`");
}

/** @var array<string, string> $connectionConfig */
$adapter = $connectionConfig['scheme'] ?? null;
$adapterConfig = [
'adapter' => $adapter,
'connection' => $connectionName,
'database' => $connectionConfig['database'],
'migration_table' => $table,
'dryrun' => $args->getOption('dry-run'),
];

$configData = [
'paths' => [
'migrations' => $dir,
],
'templates' => [
'file' => $templatePath . 'Phinx/create.php.template',
],
'migration_base_class' => 'Migrations\AbstractMigration',
'environment' => $adapterConfig,
// TODO do we want to support the DI container in migrations?
];

return new Config($configData);
}

/**
* Get the migration manager for the current CLI options and application configuration.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The command io.
* @return \Migrations\Migration\Manager
*/
protected function getManager(Arguments $args, ConsoleIo $io): Manager
{
$config = $this->getConfig($args);

return new Manager($config, $io);
}

/**
* Execute the command.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return int|null The exit code or null for success
*/
public function execute(Arguments $args, ConsoleIo $io): ?int
{
$event = $this->dispatchEvent('Migration.beforeMigrate');
if ($event->isStopped()) {
return $event->getResult() ? self::CODE_SUCCESS : self::CODE_ERROR;
}
$result = $this->executeMigrations($args, $io);
$this->dispatchEvent('Migration.afterMigrate');

return $result;
}

/**
* Execute migrations based on console inputs.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return int|null The exit code or null for success
*/
protected function executeMigrations(Arguments $args, ConsoleIo $io): ?int
{
$version = $args->getOption('target') !== null ? (int)$args->getOption('target') : null;
$date = $args->getOption('date');
$fake = (bool)$args->getOption('fake');

$manager = $this->getManager($args, $io);
$config = $manager->getConfig();

$versionOrder = $config->getVersionOrder();
$io->out('<info>using connection</info> ' . (string)$args->getOption('connection'));
$io->out('<info>using paths</info> ' . implode(', ', $config->getMigrationPaths()));
$io->out('<info>ordering by</info> ' . $versionOrder . ' time');

if ($fake) {
$io->out('<warning>warning</warning> performing fake migrations');
}

try {
// run the migrations
$start = microtime(true);
if ($date !== null) {
$manager->migrateToDateTime(new DateTime((string)$date), $fake);
} else {
$manager->migrate($version, $fake);
}
$end = microtime(true);
} catch (Exception $e) {
$io->err('<error>' . $e->getMessage() . '</error>');
$io->out($e->getTraceAsString(), 1, ConsoleIo::VERBOSE);

return self::CODE_ERROR;
} catch (Throwable $e) {
$io->err('<error>' . $e->getMessage() . '</error>');
$io->out($e->getTraceAsString(), 1, ConsoleIo::VERBOSE);

return self::CODE_ERROR;
}

$io->out('');
$io->out('<comment>All Done. Took ' . sprintf('%.4fs', $end - $start) . '</comment>');

// Run dump command to generate lock file
// TODO(mark) port in logic from src/Command/MigrationsCommand.php : 142:164

return self::CODE_SUCCESS;
}
}
3 changes: 2 additions & 1 deletion src/Command/StatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Cake\Datasource\ConnectionManager;
use Cake\Utility\Inflector;
use Migrations\Config\Config;
use Migrations\Config\ConfigInterface;
use Migrations\Migration\Manager;

/**
Expand Down Expand Up @@ -77,7 +78,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
])->addOption('source', [
'short' => 's',
'help' => 'The folder under src/Config that migrations are in',
'default' => 'Migrations',
'default' => ConfigInterface::DEFAULT_MIGRATION_FOLDER,
])->addOption('format', [
'short' => 'f',
'help' => 'The output format: text or json. Defaults to text.',
Expand Down
2 changes: 2 additions & 0 deletions src/Config/ConfigInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
interface ConfigInterface extends ArrayAccess
{
public const DEFAULT_MIGRATION_FOLDER = 'Migrations';

/**
* Returns the configuration for the current environment.
*
Expand Down
10 changes: 8 additions & 2 deletions src/Db/Adapter/AdapterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
namespace Migrations\Db\Adapter;

use Cake\Console\ConsoleIo;
use Cake\Database\Connection;
use Cake\Database\Query;
use Cake\Database\Query\DeleteQuery;
use Cake\Database\Query\InsertQuery;
Expand All @@ -21,8 +22,6 @@

/**
* Adapter Interface.
*
* @method \PDO getConnection()
*/
interface AdapterInterface
{
Expand Down Expand Up @@ -520,4 +519,11 @@ public function setIo(ConsoleIo $io);
* @return \Cake\Console\ConsoleIo $io The io instance to use
*/
public function getIo(): ?ConsoleIo;

/**
* Get the Connection for this adapter.
*
* @return \Cake\Database\Connection The connection
*/
public function getConnection(): Connection;
}
6 changes: 3 additions & 3 deletions src/Db/Adapter/AdapterWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
namespace Migrations\Db\Adapter;

use Cake\Console\ConsoleIo;
use Cake\Database\Connection;
use Cake\Database\Query;
use Cake\Database\Query\DeleteQuery;
use Cake\Database\Query\InsertQuery;
Expand All @@ -17,7 +18,6 @@
use Migrations\Db\Literal;
use Migrations\Db\Table\Column;
use Migrations\Db\Table\Table;
use PDO;
use Phinx\Migration\MigrationInterface;

/**
Expand Down Expand Up @@ -430,9 +430,9 @@ public function castToBool($value): mixed
}

/**
* @return \PDO
* @return \Cake\Database\Connection
*/
public function getConnection(): PDO
public function getConnection(): Connection
{
return $this->getAdapter()->getConnection();
}
Expand Down
14 changes: 11 additions & 3 deletions src/Db/Adapter/PhinxAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace Migrations\Db\Adapter;

use Cake\Database\Connection;
use Cake\Database\Query;
use Cake\Database\Query\DeleteQuery;
use Cake\Database\Query\InsertQuery;
Expand All @@ -32,7 +33,6 @@
use Migrations\Db\Table\ForeignKey;
use Migrations\Db\Table\Index;
use Migrations\Db\Table\Table;
use PDO;
use Phinx\Db\Action\Action as PhinxAction;
use Phinx\Db\Action\AddColumn as PhinxAddColumn;
use Phinx\Db\Action\AddForeignKey as PhinxAddForeignKey;
Expand Down Expand Up @@ -747,9 +747,9 @@ public function castToBool($value): mixed
}

/**
* @return \PDO
* @return \Cake\Database\Connection
*/
public function getConnection(): PDO
public function getConnection(): Connection
{
return $this->adapter->getConnection();
}
Expand Down Expand Up @@ -812,4 +812,12 @@ public function getDeleteBuilder(): DeleteQuery
{
return $this->adapter->getDeleteBuilder();
}

/**
* @inheritDoc
*/
public function getCakeConnection(): Connection
{
return $this->adapter->getConnection();
}
}
2 changes: 1 addition & 1 deletion src/Migration/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@ function ($phpFile) {
));
}

$io->out("Running <info>$class</info>.");
$io->verbose("Constructing <info>$class</info>.");

$input = new ArgvInput();
$output = new OutputAdapter($io);
Expand Down
Loading

0 comments on commit 5508794

Please sign in to comment.