Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new Migrate command #697

Merged
merged 10 commits into from
Mar 13, 2024
Merged
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}`");

Check warning on line 130 in src/Command/MigrateCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/MigrateCommand.php#L130

Added line #L130 was not covered by tests
}

/** @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;

Check warning on line 183 in src/Command/MigrateCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/MigrateCommand.php#L183

Added line #L183 was not covered by tests
}
$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);

Check warning on line 227 in src/Command/MigrateCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/MigrateCommand.php#L225-L227

Added lines #L225 - L227 were not covered by tests

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

Check warning on line 232 in src/Command/MigrateCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/MigrateCommand.php#L229-L232

Added lines #L229 - L232 were not covered by tests

return self::CODE_ERROR;

Check warning on line 234 in src/Command/MigrateCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/MigrateCommand.php#L234

Added line #L234 was not covered by tests
}

$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 @@
}

/**
* @return \PDO
* @return \Cake\Database\Connection
*/
public function getConnection(): PDO
public function getConnection(): Connection

Check warning on line 752 in src/Db/Adapter/PhinxAdapter.php

View check run for this annotation

Codecov / codecov/patch

src/Db/Adapter/PhinxAdapter.php#L752

Added line #L752 was not covered by tests
{
return $this->adapter->getConnection();
}
Expand Down Expand Up @@ -812,4 +812,12 @@
{
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
Loading