<?php
namespace Phinx\Migration;
use Phinx\Db\Adapter\AdapterFactory;
use Phinx\Db\Adapter\AdapterInterface;
use Phinx\Db\Adapter\ProxyAdapter;
use think\console\Output;
use Phinx\Config;
use Phinx\Migration\Manager\Environment;
use Phinx\Seed\AbstractSeed;
use Phinx\Seed\SeedInterface;
use Phinx\Util;
class Manager
{
protected $config;
protected $output;
protected $migrations;
protected $seeds;
const EXIT_STATUS_DOWN = 1;
const EXIT_STATUS_MISSING = 2;
protected $currentVersion;
protected $adapter;
public function __construct(Config $config, Output $output)
{
$this->setConfig($config);
$this->setOutput($output);
}
public function printStatus($format = null)
{
$output = $this->getOutput();
$migrations = array();
$hasDownMigration = false;
$hasMissingMigration = false;
if (count($this->getMigrations())) {
$output->writeln('');
$output->writeln(' Status Migration ID Started Finished Migration Name ');
$output->writeln('----------------------------------------------------------------------------------');
$versions = $this->getVersionLog();
$maxNameLength = $versions ? max(array_map(function ($version) {
return strlen($version['migration_name']);
}, $versions)) : 0;
foreach ($this->getMigrations() as $migration) {
$version = array_key_exists($migration->getVersion(), $versions) ? $versions[$migration->getVersion()] : false;
if ($version) {
$status = ' <info>up</info> ';
} else {
$hasDownMigration = true;
$status = ' <error>down</error> ';
}
$maxNameLength = max($maxNameLength, strlen($migration->getName()));
$output->writeln(sprintf(
'%s %14.0f %19s %19s <comment>%s</comment>',
$status, $migration->getVersion(), $version['start_time'], $version['end_time'], $migration->getName()
));
$migrations[] = array('migration_status' => trim(strip_tags($status)), 'migration_id' => sprintf('%14.0f', $migration->getVersion()), 'migration_name' => $migration->getName());
unset($versions[$migration->getVersion()]);
}
if (count($versions)) {
$hasMissingMigration = true;
foreach ($versions as $missing => $version) {
$output->writeln(sprintf(
' <error>up</error> %14.0f %19s %19s <comment>%s</comment> <error>** MISSING **</error>',
$missing, $version['start_time'], $version['end_time'], str_pad($version['migration_name'], $maxNameLength, ' ')
));
}
}
} else {
$output->writeln('');
$output->writeln('There are no available migrations. Try creating one using the <info>create</info> command.');
}
$output->writeln('');
if ($format !== null) {
switch ($format) {
case 'json':
$output->writeln(json_encode(
array(
'pending_count' => count($this->getMigrations()),
'migrations' => $migrations
)
));
break;
default:
$output->writeln('<info>Unsupported format: ' . $format . '</info>');
}
}
if ($hasMissingMigration) {
return self::EXIT_STATUS_MISSING;
} else if ($hasDownMigration) {
return self::EXIT_STATUS_DOWN;
} else {
return 0;
}
}
public function migrateToDateTime(\DateTime $dateTime)
{
$versions = array_keys($this->getMigrations());
$dateString = $dateTime->format('YmdHis');
$outstandingMigrations = array_filter($versions, function ($version) use ($dateString) {
return $version <= $dateString;
});
if (count($outstandingMigrations) > 0) {
$migration = max($outstandingMigrations);
$this->getOutput()->writeln('Migrating to version ' . $migration);
$this->migrate($migration);
}
}
public function rollbackToDateTime(\DateTime $dateTime)
{
$versions = $this->getVersions();
$dateString = $dateTime->format('YmdHis');
sort($versions);
$earlierVersion = null;
$availableMigrations = array_filter($versions, function ($version) use ($dateString, &$earlierVersion) {
if ($version <= $dateString) {
$earlierVersion = $version;
}
return $version >= $dateString;
});
if (count($availableMigrations) > 0) {
if (is_null($earlierVersion)) {
$this->getOutput()->writeln('Rolling back all migrations');
$migration = 0;
} else {
$this->getOutput()->writeln('Rolling back to version ' . $earlierVersion);
$migration = $earlierVersion;
}
$this->rollback($migration);
}
}
public function migrate($version = null)
{
$migrations = $this->getMigrations();
$versions = $this->getVersions();
$current = $this->getCurrentVersion();
if (empty($versions) && empty($migrations)) {
return;
}
if (null === $version) {
$version = max(array_merge($versions, array_keys($migrations)));
} else {
if (0 != $version && !isset($migrations[$version])) {
$this->output->writeln(sprintf(
'<comment>warning</comment> %s is not a valid version',
$version
));
return;
}
}
$direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN;
if ($direction === MigrationInterface::DOWN) {
krsort($migrations);
foreach ($migrations as $migration) {
if ($migration->getVersion() <= $version) {
break;
}
if (in_array($migration->getVersion(), $versions)) {
$this->executeMigration($migration, MigrationInterface::DOWN);
}
}
}
ksort($migrations);
foreach ($migrations as $migration) {
if ($migration->getVersion() > $version) {
break;
}
if (!in_array($migration->getVersion(), $versions)) {
$this->executeMigration($migration, MigrationInterface::UP);
}
}
}
public function executeMigration(MigrationInterface $migration, $direction = MigrationInterface::UP)
{
$this->getOutput()->writeln('');
$this->getOutput()->writeln(
' =='
. ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>'
. ' <comment>' . ($direction === MigrationInterface::UP ? 'migrating' : 'reverting') . '</comment>'
);
$start = microtime(true);
$startTime = time();
$direction = ($direction === MigrationInterface::UP) ? MigrationInterface::UP : MigrationInterface::DOWN;
$migration->setAdapter($this->getAdapter());
if ($this->getAdapter()->hasTransactions()) {
$this->getAdapter()->beginTransaction();
}
if (method_exists($migration, MigrationInterface::CHANGE)) {
if ($direction === MigrationInterface::DOWN) {
$proxyAdapter = AdapterFactory::instance()
->getWrapper('proxy', $this->getAdapter());
$migration->setAdapter($proxyAdapter);
$migration->change();
$proxyAdapter->executeInvertedCommands();
$migration->setAdapter($this->getAdapter());
} else {
$migration->change();
}
} else {
$migration->{$direction}();
}
if ($this->getAdapter()->hasTransactions()) {
$this->getAdapter()->commitTransaction();
}
$this->getAdapter()->migrated($migration, $direction, date('Y-m-d H:i:s', $startTime), date('Y-m-d H:i:s', time()));
$end = microtime(true);
$this->getOutput()->writeln(
' =='
. ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>'
. ' <comment>' . ($direction === MigrationInterface::UP ? 'migrated' : 'reverted')
. ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
);
}
public function executeSeed(SeedInterface $seed)
{
$this->getOutput()->writeln('');
$this->getOutput()->writeln(
' =='
. ' <info>' . $seed->getName() . ':</info>'
. ' <comment>seeding</comment>'
);
$start = microtime(true);
$seed->setAdapter($this->getAdapter());
if ($this->getAdapter()->hasTransactions()) {
$this->getAdapter()->beginTransaction();
}
if (method_exists($seed, SeedInterface::RUN)) {
$seed->run();
}
if ($this->getAdapter()->hasTransactions()) {
$this->getAdapter()->commitTransaction();
}
$end = microtime(true);
$this->getOutput()->writeln(
' =='
. ' <info>' . $seed->getName() . ':</info>'
. ' <comment>seeded'
. ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
);
}
public function rollback($version = null)
{
$migrations = $this->getMigrations();
$versions = $this->getVersions();
ksort($migrations);
sort($versions);
if (empty($versions) || $version == end($versions)) {
$this->getOutput()->writeln('<error>No migrations to rollback</error>');
return;
}
if (null === $version) {
$prev = count($versions) - 2;
$version = $prev >= 0 ? $versions[$prev] : 0;
} else {
$first = reset($versions);
if ($version < $first) {
$version = 0;
}
}
if (0 !== $version && !isset($migrations[$version])) {
$this->getOutput()->writeln("<error>Target version ($version) not found</error>");
return;
}
krsort($migrations);
foreach ($migrations as $migration) {
if ($migration->getVersion() <= $version) {
break;
}
if (in_array($migration->getVersion(), $versions)) {
$this->executeMigration($migration, MigrationInterface::DOWN);
}
}
}
public function seed($seed = null)
{
$seeds = $this->getSeeds();
if (null === $seed) {
foreach ($seeds as $seeder) {
if (array_key_exists($seeder->getName(), $seeds)) {
$this->executeSeed($seeder);
}
}
} else {
if (array_key_exists($seed, $seeds)) {
$this->executeSeed($seeds[$seed]);
} else {
throw new \InvalidArgumentException(sprintf('The seed class "%s" does not exist', $seed));
}
}
}
public function setOutput(Output $output)
{
$this->output = $output;
return $this;
}
public function getOutput()
{
return $this->output;
}
public function setMigrations(array $migrations)
{
$this->migrations = $migrations;
return $this;
}
public function getMigrations()
{
if (null === $this->migrations) {
$config = $this->getConfig();
$phpFiles = glob($config->getMigrationPath() . DIRECTORY_SEPARATOR . '*.php', GLOB_BRACE);
$fileNames = array();
$versions = array();
foreach ($phpFiles as $filePath) {
if (Util::isValidMigrationFileName(basename($filePath))) {
$version = Util::getVersionFromFileName(basename($filePath));
if (isset($versions[$version])) {
throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion()));
}
$class = Util::mapFileNameToClassName(basename($filePath));
if (isset($fileNames[$class])) {
throw new \InvalidArgumentException(sprintf(
'Migration "%s" has the same name as "%s"',
basename($filePath),
$fileNames[$class]
));
}
$fileNames[$class] = basename($filePath);
require_once $filePath;
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf(
'Could not find class "%s" in file "%s"',
$class,
$filePath
));
}
$migration = new $class($version);
if (!($migration instanceof AbstractMigration)) {
throw new \InvalidArgumentException(sprintf(
'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration',
$class,
$filePath
));
}
$migration->setOutput($this->getOutput());
$versions[$version] = $migration;
}
}
ksort($versions);
$this->setMigrations($versions);
}
return $this->migrations;
}
public function setSeeds(array $seeds)
{
$this->seeds = $seeds;
return $this;
}
public function getSeeds()
{
if (null === $this->seeds) {
$config = $this->getConfig();
$phpFiles = glob($config->getSeedPath() . DIRECTORY_SEPARATOR . '*.php');
$fileNames = array();
$seeds = array();
foreach ($phpFiles as $filePath) {
if (Util::isValidSeedFileName(basename($filePath))) {
$class = pathinfo($filePath, PATHINFO_FILENAME);
$fileNames[$class] = basename($filePath);
require_once $filePath;
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf(
'Could not find class "%s" in file "%s"',
$class,
$filePath
));
}
$seed = new $class();
if (!($seed instanceof AbstractSeed)) {
throw new \InvalidArgumentException(sprintf(
'The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed',
$class,
$filePath
));
}
$seed->setOutput($this->getOutput());
$seeds[$class] = $seed;
}
}
ksort($seeds);
$this->setSeeds($seeds);
}
return $this->seeds;
}
public function setConfig(Config $config)
{
$this->config = $config;
return $this;
}
public function getConfig()
{
return $this->config;
}
public function getVersions()
{
return $this->getAdapter()->getVersions();
}
public function getVersionLog()
{
return $this->getAdapter()->getVersionLog();
}
public function setCurrentVersion($version)
{
$this->currentVersion = $version;
return $this;
}
public function getCurrentVersion()
{
$versions = $this->getVersions();
$version = 0;
if (!empty($versions)) {
$version = end($versions);
}
$this->setCurrentVersion($version);
return $this->currentVersion;
}
public function setAdapter(AdapterInterface $adapter)
{
$this->adapter = $adapter;
return $this;
}
public function getAdapter()
{
if (isset($this->adapter)) {
return $this->adapter;
}
$options = $this->config->getDbConfig();
if (!isset($options['adapter'])) {
throw new \RuntimeException('No adapter was specified');
}
$adapter = AdapterFactory::instance()
->getAdapter($options['adapter'], $options);
if ($this->getOutput()) {
$adapter->setOutput($this->getOutput());
}
if ($adapter->hasOption('table_prefix') || $adapter->hasOption('table_suffix')) {
$adapter = AdapterFactory::instance()
->getWrapper('prefix', $adapter);
}
$this->setAdapter($adapter);
return $adapter;
}
}