diff --git a/UPGRADE_0.9.md b/UPGRADE_0.9.md new file mode 100644 index 000000000..2a3961ec5 --- /dev/null +++ b/UPGRADE_0.9.md @@ -0,0 +1,41 @@ +# Upgrading Phinx to 0.9 + +* Template Creation is now supported for use by Migrations, Repeatable Migrations and Seeds. + + Some renaming of the interface, abstract base class and methods has + been undertaken so that the names are not tied specifically to + Migrations. + + The following elements have been renamed: + + * Interface + + Phinx 0.8 + + Phinx\Migration\CreationInterface + + Phinx 0.9 + + Phinx\Templates\TemplateCreationInterface + + * Class + + Phinx 0.8 + + Phinx\Migration\AbstractTemplateCreation + + Phinx 0.9 + + Phinx\Templates\AbstractTemplateCreation + + * Methods + + Phinx 0.8: + + Phinx\Migration\CreationInterface\getMigrationTemplate()` + Phinx\Migration\CreationInterface\postMigrationCreation($migrationFilename, $className, $baseClassName) + + Phinx 0.9 + + Phinx\Templates\TemplateCreationInterface\getTemplate() + Phinx\Templates\TemplateCreationInterface\postTemplateCreation($filename, $className, $baseClassName) diff --git a/docs/commands.rst b/docs/commands.rst index 8dea85b83..85ce0b164 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -57,7 +57,7 @@ alternative template filename. $ phinx create MyNewMigration --template="" You can also supply a template generating class. This class must implement the -interface ``Phinx\Migration\CreationInterface``. +interface ``Phinx\Templates\TemplateCreationInterface``. .. code-block:: bash @@ -148,8 +148,8 @@ breakpoint using the ``--force`` parameter or ``-f`` for short. .. note:: - When rolling back, Phinx orders the executed migrations using - the order specified in the ``version_order`` option of your + When rolling back, Phinx orders the executed migrations using + the order specified in the ``version_order`` option of your ``phinx.yml`` file. Please see the :doc:`Configuration ` chapter for more information. diff --git a/docs/configuration.rst b/docs/configuration.rst index 8f2ef3657..1a3532d91 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -104,10 +104,65 @@ setting ``migration_base_class`` in your config: migration_base_class: MyMagicalMigration +Repeatable Migration Paths +-------------------------- + +The second option specifies the path to your repeatable migration directory. Phinx uses +``%%PHINX_CONFIG_DIR%%/db/repeatables`` by default. + +.. note:: + + ``%%PHINX_CONFIG_DIR%%`` is a special token and is automatically replaced + with the root directory where your ``phinx.yml`` file is stored. + +In order to overwrite the default ``%%PHINX_CONFIG_DIR%%/db/repeatables``, you +need to add the following to the yaml configuration. + +.. code-block:: yaml + + paths: + repeatables: /your/full/path + +You can also provide multiple repeatable migration paths by using an array in your configuration: + +.. code-block:: yaml + + paths: + repeatables: + - application/module1/repeatables + - application/module2/repeatables + + +You can also use the ``%%PHINX_CONFIG_DIR%%`` token in your path. + +.. code-block:: yaml + + paths: + repeatables: %%PHINX_CONFIG_DIR%%/your/relative/path + +Repeatable migrations are captured with ``glob``, so you can define a pattern for multiple +directories. + +.. code-block:: yaml + + paths: + migrations: %%PHINX_CONFIG_DIR%%/module/*/{views,triggers}/repeatables + +Custom Repeatable Migration Base +-------------------------------- + +By default all repeatable migrations will extend from Phinx's `AbstractRepeatableMigration` class. +This can be set to a custom class that extends from `AbstractRepeatableMigration` by +setting ``repeatable_migration_base_class`` in your config: + +.. code-block:: yaml + + repeatable_migration_base_class: MyMagicalRepeatableMigration + Seed Paths ---------- -The second option specifies the path to your seed directory. Phinx uses +The third option specifies the path to your seed directory. Phinx uses ``%%PHINX_CONFIG_DIR%%/db/seeds`` by default. .. note:: @@ -140,6 +195,25 @@ You can also use the ``%%PHINX_CONFIG_DIR%%`` token in your path. paths: seeds: %%PHINX_CONFIG_DIR%%/your/relative/path +Seeds are captured with ``glob``, so you can define a pattern for multiple +directories. + +.. code-block:: yaml + + paths: + seeds: %%PHINX_CONFIG_DIR%%/module/*/{primary,secondary}/seeds + +Custom Seed Base +---------------- + +By default all seeds will extend from Phinx's `AbstractSeed` class. +This can be set to a custom class that extends from `AbstractSeed` by +setting ``seed_base_class`` in your config: + +.. code-block:: yaml + + seed_base_class: MyMagicalSeeder + Environments ------------ @@ -293,7 +367,7 @@ Aliases Template creation class names can be aliased and used with the ``--class`` command line option for the :doc:`Create Command `. -The aliased classes will still be required to implement the ``Phinx\Migration\CreationInterface`` interface. +The aliased classes will still be required to implement the ``Phinx\Templates\TemplateCreationInterface`` interface. .. code-block:: yaml @@ -304,8 +378,8 @@ The aliased classes will still be required to implement the ``Phinx\Migration\Cr Version Order ------ -When rolling back or printing the status of migrations, Phinx orders the executed migrations according to the +When rolling back or printing the status of migrations, Phinx orders the executed migrations according to the ``version_order`` option, which can have the following values: * ``creation`` (the default): migrations are ordered by their creation time, which is also part of their filename. -* ``execution``: migrations are ordered by their execution time, also known as start time. \ No newline at end of file +* ``execution``: migrations are ordered by their execution time, also known as start time. diff --git a/phinx.yml b/phinx.yml index 5a7ba85df..a6129e24c 100644 --- a/phinx.yml +++ b/phinx.yml @@ -1,5 +1,6 @@ paths: migrations: %%PHINX_CONFIG_DIR%%/db/migrations + repeatables: %%PHINX_CONFIG_DIR%%/db/repeatables seeds: %%PHINX_CONFIG_DIR%%/db/seeds environments: @@ -32,4 +33,4 @@ environments: port: 3306 charset: utf8 -version_order: creation \ No newline at end of file +version_order: creation diff --git a/src/Phinx/Config/Config.php b/src/Phinx/Config/Config.php index 4a052b21f..eb729d9d8 100644 --- a/src/Phinx/Config/Config.php +++ b/src/Phinx/Config/Config.php @@ -251,10 +251,7 @@ public function getMigrationPaths() } /** - * Gets the base class name for migrations. - * - * @param boolean $dropNamespace Return the base migration class name without the namespace. - * @return string + * {@inheritdoc} */ public function getMigrationBaseClassName($dropNamespace = true) { @@ -263,6 +260,32 @@ public function getMigrationBaseClassName($dropNamespace = true) return $dropNamespace ? substr(strrchr($className, '\\'), 1) ?: $className : $className; } + /** + * {@inheritdoc} + */ + public function getRepeatableMigrationPaths() + { + if (!isset($this->values['paths']['repeatables'])) { + throw new \UnexpectedValueException('Repeatables path missing from config file'); + } + + if (is_string($this->values['paths']['repeatables'])) { + $this->values['paths']['repeatables'] = array($this->values['paths']['repeatables']); + } + + return $this->values['paths']['repeatables']; + } + + /** + * @inheritDoc + */ + public function getRepeatableMigrationBaseClassName($dropNamespace = true) + { + $className = !isset($this->values['repeatable_migration_base_class']) ? 'Phinx\Repeatable\AbstractRepeatableMigration' : $this->values['repeatable_migration_base_class']; + + return $dropNamespace ? substr(strrchr($className, '\\'), 1) ?: $className : $className; + } + /** * {@inheritdoc} */ @@ -279,6 +302,16 @@ public function getSeedPaths() return $this->values['paths']['seeds']; } + /** + * @inheritDoc + */ + public function getSeedBaseClassName($dropNamespace = true) + { + $className = !isset($this->values['seed_base_class']) ? 'Phinx\Seed\AbstractSeed' : $this->values['seed_base_class']; + + return $dropNamespace ? substr(strrchr($className, '\\'), 1) ?: $className : $className; + } + /** * Get the template file name. * @@ -333,8 +366,6 @@ public function isVersionOrderCreationTime() return $versionOrder == self::VERSION_ORDER_CREATION_TIME; } - - /** * Replace tokens in the specified array. * diff --git a/src/Phinx/Config/ConfigInterface.php b/src/Phinx/Config/ConfigInterface.php index 2fa1f4896..6f0515b5a 100644 --- a/src/Phinx/Config/ConfigInterface.php +++ b/src/Phinx/Config/ConfigInterface.php @@ -95,6 +95,13 @@ public function getConfigFilePath(); */ public function getMigrationPaths(); + /** + * Gets the paths to search for repeatable migration files. + * + * @return string[] + */ + public function getRepeatableMigrationPaths(); + /** * Gets the paths to search for seed files. * @@ -137,4 +144,20 @@ public function isVersionOrderCreationTime(); * @return string */ public function getMigrationBaseClassName($dropNamespace = true); + + /** + * Gets the base class name for repeatable migrations. + * + * @param boolean $dropNamespace Return the base repeatable migration class name without the namespace. + * @return string + */ + public function getRepeatableMigrationBaseClassName($dropNamespace = true); + + /** + * Gets the base class name for seeders. + * + * @param boolean $dropNamespace Return the base seeder class name without the namespace. + * @return string + */ + public function getSeedBaseClassName($dropNamespace = true); } diff --git a/src/Phinx/Console/Command/AbstractCommand.php b/src/Phinx/Console/Command/AbstractCommand.php index b5bd83d87..d9556d479 100644 --- a/src/Phinx/Console/Command/AbstractCommand.php +++ b/src/Phinx/Console/Command/AbstractCommand.php @@ -49,12 +49,17 @@ abstract class AbstractCommand extends Command /** * The location of the default migration template. */ - const DEFAULT_MIGRATION_TEMPLATE = '/../../Migration/Migration.template.php.dist'; + const DEFAULT_MIGRATION_TEMPLATE = 'Migration/Migration.template.php.dist'; + + /** + * The location of the default repeatable migration template. + */ + const DEFAULT_REPEATABLE_MIGRATION_TEMPLATE = 'Repeatable/RepeatableMigration.template.php.dist'; /** * The location of the default seed template. */ - const DEFAULT_SEED_TEMPLATE = '/../../Seed/Seed.template.php.dist'; + const DEFAULT_SEED_TEMPLATE = 'Seed/Seed.template.php.dist'; /** * @var ConfigInterface @@ -76,6 +81,7 @@ abstract class AbstractCommand extends Command */ protected function configure() { + parent::configure(); $this->addOption('--configuration', '-c', InputOption::VALUE_REQUIRED, 'The configuration file to load'); $this->addOption('--parser', '-p', InputOption::VALUE_REQUIRED, 'Parser used to read the config file. Defaults to YAML'); } @@ -95,26 +101,7 @@ public function bootstrap(InputInterface $input, OutputInterface $output) $this->loadManager($input, $output); - // report the paths - $paths = $this->getConfig()->getMigrationPaths(); - - $output->writeln('using migration paths '); - - foreach (Util::globAll($paths) as $path) { - $output->writeln(' - ' . realpath($path) . ''); - } - - try { - $paths = $this->getConfig()->getSeedPaths(); - - $output->writeln('using seed paths '); - - foreach (Util::globAll($paths) as $path) { - $output->writeln(' - ' . realpath($path) . ''); - } - } catch (\UnexpectedValueException $e) { - // do nothing as seeds are optional - } + $this->reportPaths($input, $output); } /** @@ -293,70 +280,53 @@ protected function loadManager(InputInterface $input, OutputInterface $output) } /** - * Verify that the migration directory exists and is writable. + * Report a single set of paths. * - * @param string $path - * @throws \InvalidArgumentException - * @return void + * @param InputInterface $input + * @param OutputInterface $output + * @param array $paths + * @param string $pathsLabel */ - protected function verifyMigrationDirectory($path) - { - if (!is_dir($path)) { - throw new \InvalidArgumentException(sprintf( - 'Migration directory "%s" does not exist', - $path - )); - } + protected function reportPathSet(InputInterface $input, OutputInterface $output, array $paths, $pathsLabel) { + $output->writeln(sprintf('using %s paths ', strtolower($pathsLabel))); - if (!is_writable($path)) { - throw new \InvalidArgumentException(sprintf( - 'Migration directory "%s" is not writable', - $path - )); + foreach (Util::globAll($paths) as $path) { + $output->writeln(' - ' . realpath($path) . ''); } } /** - * Verify that the seed directory exists and is writable. + * Verify that a directory exists and is writable. * * @param string $path + * @param string $pathLabel * @throws \InvalidArgumentException * @return void */ - protected function verifySeedDirectory($path) + protected function verifyDirectory($path, $pathLabel) { if (!is_dir($path)) { throw new \InvalidArgumentException(sprintf( - 'Seed directory "%s" does not exist', + '%s directory "%s" does not exist', + ucfirst(strtolower($pathLabel)), $path )); } if (!is_writable($path)) { throw new \InvalidArgumentException(sprintf( - 'Seed directory "%s" is not writable', + '%s directory "%s" is not writable', + ucfirst(strtolower($pathLabel)), $path )); } } /** - * Returns the migration template filename. + * Report paths appropriate to the command * - * @return string - */ - protected function getMigrationTemplateFilename() - { - return __DIR__ . self::DEFAULT_MIGRATION_TEMPLATE; - } - - /** - * Returns the seed template filename. - * - * @return string + * @param InputInterface $input + * @param OutputInterface $output */ - protected function getSeedTemplateFilename() - { - return __DIR__ . self::DEFAULT_SEED_TEMPLATE; - } + abstract protected function reportPaths(InputInterface $input, OutputInterface $output); } diff --git a/src/Phinx/Console/Command/AbstractCreateCommand.php b/src/Phinx/Console/Command/AbstractCreateCommand.php new file mode 100644 index 000000000..8157de875 --- /dev/null +++ b/src/Phinx/Console/Command/AbstractCreateCommand.php @@ -0,0 +1,82 @@ + + */ +abstract class AbstractCreateCommand extends AbstractCommand +{ + /** + * The name of the interface that any external template creation class is required to implement. + */ + const CREATION_INTERFACE = 'Phinx\Templates\TemplateCreationInterface'; + + /** + * @inheritDoc + */ + protected function configure() + { + parent::configure(); + + // An alternative template. + $this->addOption( + 'template', + 't', + InputOption::VALUE_REQUIRED, + 'Use an alternative template' + ); + + // A classname to be used to gain access to the template content as well as the ability to + // have a callback once the migration file has been created. + $this->addOption( + 'class', + 'l', + InputOption::VALUE_REQUIRED, + 'Use a class implementing "'.self::CREATION_INTERFACE.'" to generate the template' + ); + + // Allow the migration path to be chosen non-interactively. + $this->addOption( + 'path', + null, + InputOption::VALUE_REQUIRED, + sprintf( + 'Specify the path in which to %s', + lcfirst($this->getDescription()) + ) + ); + + } +} diff --git a/src/Phinx/Console/Command/Breakpoint.php b/src/Phinx/Console/Command/Breakpoint.php index 1c2bc49be..9b5a33732 100644 --- a/src/Phinx/Console/Command/Breakpoint.php +++ b/src/Phinx/Console/Command/Breakpoint.php @@ -29,12 +29,15 @@ */ namespace Phinx\Console\Command; +use Phinx\Console\Command\Traits\MigrationCommandTrait; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Breakpoint extends AbstractCommand { + use MigrationCommandTrait; + /** * {@inheritdoc} */ @@ -95,4 +98,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->getManager()->toggleBreakpoint($environment, $version); } } + + /** + * {@inheritdoc} + */ + protected function reportPaths(InputInterface $input, OutputInterface $output) + { + $this->reportMigrationPaths($input, $output); + } } diff --git a/src/Phinx/Console/Command/Create.php b/src/Phinx/Console/Command/Create.php index 5a9a482b7..4c55f9e38 100644 --- a/src/Phinx/Console/Command/Create.php +++ b/src/Phinx/Console/Command/Create.php @@ -28,7 +28,8 @@ */ namespace Phinx\Console\Command; -use Phinx\Migration\CreationInterface; +use Phinx\Console\Command\Traits\MigrationCommandTrait; +use Phinx\Templates\TemplateCreationInterface; use Phinx\Util\Util; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -39,10 +40,12 @@ class Create extends AbstractCommand { + use MigrationCommandTrait; + /** * The name of the interface that any external template creation class is required to implement. */ - const CREATION_INTERFACE = 'Phinx\Migration\CreationInterface'; + const CREATION_INTERFACE = 'Phinx\Templates\TemplateCreationInterface'; /** * {@inheritdoc} @@ -184,7 +187,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } // Compute the file path - $fileName = Util::mapClassNameToFileName($className); + $fileName = Util::mapClassNameToMigrationFileName($className); $filePath = $path . DIRECTORY_SEPARATOR . $fileName; if (is_file($filePath)) { @@ -265,8 +268,9 @@ protected function execute(InputInterface $input, OutputInterface $output) // Determine the appropriate mechanism to get the template if ($creationClassName) { // Get the template from the creation class + /** @var TemplateCreationInterface $creationClass */ $creationClass = new $creationClassName($input, $output); - $contents = $creationClass->getMigrationTemplate(); + $contents = $creationClass->getTemplate(); } else { // Load the alternative template if it is defined. $contents = file_get_contents($altTemplate ?: $this->getMigrationTemplateFilename()); @@ -290,7 +294,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // Do we need to do the post creation call to the creation class? if (isset($creationClass)) { - $creationClass->postMigrationCreation($filePath, $className, $this->getConfig()->getMigrationBaseClassName()); + $creationClass->postTemplateCreation($filePath, $className, $this->getConfig()->getMigrationBaseClassName()); } $output->writeln('using migration base class ' . $classes['$useClassName']); @@ -305,4 +309,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln('created ' . str_replace(getcwd() . DIRECTORY_SEPARATOR, '', $filePath)); } + + /** + * {@inheritdoc} + */ + protected function reportPaths(InputInterface $input, OutputInterface $output) + { + $this->reportMigrationPaths($input, $output); + } } diff --git a/src/Phinx/Console/Command/Migrate.php b/src/Phinx/Console/Command/Migrate.php index aacce82c3..fc80c2a88 100644 --- a/src/Phinx/Console/Command/Migrate.php +++ b/src/Phinx/Console/Command/Migrate.php @@ -28,12 +28,15 @@ */ namespace Phinx\Console\Command; +use Phinx\Console\Command\Traits\MigrationCommandTrait; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Migrate extends AbstractCommand { + use MigrationCommandTrait; + /** * {@inheritdoc} */ @@ -119,4 +122,12 @@ protected function execute(InputInterface $input, OutputInterface $output) return 0; } + + /** + * {@inheritdoc} + */ + protected function reportPaths(InputInterface $input, OutputInterface $output) + { + $this->reportMigrationPaths($input, $output); + } } diff --git a/src/Phinx/Console/Command/Rollback.php b/src/Phinx/Console/Command/Rollback.php index 495720672..949714429 100644 --- a/src/Phinx/Console/Command/Rollback.php +++ b/src/Phinx/Console/Command/Rollback.php @@ -28,6 +28,7 @@ */ namespace Phinx\Console\Command; +use Phinx\Console\Command\Traits\MigrationCommandTrait; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -35,6 +36,8 @@ class Rollback extends AbstractCommand { + use MigrationCommandTrait; + /** * {@inheritdoc} */ @@ -107,7 +110,7 @@ protected function execute(InputInterface $input, OutputInterface $output) if (isset($envOptions['name'])) { $output->writeln('using database ' . $envOptions['name']); } - + $versionOrder = $this->getConfig()->getVersionOrder(); $output->writeln('ordering by ' . $versionOrder . " time"); @@ -164,4 +167,12 @@ public function getTargetFromDate($date) return $dateTime->format('YmdHis'); } + + /** + * {@inheritdoc} + */ + protected function reportPaths(InputInterface $input, OutputInterface $output) + { + $this->reportMigrationPaths($input, $output); + } } diff --git a/src/Phinx/Console/Command/SeedCreate.php b/src/Phinx/Console/Command/SeedCreate.php index 3c32e45fc..968078d80 100644 --- a/src/Phinx/Console/Command/SeedCreate.php +++ b/src/Phinx/Console/Command/SeedCreate.php @@ -28,6 +28,7 @@ */ namespace Phinx\Console\Command; +use Phinx\Console\Command\Traits\SeedCommandTrait; use Phinx\Util\Util; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -38,6 +39,8 @@ class SeedCreate extends AbstractCommand { + use SeedCommandTrait; + /** * {@inheritdoc} */ @@ -190,4 +193,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln('using seed base class ' . $classes['$useClassName']); $output->writeln('created .' . str_replace(getcwd(), '', $filePath)); } + + /** + * {@inheritdoc} + */ + protected function reportPaths(InputInterface $input, OutputInterface $output) + { + $this->reportSeedPaths($input, $output); + } } diff --git a/src/Phinx/Console/Command/SeedRun.php b/src/Phinx/Console/Command/SeedRun.php index 86962860d..d545f1aea 100644 --- a/src/Phinx/Console/Command/SeedRun.php +++ b/src/Phinx/Console/Command/SeedRun.php @@ -28,12 +28,15 @@ */ namespace Phinx\Console\Command; +use Phinx\Console\Command\Traits\SeedCommandTrait; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class SeedRun extends AbstractCommand { + use SeedCommandTrait; + /** * {@inheritdoc} */ @@ -120,4 +123,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln(''); $output->writeln('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); } + + /** + * {@inheritdoc} + */ + protected function reportPaths(InputInterface $input, OutputInterface $output) + { + $this->reportSeedPaths($input, $output); + } } diff --git a/src/Phinx/Console/Command/Status.php b/src/Phinx/Console/Command/Status.php index 3d53ecc36..7498a8f98 100644 --- a/src/Phinx/Console/Command/Status.php +++ b/src/Phinx/Console/Command/Status.php @@ -28,12 +28,15 @@ */ namespace Phinx\Console\Command; +use Phinx\Console\Command\Traits\MigrationCommandTrait; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Status extends AbstractCommand { + use MigrationCommandTrait; + /** * {@inheritdoc} */ @@ -87,4 +90,12 @@ protected function execute(InputInterface $input, OutputInterface $output) // print the status return $this->getManager()->printStatus($environment, $format); } + + /** + * {@inheritdoc} + */ + protected function reportPaths(InputInterface $input, OutputInterface $output) + { + $this->reportMigrationPaths($input, $output); + } } diff --git a/src/Phinx/Console/Command/Test.php b/src/Phinx/Console/Command/Test.php index 35bc26c88..4711e57b1 100644 --- a/src/Phinx/Console/Command/Test.php +++ b/src/Phinx/Console/Command/Test.php @@ -26,8 +26,12 @@ * @package Phinx * @subpackage Phinx\Console */ + namespace Phinx\Console\Command; +use Phinx\Console\Command\Traits\MigrationCommandTrait; +use Phinx\Console\Command\Traits\RepeatableMigrationCommandTrait; +use Phinx\Console\Command\Traits\SeedCommandTrait; use Phinx\Migration\Manager\Environment; use Phinx\Util\Util; use Symfony\Component\Console\Input\InputInterface; @@ -39,6 +43,10 @@ */ class Test extends AbstractCommand { + use MigrationCommandTrait; + use RepeatableMigrationCommandTrait; + use SeedCommandTrait; + /** * {@inheritdoc} */ @@ -49,16 +57,16 @@ protected function configure() $this->addOption('--environment', '-e', InputOption::VALUE_REQUIRED, 'The target environment'); $this->setName('test') - ->setDescription('Verify the configuration file') - ->setHelp( -<<setDescription('Verify the configuration file') + ->setHelp( + <<test command verifies the YAML configuration file and optionally an environment phinx test phinx test -e development EOT - ); + ); } /** @@ -66,34 +74,37 @@ protected function configure() * * @param InputInterface $input * @param OutputInterface $output + * * @throws \RuntimeException * @throws \InvalidArgumentException * @return void */ protected function execute(InputInterface $input, OutputInterface $output) { - $this->loadConfig($input, $output); - $this->loadManager($input, $output); + $this->bootstrap($input, $output); - // Verify the migrations path(s) - array_map( - [$this, 'verifyMigrationDirectory'], - Util::globAll($this->getConfig()->getMigrationPaths()) - ); - - // Verify the seed path(s) - array_map( - [$this, 'verifySeedDirectory'], - Util::globAll($this->getConfig()->getSeedPaths()) - ); + // Verify the path(s) + foreach ( + [ + 'verifyMigrationDirectory' => $this->getConfig()->getMigrationPaths(), + 'verifyRepeatableMigrationDirectory' => $this->getConfig()->getRepeatableMigrationPaths(), + 'verifySeedDirectory' => $this->getConfig()->getSeedPaths(), + ] as $verifyDirectoryMethod => $paths) { + array_map( + [$this, $verifyDirectoryMethod], + Util::globAll($paths) + ); + } $envName = $input->getOption('environment'); if ($envName) { if (!$this->getConfig()->hasEnvironment($envName)) { - throw new \InvalidArgumentException(sprintf( - 'The environment "%s" does not exist', - $envName - )); + throw new \InvalidArgumentException( + sprintf( + 'The environment "%s" does not exist', + $envName + ) + ); } $output->writeln(sprintf('validating environment %s', $envName)); @@ -107,4 +118,14 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln('success!'); } + + /** + * {@inheritdoc} + */ + protected function reportPaths(InputInterface $input, OutputInterface $output) + { + $this->reportMigrationPaths($input, $output); + $this->reportRepeatableMigrationPaths($input, $output); + $this->reportSeedPaths($input, $output); + } } diff --git a/src/Phinx/Console/Command/Traits/AbstractCommandTrait.php b/src/Phinx/Console/Command/Traits/AbstractCommandTrait.php new file mode 100644 index 000000000..77657824d --- /dev/null +++ b/src/Phinx/Console/Command/Traits/AbstractCommandTrait.php @@ -0,0 +1,12 @@ +reportPathSet($input, $output, $this->getConfig()->getMigrationPaths(), 'migration'); + } + + /** + * Verify that the migration directory exists and is writable. + * + * @param string $path + * @throws \InvalidArgumentException + */ + protected function verifyMigrationDirectory($path) + { + $this->verifyDirectory($path, 'migration'); + } + + /** + * Returns the migration template filename. + * + * @return string + */ + protected function getMigrationTemplateFilename() + { + return __DIR__ . '/../../../' . AbstractCommand::DEFAULT_MIGRATION_TEMPLATE; + } +} diff --git a/src/Phinx/Console/Command/Traits/RepeatableMigrationCommandTrait.php b/src/Phinx/Console/Command/Traits/RepeatableMigrationCommandTrait.php new file mode 100644 index 000000000..7f2f000cd --- /dev/null +++ b/src/Phinx/Console/Command/Traits/RepeatableMigrationCommandTrait.php @@ -0,0 +1,70 @@ +reportPathSet($input, $output, $this->getConfig()->getRepeatableMigrationPaths(), 'repeatable migration'); + } + + /** + * Verify that the repeatable migration directory exists and is writable. + * + * @param string $path + * @throws \InvalidArgumentException + */ + protected function verifyRepeatableMigrationDirectory($path) + { + $this->verifyDirectory($path, 'repeatable migration'); + } + + /** + * Returns the repeatable migration template filename. + * + * @return string + */ + protected function getRepeatableMigrationTemplateFilename() + { + return __DIR__ . '/../../../' . AbstractCommand::DEFAULT_REPEATABLE_MIGRATION_TEMPLATE; + } +} diff --git a/src/Phinx/Console/Command/Traits/SeedCommandTrait.php b/src/Phinx/Console/Command/Traits/SeedCommandTrait.php new file mode 100644 index 000000000..13e89395c --- /dev/null +++ b/src/Phinx/Console/Command/Traits/SeedCommandTrait.php @@ -0,0 +1,70 @@ +reportPathSet($input, $output, $this->getConfig()->getSeedPaths(), 'seed'); + } + + /** + * Verify that the seed directory exists and is writable. + * + * @param string $path + * @throws \InvalidArgumentException + */ + protected function verifySeedDirectory($path) + { + $this->verifyDirectory($path, 'seed'); + } + + /** + * Returns the seed template filename. + * + * @return string + */ + protected function getSeedTemplateFilename() + { + return __DIR__ . '/../../../' . AbstractCommand::DEFAULT_SEED_TEMPLATE; + } +} diff --git a/src/Phinx/Console/PhinxApplication.php b/src/Phinx/Console/PhinxApplication.php index d641b98df..2246cbd8e 100644 --- a/src/Phinx/Console/PhinxApplication.php +++ b/src/Phinx/Console/PhinxApplication.php @@ -28,10 +28,10 @@ */ namespace Phinx\Console; +use Phinx\Console\Command; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Phinx\Console\Command; /** * Phinx console application. diff --git a/src/Phinx/Migration/Manager.php b/src/Phinx/Migration/Manager.php index 6c7ca9f60..c1271fe79 100644 --- a/src/Phinx/Migration/Manager.php +++ b/src/Phinx/Migration/Manager.php @@ -147,7 +147,7 @@ public function printStatus($environment, $format = null) } if (empty($sortedMigrations) && !empty($missingVersions)) { - // this means we have no up migrations, so we write all the missing versions already so they show up + // this means we have no up migrations, so we write all the missing versions already so they show up // before any possible down migration foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) { $this->printMissingVersion($missingVersion, $maxNameLength); @@ -156,7 +156,7 @@ public function printStatus($environment, $format = null) } } - // any migration left in the migrations (ie. not unset when sorting the migrations by the version order) is + // any migration left in the migrations (ie. not unset when sorting the migrations by the version order) is // a migration that is down, so we add them to the end of the sorted migrations list if (!empty($migrations)) { $sortedMigrations = array_merge($sortedMigrations, $migrations); @@ -174,7 +174,7 @@ public function printStatus($environment, $format = null) } else { if ($missingVersion['start_time'] > $version['start_time']) { break; - } elseif ($missingVersion['start_time'] == $version['start_time'] && + } elseif ($missingVersion['start_time'] == $version['start_time'] && $missingVersion['version'] > $version['version']) { break; } @@ -438,7 +438,7 @@ public function rollback($environment, $target = null, $force = false, $targetMu if (isset($migrations[$versionCreationTime])) { array_unshift($sortedMigrations, $migrations[$versionCreationTime]); } else { - // this means the version is missing so we unset it so that we don't consider it when rolling back + // this means the version is missing so we unset it so that we don't consider it when rolling back // migrations (or choosing the last up version as target) unset($executedVersions[$versionCreationTime]); } @@ -626,7 +626,7 @@ public function setMigrations(array $migrations) } /** - * Gets an array of the database migrations, indexed by migration name (aka creation time) and sorted in ascending + * Gets an array of the database migrations, indexed by migration name (aka creation time) and sorted in ascending * order * * @throws \InvalidArgumentException @@ -651,7 +651,7 @@ public function getMigrations() } // convert the filename to a class name - $class = Util::mapFileNameToClassName(basename($filePath)); + $class = Util::mapMigrationFileNameToClassName(basename($filePath)); if (isset($fileNames[$class])) { throw new \InvalidArgumentException(sprintf( diff --git a/src/Phinx/Seed/SeedInterface.php b/src/Phinx/Seed/SeedInterface.php index 5ff36bf37..8b868e81b 100644 --- a/src/Phinx/Seed/SeedInterface.php +++ b/src/Phinx/Seed/SeedInterface.php @@ -30,7 +30,6 @@ use Phinx\Db\Adapter\AdapterInterface; use Phinx\Db\Table; -use Phinx\Migration\MigrationInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; diff --git a/src/Phinx/Migration/AbstractTemplateCreation.php b/src/Phinx/Templates/AbstractTemplateCreation.php similarity index 95% rename from src/Phinx/Migration/AbstractTemplateCreation.php rename to src/Phinx/Templates/AbstractTemplateCreation.php index 504212fb4..2a83509ad 100644 --- a/src/Phinx/Migration/AbstractTemplateCreation.php +++ b/src/Phinx/Templates/AbstractTemplateCreation.php @@ -26,12 +26,12 @@ * @package Phinx * @subpackage Phinx\Migration */ -namespace Phinx\Migration; +namespace Phinx\Templates; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -abstract class AbstractTemplateCreation implements CreationInterface +abstract class AbstractTemplateCreation implements TemplateCreationInterface { /** * @var InputInterface diff --git a/src/Phinx/Migration/CreationInterface.php b/src/Phinx/Templates/TemplateCreationInterface.php similarity index 76% rename from src/Phinx/Migration/CreationInterface.php rename to src/Phinx/Templates/TemplateCreationInterface.php index 2c7d91efe..0e5a4358b 100644 --- a/src/Phinx/Migration/CreationInterface.php +++ b/src/Phinx/Templates/TemplateCreationInterface.php @@ -26,20 +26,20 @@ * @package Phinx * @subpackage Phinx\Migration */ -namespace Phinx\Migration; +namespace Phinx\Templates; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** - * Migration interface + * Template creation interface * * @author Richard Quadling */ -interface CreationInterface +interface TemplateCreationInterface { /** - * CreationInterface constructor. + * TemplateCreationInterface constructor. * * @param InputInterface|null $input * @param OutputInterface|null $output @@ -49,14 +49,14 @@ public function __construct(InputInterface $input = null, OutputInterface $outpu /** * @param InputInterface $input * - * @return CreationInterface + * @return TemplateCreationInterface */ public function setInput(InputInterface $input); /** * @param OutputInterface $output * - * @return CreationInterface + * @return TemplateCreationInterface */ public function setOutput(OutputInterface $output); @@ -71,24 +71,25 @@ public function getInput(); public function getOutput(); /** - * Get the migration template. + * Get the template. * - * This will be the content that Phinx will amend to generate the migration file. + * This will be the content that Phinx will amend to generate the template file. * * @return string The content of the template for Phinx to amend. */ - public function getMigrationTemplate(); + public function getTemplate(); /** - * Post Migration Creation. + * Post Template Creation. * - * Once the migration file has been created, this method will be called, allowing any additional + * Once the template file has been created, this method will be called, allowing any additional * processing, specific to the template to be performed. * - * @param string $migrationFilename The name of the newly created migration. - * @param string $className The class name. - * @param string $baseClassName The name of the base class. + * @param string $filename The name of the newly created file. + * @param string $className The class name. + * @param string $baseClassName The name of the base class. + * * @return void */ - public function postMigrationCreation($migrationFilename, $className, $baseClassName); + public function postTemplateCreation($filename, $className, $baseClassName); } diff --git a/src/Phinx/Util/Traits/MigrationUtilTrait.php b/src/Phinx/Util/Traits/MigrationUtilTrait.php new file mode 100644 index 000000000..7cb1cad12 --- /dev/null +++ b/src/Phinx/Util/Traits/MigrationUtilTrait.php @@ -0,0 +1,110 @@ +format(static::DATE_FORMAT); } - /** - * Gets an array of all the existing migration class names. - * - * @return string[] - */ - public static function getExistingMigrationClassNames($path) - { - $classNames = array(); - - if (!is_dir($path)) { - return $classNames; - } - - // filter the files to only get the ones that match our naming scheme - $phpFiles = glob($path . DIRECTORY_SEPARATOR . '*.php'); - - foreach ($phpFiles as $filePath) { - if (preg_match('/([0-9]+)_([_a-z0-9]*).php/', basename($filePath))) { - $classNames[] = static::mapFileNameToClassName(basename($filePath)); - } - } - - return $classNames; - } - - /** - * Get the version from the beginning of a file name. - * - * @param string $fileName File Name - * @return string - */ - public static function getVersionFromFileName($fileName) - { - $matches = array(); - preg_match('/^[0-9]+/', basename($fileName), $matches); - return $matches[0]; - } - - /** - * Turn migration names like 'CreateUserTable' into file names like - * '12345678901234_create_user_table.php' or 'LimitResourceNamesTo30Chars' into - * '12345678901234_limit_resource_names_to_30_chars.php'. - * - * @param string $className Class Name - * @return string - */ - public static function mapClassNameToFileName($className) - { - $arr = preg_split('/(?=[A-Z])/', $className); - unset($arr[0]); // remove the first element ('') - $fileName = static::getCurrentTimestamp() . '_' . strtolower(implode($arr, '_')) . '.php'; - return $fileName; - } - - /** - * Turn file names like '12345678901234_create_user_table.php' into class - * names like 'CreateUserTable'. - * - * @param string $fileName File Name - * @return string - */ - public static function mapFileNameToClassName($fileName) - { - $matches = array(); - if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches)) { - $fileName = $matches[1]; - } - - return str_replace(' ', '', ucwords(str_replace('_', ' ', $fileName))); - } - - /** - * Check if a migration class name is unique regardless of the - * timestamp. - * - * This method takes a class name and a path to a migrations directory. - * - * Migration class names must be in CamelCase format. - * e.g: CreateUserTable or AddIndexToPostsTable. - * - * Single words are not allowed on their own. - * - * @param string $className Class Name - * @param string $path Path - * @return boolean - */ - public static function isUniqueMigrationClassName($className, $path) - { - $existingClassNames = static::getExistingMigrationClassNames($path); - return !(in_array($className, $existingClassNames)); - } - /** * Check if a migration/seed class name is valid. * @@ -164,33 +83,9 @@ public static function isValidPhinxClassName($className) return (bool) preg_match('/^([A-Z][a-z0-9]+)+$/', $className); } - /** - * Check if a migration file name is valid. - * - * @param string $fileName File Name - * @return boolean - */ - public static function isValidMigrationFileName($fileName) - { - $matches = array(); - return preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches); - } - - /** - * Check if a seed file name is valid. - * - * @param string $fileName File Name - * @return boolean - */ - public static function isValidSeedFileName($fileName) - { - $matches = array(); - return preg_match(static::SEED_FILE_NAME_PATTERN, $fileName, $matches); - } - /** * Expands a set of paths with curly braces (if supported by the OS). - * + * * @param array $paths * @return array */ diff --git a/tests/Phinx/Config/ConfigFileTest.php b/tests/Phinx/Config/ConfigFileTest.php index 4c48f4896..808bf1f10 100644 --- a/tests/Phinx/Config/ConfigFileTest.php +++ b/tests/Phinx/Config/ConfigFileTest.php @@ -2,11 +2,10 @@ namespace Test\Phinx\Config; -use Phinx\Console\Command\AbstractCommand; -use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputOption; +use Test\Phinx\Config\Fixtures\VoidCommand; class ConfigFileTest extends \PHPUnit_Framework_TestCase { @@ -116,16 +115,3 @@ public function notWorkingProvider() ); } } - -/** - * Class VoidCommand : used to expose locateConfigFile To testing - * - * @package Test\Phinx\Config - */ -class VoidCommand extends AbstractCommand -{ - public function locateConfigFile(InputInterface $input) - { - return parent::locateConfigFile($input); - } -} diff --git a/tests/Phinx/Config/Fixtures/VoidCommand.php b/tests/Phinx/Config/Fixtures/VoidCommand.php new file mode 100644 index 000000000..c99328a4d --- /dev/null +++ b/tests/Phinx/Config/Fixtures/VoidCommand.php @@ -0,0 +1,26 @@ + '\Test\Phinx\Console\Command\TemplateGenerators\DoesNotImplementRequiredInterface'), - 'The class "\Test\Phinx\Console\Command\TemplateGenerators\DoesNotImplementRequiredInterface" does not implement the required interface "Phinx\Migration\CreationInterface"', + 'The class "\Test\Phinx\Console\Command\TemplateGenerators\DoesNotImplementRequiredInterface" does not implement the required interface "Phinx\Templates\TemplateCreationInterface"', ), array( array( @@ -174,7 +174,7 @@ public function provideFailingTemplateGenerator() array( '--class' => 'PoorInterface', ), - 'The class "\Test\Phinx\Console\Command\TemplateGenerators\DoesNotImplementRequiredInterface" via the alias "PoorInterface" does not implement the required interface "Phinx\Migration\CreationInterface"', + 'The class "\Test\Phinx\Console\Command\TemplateGenerators\DoesNotImplementRequiredInterface" via the alias "PoorInterface" does not implement the required interface "Phinx\Templates\TemplateCreationInterface"', ), ); } diff --git a/tests/Phinx/Console/Command/TemplateGenerators/DoesNotImplementRequiredInterface.php b/tests/Phinx/Console/Command/TemplateGenerators/DoesNotImplementRequiredInterface.php index d34f1d0b2..f706a3275 100644 --- a/tests/Phinx/Console/Command/TemplateGenerators/DoesNotImplementRequiredInterface.php +++ b/tests/Phinx/Console/Command/TemplateGenerators/DoesNotImplementRequiredInterface.php @@ -1,7 +1,8 @@ $expectedResult) { - $this->assertRegExp($expectedResult, Util::mapClassNameToFileName($input)); + $this->assertRegExp($expectedResult, Util::mapClassNameToMigrationFileName($input)); } } @@ -67,7 +67,7 @@ public function testMapFileNameToClassName() ); foreach ($expectedResults as $input => $expectedResult) { - $this->assertEquals($expectedResult, Util::mapFileNameToClassName($input)); + $this->assertEquals($expectedResult, Util::mapMigrationFileNameToClassName($input)); } }