Initial commit: apps.prometeysev.ru

This commit is contained in:
root committed 2026-10-22 00:22:57 +03:00
commit 96ccabcd8b
6561 files changed
+1270349

No files matched your search

+192
View File
@@ -0,0 +1,192 @@
<?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\Shell;
use Cake\Console\Shell;
use Migrations\MigrationsDispatcher;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
/**
* A wrapper shell for phinx migrations, used to inject our own
* console actions so that database configuration already defined
* for the application can be reused.
*
* @property \Migrations\Shell\Task\CreateTask $Create
* @property \Migrations\Shell\Task\DumpTask $Dump
* @property \Migrations\Shell\Task\MarkMigratedTask $MarkMigrated
* @property \Migrations\Shell\Task\MigrateTask $Migrate
* @property \Migrations\Shell\Task\RollbackTask $Rollback
* @property \Migrations\Shell\Task\StatusTask $Status
*/
class MigrationsShell extends Shell
{
/**
* {@inheritDoc}
*/
public $tasks = [
'Migrations.Create',
'Migrations.Dump',
'Migrations.MarkMigrated',
'Migrations.Migrate',
'Migrations.Rollback',
'Migrations.Status',
];
/**
* Array of arguments to run the shell with.
*
* @var array
*/
public $argv = [];
/**
* Defines what options can be passed to the shell.
* This is required because CakePHP validates the passed options
* and would complain if something not configured here is present
*
* @return \Cake\Console\ConsoleOptionParser
*/
public function getOptionParser()
{
return parent::getOptionParser()
->addOption('plugin', ['short' => 'p'])
->addOption('target', ['short' => 't'])
->addOption('connection', ['short' => 'c'])
->addOption('source', ['short' => 's'])
->addOption('seed')
->addOption('ansi')
->addOption('no-ansi')
->addOption('no-lock', ['boolean' => true])
->addOption('force', ['boolean' => true])
->addOption('version', ['short' => 'V'])
->addOption('no-interaction', ['short' => 'n'])
->addOption('template', ['short' => 't'])
->addOption('format', ['short' => 'f'])
->addOption('only', ['short' => 'o'])
->addOption('dry-run', ['short' => 'x'])
->addOption('exclude', ['short' => 'e']);
}
/**
* Defines constants that are required by phinx to get running
*
* @return void
*/
public function initialize()
{
if (!defined('PHINX_VERSION')) {
define('PHINX_VERSION', 'UNKNOWN');
}
parent::initialize();
}
/**
* This acts as a front-controller for phinx. It just instantiates the classes
* responsible for parsing the command line from phinx and gives full control of
* the rest of the flow to it.
*
* The input parameter of the ``MigrationDispatcher::run()`` method is manually built
* in case a MigrationsShell is dispatched using ``Shell::dispatch()``.
*
* @return bool Success of the call.
*/
public function main()
{
$app = $this->getApp();
$input = new ArgvInput($this->argv);
$app->setAutoExit(false);
$exitCode = $app->run($input, $this->getOutput());
if (in_array('-h', $this->argv) || in_array('--help', $this->argv)) {
return $exitCode === 0;
}
if (
isset($this->argv[1]) && in_array($this->argv[1], ['migrate', 'rollback']) &&
!$this->params['no-lock'] &&
$exitCode === 0
) {
$dispatchCommand = 'migrations dump';
if (!empty($this->params['connection'])) {
$dispatchCommand .= ' -c ' . $this->params['connection'];
}
if (!empty($this->params['plugin'])) {
$dispatchCommand .= ' -p ' . $this->params['plugin'];
}
$dumpExitCode = $this->dispatchShell($dispatchCommand);
}
if (isset($dumpExitCode) && $exitCode === 0 && $dumpExitCode !== 0) {
$exitCode = 1;
}
return $exitCode === 0;
}
/**
* Returns the MigrationsDispatcher the Shell will have to use
*
* @return \Migrations\MigrationsDispatcher
*/
protected function getApp()
{
return new MigrationsDispatcher(PHINX_VERSION);
}
/**
* Returns the instance of OutputInterface the MigrationsDispatcher will have to use.
*
* @return \Symfony\Component\Console\Output\ConsoleOutput
*/
protected function getOutput()
{
return new ConsoleOutput();
}
/**
* Override the default behavior to save the command called
* in order to pass it to the command dispatcher
*
* {@inheritDoc}
*/
public function runCommand($argv, $autoMethod = false, $extra = [])
{
array_unshift($argv, 'migrations');
$this->argv = $argv;
return parent::runCommand($argv, $autoMethod, $extra);
}
/**
* Display the help in the correct format
*
* @param string $command The command to get help for.
* @return int|bool|null Exit code or number of bytes written to stdout
*/
protected function displayHelp($command)
{
return $this->main();
}
/**
* {@inheritDoc}
*/
// @codingStandardsIgnoreStart
protected function _displayHelp($command)
{
// @codingStandardsIgnoreEnd
return $this->displayHelp($command);
}
}
@@ -0,0 +1,54 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
use Cake\Console\Shell;
/**
* Base Task that Migrations subcommands Task class should extend.
* It implements the getOptionParser method that defines the common options
* for all subcommands.
*/
class CommandTask extends Shell
{
/**
* {@inheritDoc}
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser
->addOption('plugin', [
'short' => 'p',
'help' => 'The plugin the command should be applied to',
'required' => false,
])
->addOption('connection', [
'short' => 'c',
'help' => 'The datasource connection to use',
'required' => false,
])
->addOption('source', [
'short' => 's',
'help' => 'The name of the folder where migrations are stored',
'required' => false,
])
->addOption('ansi')
->addOption('no-ansi')
->addOption('no-interaction', ['short' => 'n']);
return $parser;
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
use Phinx\Console\Command\Create;
/**
* This task class is needed in order to provide a correct autocompletion feature
* when using the CakePHP migrations shell plugin. It has no effect on the
* migrations process.
*/
class CreateTask extends CommandTask
{
/**
* {@inheritDoc}
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser
->addArgument('name', [
'help' => 'What is the name of the migration?',
])
->addOption('template', [
'short' => 't',
'help' => 'Use an alternative template',
'required' => false,
])
->addOption('class', [
'short' => 'l',
'help' => 'Use a class implementing "' . Create::CREATION_INTERFACE . '" to generate the template',
'required' => false,
]);
return $parser;
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
/**
* This task class is needed in order to provide a correct autocompletion feature
* when using the CakePHP migrations shell plugin. It has no effect on the
* migrations process.
*/
class DumpTask extends CommandTask
{
/**
* {@inheritDoc}
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser
->setDescription('Dumps the current schema of the database to be used while baking a diff');
return $parser;
}
}
@@ -0,0 +1,47 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
/**
* This task class is needed in order to provide a correct autocompletion feature
* when using the CakePHP migrations shell plugin. It has no effect on the
* migrations process.
*/
class MarkMigratedTask extends CommandTask
{
/**
* {@inheritDoc}
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser
->addArgument('version', [
'help' => 'What is the version of the migration?',
])
->addOption('exclude', [
'short' => 'x',
'help' => 'If present it will mark migrations from beginning until the given version, excluding it',
'required' => false,
])
->addOption('only', [
'short' => 'o',
'help' => 'If present it will only mark the given migration version',
'required' => false,
]);
return $parser;
}
}
@@ -0,0 +1,50 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
/**
* This task class is needed in order to provide a correct autocompletion feature
* when using the CakePHP migrations shell plugin. It has no effect on the
* migrations process.
*/
class MigrateTask extends CommandTask
{
/**
* {@inheritDoc}
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser
->addOption('target', [
'short' => 't',
'help' => 'The version number to migrate to',
'required' => false,
])
->addOption('date', [
'short' => 'd',
'help' => 'The date to migrate to',
'required' => false,
])
->addOption('dry-run', ['short' => 'x'])
->addOption('fake', ['boolean' => true])
->addOption('no-lock', [
'help' => 'If present, no lock file will be generated after migrating',
'boolean' => true,
]);
return $parser;
}
}
@@ -0,0 +1,561 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
use Cake\Core\Configure;
use Cake\Database\Schema\Table;
use Cake\Datasource\ConnectionManager;
use Cake\Event\Event;
use Cake\Event\EventManager;
use Migrations\Util\UtilTrait;
use Symfony\Component\Console\Input\ArrayInput;
/**
* Task class for generating migration diff files.
*
* @property \Bake\Shell\Task\BakeTemplateTask $BakeTemplate
* @property \Bake\Shell\Task\TestTask $Test
*/
class MigrationDiffTask extends SimpleMigrationTask
{
use SnapshotTrait;
use UtilTrait;
/**
* Array of migrations that have already been migrated
*
* @var array
*/
protected $migratedItems = [];
/**
* Path to the migration files
*
* @var string
*/
protected $migrationsPath;
/**
* Migration files that are stored in the self::migrationsPath
*
* @var array
*/
protected $migrationsFiles = [];
/**
* Name of the phinx log table
*
* @var string
*/
protected $phinxTable;
/**
* List the tables the connection currently holds
*
* @var array
*/
protected $tables = [];
/**
* Array of \Cake\Database\Schema\Table objects from the dump file which
* represents the state of the database after the last migrate / rollback command
*
* @var array
*/
protected $dumpSchema;
/**
* Array of \Cake\Database\Schema\Table objects from the current state of the database
*
* @var array
*/
protected $currentSchema;
/**
* List of the tables that are commonly found in the dump schema and the current schema
*
* @var array
*/
protected $commonTables;
/**
* {@inheritDoc}
*/
protected $templateData = [];
/**
* {@inheritDoc}
*/
public function bake($name)
{
$this->setup();
if (!$this->checkSync()) {
$this->abort('Your migrations history is not in sync with your migrations files. ' .
'Make sure all your migrations have been migrated before baking a diff.');
return 1;
}
if (empty($this->migrationsFiles) && empty($this->migratedItems)) {
return $this->bakeSnapshot($name);
}
$collection = $this->getCollection($this->connection);
EventManager::instance()->on('Bake.initialize', function (Event $event) use ($collection) {
$event->getSubject()->loadHelper('Migrations.Migration', [
'collection' => $collection,
]);
});
return parent::bake($name);
}
/**
* Sets up everything the baking process needs
*
* @return void
*/
public function setup()
{
$this->migrationsPath = $this->getPath();
$this->migrationsFiles = glob($this->migrationsPath . '*.php');
$this->phinxTable = $this->getPhinxTable($this->plugin);
$connection = ConnectionManager::get($this->connection);
$this->tables = $connection->getSchemaCollection()->listTables();
$tableExists = in_array($this->phinxTable, $this->tables);
$migratedItems = [];
if ($tableExists) {
$query = $connection->newQuery();
$migratedItems = $query
->select(['version'])
->from($this->phinxTable)
->order(['version DESC'])
->execute()->fetchAll('assoc');
}
$this->migratedItems = $migratedItems;
}
/**
* Get a collection from a database.
*
* @param string $connection Database connection name.
* @return \Cake\Database\Schema\Collection
*/
public function getCollection($connection)
{
$connection = ConnectionManager::get($connection);
return $connection->getSchemaCollection();
}
/**
* Process and prepare the data needed for the bake template to be generated.
*
* @return array
*/
public function templateData()
{
$this->dumpSchema = $this->getDumpSchema();
$this->currentSchema = $this->getCurrentSchema();
$this->commonTables = array_intersect_key($this->currentSchema, $this->dumpSchema);
$this->calculateDiff();
return [
'data' => $this->templateData,
'dumpSchema' => $this->dumpSchema,
'currentSchema' => $this->currentSchema,
];
}
/**
* This methods runs the various methods needed to calculate a diff between the current
* state of the database and the schema dump file.
*
* @return void
*/
protected function calculateDiff()
{
$this->getConstraints();
$this->getIndexes();
$this->getColumns();
$this->getTables();
}
/**
* Calculate the diff between the current state of the database and the schema dump
* by returning an array containing the full \Cake\Database\Schema\Table definitions
* of tables to be created and removed in the diff file.
*
* The method directly sets the diff in a property of the class.
*
* @return void
*/
protected function getTables()
{
$this->templateData['fullTables'] = [
'add' => array_diff_key($this->currentSchema, $this->dumpSchema),
'remove' => array_diff_key($this->dumpSchema, $this->currentSchema),
];
}
/**
* Calculate the diff between columns in existing tables.
* This will look for columns addition, columns removal and changes in columns metadata
* such as change of types or property such as length.
*
* Note that the method is not able to detect columns name change.
* The method directly sets the diff in a property of the class.
*
* @return void
*/
protected function getColumns()
{
foreach ($this->commonTables as $table => $currentSchema) {
$currentColumns = $currentSchema->columns();
$oldColumns = $this->dumpSchema[$table]->columns();
// brand new columns
$addedColumns = array_diff($currentColumns, $oldColumns);
foreach ($addedColumns as $columnName) {
$column = $currentSchema->getColumn($columnName);
$key = array_search($columnName, $currentColumns);
if ($key > 0) {
$column['after'] = $currentColumns[$key - 1];
}
if (isset($column['unsigned'])) {
$column['signed'] = !$column['unsigned'];
unset($column['unsigned']);
}
$this->templateData[$table]['columns']['add'][$columnName] = $column;
}
// changes in columns meta-data
foreach ($currentColumns as $columnName) {
$column = $currentSchema->getColumn($columnName);
$oldColumn = $this->dumpSchema[$table]->getColumn($columnName);
unset($column['collate']);
unset($oldColumn['collate']);
if (
in_array($columnName, $oldColumns) &&
$column !== $oldColumn
) {
$changedAttributes = array_diff_assoc($column, $oldColumn);
foreach (['type', 'length', 'null', 'default'] as $attribute) {
$phinxAttributeName = $attribute;
if ($attribute == 'length') {
$phinxAttributeName = 'limit';
}
if (!isset($changedAttributes[$phinxAttributeName])) {
$changedAttributes[$phinxAttributeName] = $column[$attribute];
}
}
if (isset($changedAttributes['unsigned'])) {
$changedAttributes['signed'] = !$changedAttributes['unsigned'];
unset($changedAttributes['unsigned']);
} else {
// badish hack
if (isset($column['unsigned']) && $column['unsigned'] === true) {
$changedAttributes['signed'] = false;
}
}
if (isset($changedAttributes['length'])) {
if (!isset($changedAttributes['limit'])) {
$changedAttributes['limit'] = $changedAttributes['length'];
}
unset($changedAttributes['length']);
}
$this->templateData[$table]['columns']['changed'][$columnName] = $changedAttributes;
}
}
// columns deletion
if (!isset($this->templateData[$table]['columns']['remove'])) {
$this->templateData[$table]['columns']['remove'] = [];
}
$removedColumns = array_diff($oldColumns, $currentColumns);
if (!empty($removedColumns)) {
foreach ($removedColumns as $columnName) {
$column = $this->dumpSchema[$table]->getColumn($columnName);
$key = array_search($columnName, $oldColumns);
if ($key > 0) {
$column['after'] = $oldColumns[$key - 1];
}
$this->templateData[$table]['columns']['remove'][$columnName] = $column;
}
}
}
}
/**
* Calculate the diff between contraints in existing tables.
* This will look for contraints addition, contraints removal and changes in contraints metadata
* such as change of referenced columns if the old constraints and the new one have the same name.
*
* The method directly sets the diff in a property of the class.
*
* @return void
*/
protected function getConstraints()
{
foreach ($this->commonTables as $table => $currentSchema) {
$currentConstraints = $currentSchema->constraints();
$oldConstraints = $this->dumpSchema[$table]->constraints();
// brand new constraints
$addedConstraints = array_diff($currentConstraints, $oldConstraints);
foreach ($addedConstraints as $constraintName) {
$this->templateData[$table]['constraints']['add'][$constraintName] =
$currentSchema->getConstraint($constraintName);
$constraint = $currentSchema->getConstraint($constraintName);
if ($constraint['type'] === Table::CONSTRAINT_FOREIGN) {
$this->templateData[$table]['constraints']['add'][$constraintName] = $constraint;
} else {
$this->templateData[$table]['indexes']['add'][$constraintName] = $constraint;
}
}
// constraints having the same name between new and old schema
// if present in both, check if they are the same : if not, remove the old one and add the new one
foreach ($currentConstraints as $constraintName) {
$constraint = $currentSchema->getConstraint($constraintName);
if (
in_array($constraintName, $oldConstraints) &&
$constraint !== $this->dumpSchema[$table]->getConstraint($constraintName)
) {
$this->templateData[$table]['constraints']['remove'][$constraintName] =
$this->dumpSchema[$table]->getConstraint($constraintName);
$this->templateData[$table]['constraints']['add'][$constraintName] =
$constraint;
}
}
// removed constraints
$removedConstraints = array_diff($oldConstraints, $currentConstraints);
foreach ($removedConstraints as $constraintName) {
$constraint = $this->dumpSchema[$table]->getConstraint($constraintName);
if ($constraint['type'] === Table::CONSTRAINT_FOREIGN) {
$this->templateData[$table]['constraints']['remove'][$constraintName] = $constraint;
} else {
$this->templateData[$table]['indexes']['remove'][$constraintName] = $constraint;
}
}
}
}
/**
* Calculate the diff between indexes in existing tables.
* This will look for indexes addition, indexes removal and changes in indexes metadata
* such as change of referenced columns if the old indexes and the new one have the same name.
*
* The method directly sets the diff in a property of the class.
*
* @return void
*/
protected function getIndexes()
{
foreach ($this->commonTables as $table => $currentSchema) {
$currentIndexes = $currentSchema->indexes();
$oldIndexes = $this->dumpSchema[$table]->indexes();
sort($currentIndexes);
sort($oldIndexes);
// brand new indexes
$addedIndexes = array_diff($currentIndexes, $oldIndexes);
foreach ($addedIndexes as $indexName) {
$this->templateData[$table]['indexes']['add'][$indexName] = $currentSchema->getIndex($indexName);
}
// indexes having the same name between new and old schema
// if present in both, check if they are the same : if not, remove the old one and add the new one
foreach ($currentIndexes as $indexName) {
$index = $currentSchema->getIndex($indexName);
if (
in_array($indexName, $oldIndexes) &&
$index !== $this->dumpSchema[$table]->getIndex($indexName)
) {
$this->templateData[$table]['indexes']['remove'][$indexName] =
$this->dumpSchema[$table]->getIndex($indexName);
$this->templateData[$table]['indexes']['add'][$indexName] = $index;
}
}
// indexes deletion
if (!isset($this->templateData[$table]['indexes']['remove'])) {
$this->templateData[$table]['indexes']['remove'] = [];
}
$removedIndexes = array_diff($oldIndexes, $currentIndexes);
$parts = [];
if (!empty($removedIndexes)) {
foreach ($removedIndexes as $index) {
$parts[$index] = $this->dumpSchema[$table]->getIndex($index);
}
}
$this->templateData[$table]['indexes']['remove'] = array_merge(
$this->templateData[$table]['indexes']['remove'],
$parts
);
}
}
/**
* Checks that the migrations history is in sync with the migrations files
*
* @return bool Whether migrations history is sync or not
*/
protected function checkSync()
{
if (empty($this->migrationsFiles) && empty($this->migratedItems)) {
return true;
}
if (!empty($this->migratedItems)) {
$lastVersion = $this->migratedItems[0]['version'];
$lastFile = end($this->migrationsFiles);
return (bool)strpos($lastFile, (string)$lastVersion);
}
return false;
}
/**
* Fallback method called to bake a snapshot when the phinx log history is empty and
* there are no migration files.
*
* @param string $name Name.
* @return int Value of the snapshot baking dispatch process
*/
protected function bakeSnapshot($name)
{
$this->out('Your migrations history is empty and you do not have any migrations files.');
$this->out('Falling back to baking a snapshot...');
$dispatchCommand = 'bake migration_snapshot ' . $name;
if (!empty($this->params['connection'])) {
$dispatchCommand .= ' -c ' . $this->params['connection'];
}
if (!empty($this->params['plugin'])) {
$dispatchCommand .= ' -p ' . $this->params['plugin'];
}
$dispatch = $this->dispatchShell([
'command' => $dispatchCommand,
]);
if ($dispatch === 1) {
$this->abort('Something went wrong during the snapshot baking. Please try again.');
}
return $dispatch;
}
/**
* Fetch the correct schema dump based on the arguments and options passed to the shell call
* and returns it as an array
*
* @return array Full database schema : the key is the name of the table and the value is
* an instance of \Cake\Database\Schema\Table.
*/
protected function getDumpSchema()
{
$inputArgs = [];
$connectionName = 'default';
if (!empty($this->params['connection'])) {
$connectionName = $inputArgs['--connection'] = $this->params['connection'];
}
if (!empty($this->params['plugin'])) {
$inputArgs['--plugin'] = $this->params['plugin'];
}
$className = '\Migrations\Command\Dump';
$definition = (new $className())->getDefinition();
$input = new ArrayInput($inputArgs, $definition);
$path = $this->getOperationsPath($input) . DS . 'schema-dump-' . $connectionName . '.lock';
if (!file_exists($path)) {
$msg = 'Unable to retrieve the schema dump file. You can create a dump file using ' .
'the `cake migrations dump` command';
$this->abort($msg);
}
return unserialize(file_get_contents($path));
}
/**
* Reflects the current database schema.
*
* @return array Full database schema : the key is the name of the table and the value is
* an instance of \Cake\Database\Schema\Table.
*/
protected function getCurrentSchema()
{
$schema = [];
if (empty($this->tables)) {
return $schema;
}
$collection = ConnectionManager::get($this->connection)->getSchemaCollection();
foreach ($this->tables as $table) {
if (preg_match("/^.*phinxlog$/", $table) === 1) {
continue;
}
$schema[$table] = $collection->describe($table);
}
return $schema;
}
/**
* {@inheritDoc}
*/
public function template()
{
return 'Migrations.config/diff';
}
/**
* Gets the option parser instance and configures it.
*
* @return \Cake\Console\ConsoleOptionParser
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser->addArgument('name', [
'help' => 'Name of the migration to bake. Can use Plugin.name to bake migration files into plugins.',
'required' => true,
]);
return $parser;
}
}
@@ -0,0 +1,155 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
use Cake\Core\Configure;
use Cake\Datasource\ConnectionManager;
use Cake\Event\Event;
use Cake\Event\EventManager;
use Migrations\TableFinderTrait;
use Migrations\Util\UtilTrait;
/**
* Task class for generating migration snapshot files.
*
* @property \Bake\Shell\Task\BakeTemplateTask $BakeTemplate
* @property \Bake\Shell\Task\TestTask $Test
*/
class MigrationSnapshotTask extends SimpleMigrationTask
{
use SnapshotTrait;
use TableFinderTrait;
use UtilTrait;
/**
* {@inheritDoc}
*/
public function bake($name)
{
$collection = $this->getCollection($this->connection);
EventManager::instance()->on('Bake.initialize', function (Event $event) use ($collection) {
$event->getSubject()->loadHelper('Migrations.Migration', [
'collection' => $collection,
]);
});
return parent::bake($name);
}
/**
* {@inheritDoc}
*/
public function template()
{
return 'Migrations.config/snapshot';
}
/**
* {@inheritDoc}
*/
public function templateData()
{
$namespace = Configure::read('App.namespace');
$pluginPath = '';
if ($this->plugin) {
$namespace = $this->_pluginNamespace($this->plugin);
$pluginPath = $this->plugin . '.';
}
$collection = $this->getCollection($this->connection);
$options = [
'require-table' => $this->params['require-table'],
'plugin' => $this->plugin,
];
$tables = $this->getTablesToBake($collection, $options);
sort($tables, SORT_NATURAL);
$tables = array_combine($tables, $tables);
$autoId = true;
if (isset($this->params['disable-autoid'])) {
$autoId = !$this->params['disable-autoid'];
}
return [
'plugin' => $this->plugin,
'pluginPath' => $pluginPath,
'namespace' => $namespace,
'collection' => $collection,
'tables' => $tables,
'action' => 'create_table',
'name' => $this->BakeTemplate->viewVars['name'],
'autoId' => $autoId,
];
}
/**
* Get a collection from a database
*
* @param string $connection Database connection name.
* @return \Cake\Database\Schema\Collection
*/
public function getCollection($connection)
{
$connection = ConnectionManager::get($connection);
return $connection->getSchemaCollection();
}
/**
* To check if a Table Model is to be added in the migration file
*
* @param string $tableName Table name in underscore case.
* @param string|null $pluginName Plugin name if exists.
* @deprecated Will be removed in the next version
* @return bool True if the model is to be added.
*/
public function tableToAdd($tableName, $pluginName = null)
{
return true;
}
/**
* Gets the option parser instance and configures it.
*
* @return \Cake\Console\ConsoleOptionParser
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser->setDescription(
'Bake migration snapshot class.'
)->addArgument('name', [
'help' => 'Name of the migration to bake. Can use Plugin.name to bake migration files into plugins.',
'required' => true,
])
->addOption('require-table', [
'boolean' => true,
'default' => false,
'help' => 'If require-table is set to true, check also that the table class exists.',
])->addOption('disable-autoid', [
'boolean' => true,
'default' => false,
'help' => 'Disable phinx behavior of automatically adding an id field.',
])
->addOption('no-lock', [
'help' => 'If present, no lock file will be generated after baking',
'boolean' => true,
]);
return $parser;
}
}
@@ -0,0 +1,134 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
use Cake\Core\Configure;
use Cake\Event\Event;
use Cake\Event\EventManager;
use Cake\Utility\Inflector;
use Migrations\Util\ColumnParser;
/**
* Task class for generating migration snapshot files.
*
* @property \Bake\Shell\Task\BakeTemplateTask $BakeTemplate
* @property \Bake\Shell\Task\TestTask $Test
*/
class MigrationTask extends SimpleMigrationTask
{
/**
* {@inheritDoc}
*/
public function bake($name)
{
EventManager::instance()->on('Bake.initialize', function (Event $event) {
$event->getSubject()->loadHelper('Migrations.Migration');
});
return parent::bake($name);
}
/**
* {@inheritDoc}
*/
public function template()
{
return 'Migrations.config/skeleton';
}
/**
* {@inheritdoc}
*/
public function templateData()
{
$className = $this->BakeTemplate->viewVars['name'];
$namespace = Configure::read('App.namespace');
$pluginPath = '';
if ($this->plugin) {
$namespace = $this->_pluginNamespace($this->plugin);
$pluginPath = $this->plugin . '.';
}
$action = $this->detectAction($className);
if (empty($action)) {
return [
'plugin' => $this->plugin,
'pluginPath' => $pluginPath,
'namespace' => $namespace,
'tables' => [],
'action' => null,
'name' => $className,
];
}
$arguments = $this->args;
unset($arguments[0]);
$columnParser = new ColumnParser();
$fields = $columnParser->parseFields($arguments);
$indexes = $columnParser->parseIndexes($arguments);
$primaryKey = $columnParser->parsePrimaryKey($arguments);
if (in_array($action[0], ['alter_table', 'add_field']) && !empty($primaryKey)) {
$this->abort('Adding a primary key to an already existing table is not supported.');
}
list($action, $table) = $action;
return [
'plugin' => $this->plugin,
'pluginPath' => $pluginPath,
'namespace' => $namespace,
'tables' => [$table],
'action' => $action,
'columns' => [
'fields' => $fields,
'indexes' => $indexes,
'primaryKey' => $primaryKey,
],
'name' => $className,
];
}
/**
* Detects the action and table from the name of a migration
*
* @param string $name Name of migration
* @return array
**/
public function detectAction($name)
{
if (preg_match('/^(Create|Drop)(.*)/', $name, $matches)) {
$action = strtolower($matches[1]) . '_table';
$table = Inflector::underscore($matches[2]);
} elseif (preg_match('/^(Add).+?(?:To)(.*)/', $name, $matches)) {
$action = 'add_field';
$table = Inflector::underscore($matches[2]);
} elseif (preg_match('/^(Remove).+?(?:From)(.*)/', $name, $matches)) {
$action = 'drop_field';
$table = Inflector::underscore($matches[2]);
} elseif (preg_match('/^(Alter).+?(?:On)(.*)/', $name, $matches)) {
$action = 'alter_field';
$table = Inflector::underscore($matches[2]);
} elseif (preg_match('/^(Alter)(.*)/', $name, $matches)) {
$action = 'alter_table';
$table = Inflector::underscore($matches[2]);
} else {
return [];
}
return [$action, $table];
}
}
@@ -0,0 +1,50 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
/**
* This task class is needed in order to provide a correct autocompletion feature
* when using the CakePHP migrations shell plugin. It has no effect on the
* migrations process.
*/
class RollbackTask extends CommandTask
{
/**
* {@inheritDoc}
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser
->addOption('target', [
'short' => 't',
'help' => 'The version number to migrate to',
'required' => false,
])
->addOption('date', [
'short' => 'd',
'help' => 'The date to migrate to',
'required' => false,
])
->addOption('dry-run', ['short' => 'x'])
->addOption('fake', ['boolean' => true])
->addOption('no-lock', [
'help' => 'If present, no lock file will be generated after rolling back',
'boolean' => true,
]);
return $parser;
}
}
+258
View File
@@ -0,0 +1,258 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
use Bake\Shell\Task\SimpleBakeTask;
use Cake\Console\ConsoleOptionParser;
use Cake\Core\Configure;
use Cake\Core\Plugin as CorePlugin;
use Cake\Datasource\ConnectionManager;
use Cake\Utility\Inflector;
/**
* Task class for generating seed files.
*
* @property \Bake\Shell\Task\BakeTemplateTask $BakeTemplate
* @property \Bake\Shell\Task\TestTask $Test
*/
class SeedTask extends SimpleBakeTask
{
/**
* path to Migration directory
*
* @var string
*/
public $pathFragment = 'config/Seeds/';
/**
* {@inheritDoc}
*/
public function name()
{
return 'seed';
}
/**
* {@inheritDoc}
*/
public function fileName($name)
{
return Inflector::camelize($name) . 'Seed.php';
}
/**
* {@inheritDoc}
*/
public function getPath()
{
$path = ROOT . DS . $this->pathFragment;
if (isset($this->plugin)) {
$path = $this->_pluginPath($this->plugin) . $this->pathFragment;
}
return str_replace('/', DS, $path);
}
/**
* {@inheritDoc}
*/
public function template()
{
return 'Migrations.Seed/seed';
}
/**
* Get template data.
*
* @return array
*/
public function templateData()
{
$namespace = Configure::read('App.namespace');
if ($this->plugin) {
$namespace = $this->_pluginNamespace($this->plugin);
}
$table = Inflector::tableize($this->args[0]);
if (!empty($this->params['table'])) {
$table = $this->params['table'];
}
$records = false;
if ($this->param('data')) {
$limit = (int)$this->param('limit');
$fields = $this->param('fields') ?: '*';
if ($fields !== '*') {
$fields = explode(',', $fields);
}
$connection = ConnectionManager::get($this->connection);
$query = $connection->newQuery()
->from($table)
->select($fields);
if ($limit) {
$query->limit($limit);
}
$records = $connection->execute($query)->fetchAll('assoc');
$records = $this->prettifyArray($records);
}
return [
'className' => $this->BakeTemplate->viewVars['name'],
'namespace' => $namespace,
'records' => $records,
'table' => $table,
];
}
/**
* {@inheritDoc}
*/
public function bake($name)
{
$this->params['no-test'] = true;
return parent::bake($name);
}
/**
* Gets the option parser instance and configures it.
*
* @return \Cake\Console\ConsoleOptionParser
*/
public function getOptionParser()
{
$name = ($this->plugin ? $this->plugin . '.' : '') . $this->name;
$parser = new ConsoleOptionParser($name);
$bakeThemes = [];
foreach (CorePlugin::loaded() as $plugin) {
$path = CorePlugin::classPath($plugin);
if (is_dir($path . 'Template' . DS . 'Bake')) {
$bakeThemes[] = $plugin;
}
}
$parser->setDescription(
'Bake seed class.'
)->addOption('plugin', [
'short' => 'p',
'help' => 'Plugin to bake into.',
])->addOption('force', [
'short' => 'f',
'boolean' => true,
'help' => 'Force overwriting existing files without prompting.',
])->addOption('connection', [
'short' => 'c',
'default' => 'default',
'help' => 'The datasource connection to get data from.',
])->addOption('table', [
'help' => 'The database table to use.',
])->addOption('theme', [
'short' => 't',
'help' => 'The theme to use when baking code.',
'choices' => $bakeThemes,
])->addArgument('name', [
'help' => 'Name of the seed to bake. Can use Plugin.name to bake plugin models.',
])->addOption('data', [
'boolean' => true,
'help' => 'Include data from the table to the seed',
])->addOption('fields', [
'default' => '*',
'help' => 'If including data, comma separated list of fields to select (all fields by default)',
])->addOption('limit', [
'short' => 'l',
'help' => 'If including data, max number of rows to select',
]);
return $parser;
}
/**
* Prettify var_export of an array output
*
* @param array $array Array to prettify
* @param int $tabCount Initial tab count
* @param string $indentCharacter Desired indent for the code.
* @return string
*/
protected function prettifyArray($array, $tabCount = 3, $indentCharacter = " ")
{
$content = var_export($array, true);
$lines = explode("\n", $content);
$inString = false;
foreach ($lines as $k => &$line) {
if ($k === 0) {
// First row
$line = '[';
continue;
}
if ($k === count($lines) - 1) {
// Last row
$line = str_repeat($indentCharacter, --$tabCount) . ']';
continue;
}
$line = ltrim($line);
if (!$inString) {
if ($line === '),') {
//Check for closing bracket
$line = '],';
$tabCount--;
} elseif (preg_match("/^\d+\s\=\>\s$/", $line)) {
// Mark '0 =>' kind of lines to remove
$line = false;
continue;
}
//Insert tab count
$line = str_repeat($indentCharacter, $tabCount) . $line;
}
$length = strlen($line);
for ($j = 0; $j < $length; $j++) {
if ($line[$j] === '\\') {
//skip character right after an escape \
$j++;
} elseif ($line[$j] === '\'') {
//check string open/end
$inString = !$inString;
}
}
//check for opening bracket
if (!$inString && trim($line) === 'array (') {
$line = str_replace('array (', '[', $line);
$tabCount++;
}
}
unset($line);
// Remove marked lines
$lines = array_filter($lines, function ($line) {
return $line !== false;
});
return implode("\n", $lines);
}
}
@@ -0,0 +1,165 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
use Bake\Shell\Task\SimpleBakeTask;
use Cake\Console\ConsoleOptionParser;
use Cake\Core\Plugin as CorePlugin;
use Cake\Utility\Inflector;
use Phinx\Util\Util;
/**
* Task class for generating migration snapshot files.
*/
abstract class SimpleMigrationTask extends SimpleBakeTask
{
/**
* path to Migration directory
*
* @var string
*/
public $pathFragment = 'config/Migrations/';
/**
* {@inheritDoc}
*/
public function name()
{
return 'migration';
}
/**
* {@inheritDoc}
*/
public function fileName($name)
{
$name = $this->getMigrationName($name);
return Util::getCurrentTimestamp() . '_' . Inflector::camelize($name) . '.php';
}
/**
* {@inheritDoc}
*/
public function getPath()
{
$path = ROOT . DS . $this->pathFragment;
if (isset($this->plugin)) {
$path = $this->_pluginPath($this->plugin) . $this->pathFragment;
}
return str_replace('/', DS, $path);
}
/**
* {@inheritDoc}
*/
public function bake($name)
{
$migrationWithSameName = glob($this->getPath() . '*_' . $name . '.php');
if (!empty($migrationWithSameName)) {
$force = $this->param('force');
if (!$force) {
$this->abort(
sprintf(
'A migration with the name `%s` already exists. Please use a different name.',
$name
)
);
}
$this->info(sprintf('A migration with the name `%s` already exists, it will be deleted.', $name));
foreach ($migrationWithSameName as $migration) {
$this->info(sprintf('Deleting migration file `%s`...', $migration));
if (unlink($migration)) {
$this->success(sprintf('Deleted `%s`', $migration));
} else {
$this->err(sprintf('An error occurred while deleting `%s`', $migration));
}
}
}
$this->params['no-test'] = true;
return parent::bake($name);
}
/**
* Returns a class name for the migration class
*
* If the name is invalid, the task will exit
*
* @param string|null $name Name for the generated migration
* @return string|null Name of the migration file or null if empty
*/
protected function getMigrationName($name = null)
{
if (empty($name)) {
$this->abort('Choose a migration name to bake in CamelCase format');
}
$name = $this->_getName($name);
$name = Inflector::camelize($name);
if (!preg_match('/^[A-Z]{1}[a-zA-Z0-9]+$/', $name)) {
$this->abort('The className is not correct. The className can only contain "A-Z" and "0-9".');
}
return $name;
}
/**
* Gets the option parser instance and configures it.
*
* @return \Cake\Console\ConsoleOptionParser
*/
public function getOptionParser()
{
$name = ($this->plugin ? $this->plugin . '.' : '') . $this->name;
$parser = new ConsoleOptionParser($name);
$bakeThemes = [];
foreach (CorePlugin::loaded() as $plugin) {
$path = CorePlugin::classPath($plugin);
if (is_dir($path . 'Template' . DS . 'Bake')) {
$bakeThemes[] = $plugin;
}
}
$parser->setDescription(
'Bake migration class.'
)
->addOption('plugin', [
'short' => 'p',
'help' => 'Plugin to bake into.',
])
->addOption('force', [
'short' => 'f',
'boolean' => true,
'help' => 'Force overwriting existing file if a migration already exists with the same name.',
])
->addOption('connection', [
'short' => 'c',
'default' => 'default',
'help' => 'The datasource connection to get data from.',
])
->addOption('theme', [
'short' => 't',
'help' => 'The theme to use when baking code.',
'choices' => $bakeThemes,
]);
return $parser;
}
}
@@ -0,0 +1,97 @@
<?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\Shell\Task;
use Cake\Core\App;
use Cake\Core\Plugin;
use Cake\Database\Schema\Collection;
use Cake\Datasource\ConnectionManager;
use Cake\Filesystem\Folder;
use Cake\ORM\TableRegistry;
use ReflectionClass;
/**
* Trait needed for all "snapshot" type of bake operations.
* Snapshot type operations are : baking a snapshot and baking a diff.
*/
trait SnapshotTrait
{
/**
* After a file has been successfully created, we mark the newly
* created migration as applied
*
* @param string $path Where to put the file.
* @param string $contents Content to put in the file.
* @return bool Success
*/
public function createFile($path, $contents)
{
$createFile = parent::createFile($path, $contents);
if ($createFile) {
$this->markSnapshotApplied($path);
if (!isset($this->params['no-lock']) || !$this->params['no-lock']) {
$this->refreshDump();
}
}
return $createFile;
}
/**
* Will mark a snapshot created, the snapshot being identified by its
* full file path.
*
* @param string $path Path to the newly created snapshot
* @return void
*/
protected function markSnapshotApplied($path)
{
$fileName = pathinfo($path, PATHINFO_FILENAME);
list($version, ) = explode('_', $fileName, 2);
$dispatchCommand = 'migrations mark_migrated -t ' . $version . ' -o';
if (!empty($this->params['connection'])) {
$dispatchCommand .= ' -c ' . $this->params['connection'];
}
if (!empty($this->params['plugin'])) {
$dispatchCommand .= ' -p ' . $this->params['plugin'];
}
$this->_io->out('Marking the migration ' . $fileName . ' as migrated...');
$this->dispatchShell($dispatchCommand);
}
/**
* After a file has been successfully created, we refresh the dump of the database
* to be able to generate a new diff afterward.
*
* @return void
*/
protected function refreshDump()
{
$dispatchCommand = 'migrations dump';
if (!empty($this->params['connection'])) {
$dispatchCommand .= ' -c ' . $this->params['connection'];
}
if (!empty($this->params['plugin'])) {
$dispatchCommand .= ' -p ' . $this->params['plugin'];
}
$this->_io->out('Creating a dump of the new database state...');
$this->dispatchShell($dispatchCommand);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* 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\Shell\Task;
/**
* This task class is needed in order to provide a correct autocompletion feature
* when using the CakePHP migrations shell plugin. It has no effect on the
* migrations process.
*/
class StatusTask extends CommandTask
{
/**
* {@inheritDoc}
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser->addOption('format', [
'short' => 'f',
'help' => 'The output format: text or json. Defaults to text.',
'required' => false,
]);
return $parser;
}
}