This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

19
vendor/doctrine/migrations/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2006-2018 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

109
vendor/doctrine/migrations/README.md vendored Normal file
View File

@@ -0,0 +1,109 @@
# Doctrine Database Migrations
## Status
[![Build Status](https://travis-ci.org/doctrine/migrations.svg)](https://travis-ci.org/doctrine/migrations)
[![Dependency Status](https://www.versioneye.com/php/doctrine:migrations/badge.svg)](https://www.versioneye.com/php/doctrine:migrations/)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/migrations/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/doctrine/migrations/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/migrations/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/doctrine/migrations/?branch=master)
## Official Documentation
All available documentation can be found [here](http://docs.doctrine-project.org/projects/doctrine-migrations/en/latest/).
The repository containing the documentation is [there](https://github.com/doctrine/migrations-documentation).
## Working with Doctrine Migrations
### Using the integration of your framework
* [Symfony](https://packagist.org/packages/doctrine/doctrine-migrations-bundle)
* [ZF2](https://packagist.org/packages/doctrine/doctrine-orm-module)
* [laravel](https://packagist.org/packages/laravel-doctrine/migrations)
* [Silex](https://packagist.org/packages/kurl/silex-doctrine-migrations-provider)
* [Silex](https://packagist.org/packages/dbtlr/silex-doctrine-migrations)
* [nette](https://packagist.org/packages/zenify/doctrine-migrations)
* others...
### Using composer
```composer require doctrine/migrations```
### Downloading the latest phar release
You can download the [doctrine migrations phar](https://github.com/doctrine/migrations/releases) directly on the release page
### Building Your own Phar
Make sure Composer and all necessary dependencies are installed:
```bash
curl -s https://getcomposer.org/installer | php
php composer.phar install
```
Make sure that the Box project is installed:
```bash
curl -LSs http://box-project.github.io/box2/installer.php | php
```
Build the PHAR archive:
```bash
php box.phar build
```
The `doctrine-migrations.phar` archive is built in the `build` directory.
#### Creating archive disabled by INI setting
If you receive an error that looks like:
creating archive "build/doctrine-migrations.phar" disabled by INI setting
This can be fixed by setting the following in your php.ini:
```ini
; http://php.net/phar.readonly
phar.readonly = Off
```
## Installing Dependencies
To install dependencies run a composer update:
```composer update```
## symfony 2.3 users
Doctrine migration need the doctrine/orm 2.4, you need to [update your composer.json](https://github.com/symfony/symfony-standard/blob/v2.3.28/composer.json#L12) to the last version of it for symfony 2.3.
That version is compatible with the doctrine/orm 2.4 and there are [very little upgrade needed](https://github.com/doctrine/doctrine2/blob/master/UPGRADE.md#upgrade-to-24).
## Running the unit tests
To run the tests, you need the sqlite extension for php.
On Unix-like systems, install:
- php5-sqlite
On Windows, enable the extension by uncommenting the following lines in php.ini
```
extension = php_pdo_sqlite.dll
extension = php_sqlite3.dll
extension_dir = ext
```
Running the tests from the project root:
```
./vendor/bin/phpunit
```
On Windows run phpunit from the full path
```
php vendor/phpunit/phpunit/phpunit
```
This appears to be some bug.
Happy testing :-)

47
vendor/doctrine/migrations/UPGRADE.md vendored Normal file
View File

@@ -0,0 +1,47 @@
UPGRADE TO 1.8
==============
## AbstractMigration
The `Doctrine\DBAL\Migrations\AbstractMigration` class has been deprecated and replaced with
`Doctrine\Migrations\AbstractMigration`. It will be removed in 2.0 so please update to use the class in the new namespace.
UPGRADE FROM 1.0-alpha1 to 1.0.0-alpha3
=======================================
## AbstractMigration
### Before:
The method `getName()` was defined and it's implementation would change the order in which the migration would be processed.
It would cause discrepancies between the file order in a file browser and the order of execution of the migrations.
### After:
The `getName()` method as been removed | set final and new `getDescription()` method has been added.
The goal of this method is to be able to provide context for the migration.
This context is shown for the last migrated migration when the status command is called.
## --write-sql option from the migrate command
### Before:
The `--write-sql` option would only output sql contained in the migration and would not update the table containing the migrated migrations.
### After:
That option now also output the sql queries necessary to update the table containing the state of the migrations.
If you want to go back to the previous behavior just make a request on the bug tracker as for now the need for it is not very clear.
## MigrationsVersion::VERSION
### Before:
`MigrationsVersion::VERSION` used to be a property.
The returned value was fanciful.
### After:
It is now a a function so that a different value can be automatically send back if it's a modified version that's used.
The returned value is now the git tag.
The tag is in lowercase as the other doctrine projects.

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env php
<?php
include('doctrine-migrations.php');

View File

@@ -0,0 +1,72 @@
<?php
$autoloadFiles = [
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php'
];
$autoloader = false;
foreach ($autoloadFiles as $autoloadFile) {
if (file_exists($autoloadFile)) {
require_once $autoloadFile;
$autoloader = true;
}
}
if (!$autoloader) {
if (extension_loaded('phar') && ($uri = Phar::running())) {
echo 'The phar has been built without dependencies' . PHP_EOL;
}
die('vendor/autoload.php could not be found. Did you run `php composer.phar install`?');
}
// Support for using the Doctrine ORM convention of providing a `cli-config.php` file.
$directories = [getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config'];
$configFile = null;
foreach ($directories as $directory) {
$configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php';
if (file_exists($configFile)) {
break;
}
}
$helperSet = null;
if (file_exists($configFile)) {
if ( ! is_readable($configFile)) {
trigger_error(
'Configuration file [' . $configFile . '] does not have read permission.', E_USER_ERROR
);
}
$helperSet = require $configFile;
if ( ! ($helperSet instanceof \Symfony\Component\Console\Helper\HelperSet)) {
foreach ($GLOBALS as $helperSetCandidate) {
if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) {
$helperSet = $helperSetCandidate;
break;
}
}
}
}
$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();
if(class_exists('\Symfony\Component\Console\Helper\QuestionHelper')) {
$helperSet->set(new \Symfony\Component\Console\Helper\QuestionHelper(), 'question');
} else {
$helperSet->set(new \Symfony\Component\Console\Helper\DialogHelper(), 'dialog');
}
$input = file_exists('migrations-input.php')
? include 'migrations-input.php' : null;
$output = file_exists('migrations-output.php')
? include 'migrations-output.php' : null;
$cli = \Doctrine\DBAL\Migrations\Tools\Console\ConsoleRunner::createApplication($helperSet);
$cli->run($input, $output);

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
git fetch origin master --tags
if [ -f composer.lock ]; then
rm composer.lock
fi
composer install --no-dev --optimize-autoloader
mkdir -p build
if [ ! -f box.phar ]; then
wget https://github.com/box-project/box2/releases/download/2.6.0/box-2.6.0.phar -O box.phar
fi
php box.phar build -vv

View File

@@ -0,0 +1,51 @@
{
"name": "doctrine/migrations",
"type": "library",
"description": "Database Schema migrations using Doctrine DBAL",
"keywords": ["migrations", "database"],
"homepage": "https://www.doctrine-project.org/projects/migrations.html",
"license": "MIT",
"authors": [
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Michael Simonson", "email": "contact@mikesimonson.com" }
],
"require": {
"php": "^7.1",
"doctrine/dbal": "~2.6",
"symfony/console": "~3.3|^4.0",
"ocramius/proxy-manager": "^1.0|^2.0"
},
"require-dev": {
"doctrine/orm": "~2.5",
"symfony/yaml": "~3.3|^4.0",
"phpunit/phpunit": "~7.0",
"doctrine/coding-standard": "^1.0",
"jdorn/sql-formatter": "~1.1",
"mikey179/vfsStream": "^1.6",
"squizlabs/php_codesniffer": "^3.0"
},
"suggest": {
"jdorn/sql-formatter": "Allows to generate formatted SQL with the diff command.",
"symfony/yaml": "Allows the use of yaml for migration configuration files."
},
"autoload": {
"psr-4": {
"Doctrine\\DBAL\\Migrations\\": "lib/Doctrine/DBAL/Migrations",
"Doctrine\\Migrations\\": "lib/Doctrine/Migrations"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\DBAL\\Migrations\\Tests\\": "tests/Doctrine/DBAL/Migrations/Tests"
}
},
"extra": {
"branch-alias": {
"dev-master": "v1.8.x-dev"
}
},
"bin": [
"bin/doctrine-migrations"
]
}

View File

@@ -0,0 +1,22 @@
Migrations Documentation
=================
The Doctrine Migrations documentation is a reference guide to everything you need
to know about the migrations project.
Getting Help
------------
If this documentation is not helping to answer questions you have about
Doctrine Migrations don't panic. You can get help from different sources:
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
- Slack chat room `#migrations <https://www.doctrine-project.org/slack>`_
- Report a bug on `GitHub <https://github.com/doctrine/migrations/issues>`_.
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-migrations>`_
Getting Started
---------------
The best way to get started is with the :doc:`Introduction <introduction>` section.
Use the sidebar to browse other documentation for the Doctrine PHP Migrations project.

View File

@@ -0,0 +1,113 @@
Custom Integration
==================
For up to date code take a look at `the doctrine migrations command line integration <https://github.com/doctrine/migrations/blob/master/bin/doctrine-migrations.php>`_.
None the less the main steps required to make a functional integration are presented below.
Installation
~~~~~~~~~~~~
First you need to require Doctrine Migrations as a dependency of your code
.. code-block:: sh
composer require doctrine/migrations
Then you have to require the composer autoloader to use the classes from the `Doctrine\DBAL\Migrations`
namespace in your project:
.. code-block:: php
$autoloadFiles = array(
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php'
);
$autoloader = false;
foreach ($autoloadFiles as $autoloadFile) {
if (file_exists($autoloadFile)) {
require_once $autoloadFile;
$autoloader = true;
}
}
if (!$autoloader) {
die('vendor/autoload.php could not be found. Did you run `php composer.phar install`?');
}
Now the above autoloader is able to load a class like the following:
.. code-block:: bash
/path/to/migrations/lib/Doctrine/DBAL/Migrations/Migrations/Migration.php
Register Console Commands
~~~~~~~~~~~~~~~~~~~~~~~~~
Now that we have setup the autoloaders we are ready to add the migration console
commands to our `Doctrine Command Line Interface <http://doctrine-orm.readthedocs.org/en/latest/reference/tools.html#adding-own-commands>`_:
.. code-block:: php
// ...
$cli->addCommands(array(
// Migrations Commands
new \Doctrine\DBAL\Migrations\Tools\Console\Command\ExecuteCommand(),
new \Doctrine\DBAL\Migrations\Tools\Console\Command\GenerateCommand(),
new \Doctrine\DBAL\Migrations\Tools\Console\Command\LatestCommand(),
new \Doctrine\DBAL\Migrations\Tools\Console\Command\MigrateCommand(),
new \Doctrine\DBAL\Migrations\Tools\Console\Command\StatusCommand(),
new \Doctrine\DBAL\Migrations\Tools\Console\Command\VersionCommand()
));
Register Console helpers
~~~~~~~~~~~~~~~~~~~~~~~~
Additionally you have to make sure the 'db' and 'dialog' Helpers are added to your Symfony
Console HelperSet in a cli-config.php file.
This file can be either in the directory you are calling the console tool from or in as config subfolder.
.. code-block:: php
$db = \Doctrine\DBAL\DriverManager::getConnection($params);
// or
$em = \Doctrine\ORM\EntityManager::create($params);
$db = $em->getConnection();
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($db),
'question' => new \Symfony\Component\Console\Helper\QuestionHelper(),
));
return $helperset;
Note that the db helper is not required as you might want to pass the connection information
from the command line directly.
You will see that you have a few new commands when you execute the following command:
.. code-block:: bash
$ ./doctrine list migrations
Doctrine Command Line Interface version 1.2.1
Usage:
[options] command [arguments]
Options:
--help -h Display this help message.
--quiet -q Do not output any message.
--verbose -v Increase verbosity of messages.
--version -V Display this program version.
--color -c Force ANSI color output.
--no-interaction -n Do not ask any interactive question.
Available commands for the "migrations" namespace:
:diff Generate a migration by comparing your current database to your mapping information.
:execute Execute a single migration version up or down manually.
:generate Generate a blank migration class.
:migrate Execute a migration to a specified version or the latest available version.
:status View the status of a set of migrations.
:version Manually add and delete migration versions from the version table.

View File

@@ -0,0 +1,20 @@
Custom Configuration
====================
The ``AbstractCommand::setMigrationConfiguration()`` method allows you to set your own configuration.
This allows you to to build doctrine migration integration into your application or framework with
code that would looks like the following:
.. code-block:: php
use Doctrine\DBAL\Migrations\Configuration\Configuration;
$configuration = new Configuration();
$configuration->setMigrationsTableName(...);
$configuration->setMigrationsDirectory(...);
$configuration->setMigrationsNamespace(...);
$configuration->registerMigrationsFromDirectory(...);
// My command that extends Doctrine\DBAL\Migrations\Tools\Console\Command\AbstractCommand
$command->setMigrationConfiguration($configuration);

View File

@@ -0,0 +1,78 @@
Migrations Events
=================
The migrations library emits a series of events during the migration process.
- ``onMigrationsMigrating``: fired immediately before starting to execute
versions. This does not fire if there are no versions to be executed.
- ``onMigrationsVersionExecuting``: fired before a single version
executes.
- ``onMigrationsVersionExecuted``: fired after a single version executes.
- ``onMigrationsVersionSkipped``: fired when a single version is skipped.
- ``onMigrationsMigrated``: fired when all versions have been executed.
All of these events are emitted via the connection's event manager. Here's an
example event subscriber that listens for all possible migrations events.
.. code-block:: php
<?php
use Doctrine\Common\EventSubscriber;
use Doctrine\DBAL\Migrations\Event\MigrationsEventArgs;
use Doctrine\DBAL\Migrations\Event\MigrationsVersionEventArgs;
class MigrationsListener implements EventSubscriber
{
public function getSubscribedEvents()
{
return [
Events::onMigrationsMigrating,
Events::onMigrationsMigrated,
Events::onMigrationsVersionExecuting,
Events::onMigrationsVersionExecuted,
Events::onMigrationsVersionSkipped,
];
}
public function onMigrationsMigrating(MigrationsEventArgs $args)
{
// ...
}
public function onMigrationsMigrated(MigrationsEventArgs $args)
{
// ...
}
public function onMigrationsVersionExecuting(MigrationsVersionEventArgs $args)
{
// ...
}
public function onMigrationsVersionExecuted(MigrationsVersionEventArgs $args)
{
// ...
}
public function onMigrationsVersionSkipped(MigrationsVersionEventArgs $args)
{
// ...
}
}
To hook a migrations event subscriber into a connection, use its event manager.
This might go in the ``cli-config.php`` file or somewhere in a frameworks
container or dependency injection configuration.
.. code-block:: php
<?php
use Doctrine\DBAL\DriverManager;
$conn = DriverManager::getConnection([
// ...
]);
$conn->getEventManager()->addEventSubscriber(new MigrationsListener());
// rest of the cli set up...

View File

@@ -0,0 +1,194 @@
Generating Migrations
=====================
Migrations can be created for you if you're using the Doctrine 2 ORM or the DBAL
`Schema Representation <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/schema-representation.html>`_.
Empty migration classes can also be created.
Favor the tools described here over manually created migration files as the library
has some :doc:`requirements around migration version numbers <version_numbers>`.
Using the ORM
-------------
If you are using the Doctrine 2 ORM you can easily generate a migration class
by modifying your mapping information and running the diff task to compare it
to your current database schema.
If you are using the sandbox you can modify the provided `yaml/Entities.User.dcm.yml`
and add a new column:
.. code-block:: yaml
Entities\User:
# ...
fields:
# ...
test:
type: string
length: 255
# ...
Be sure that you add the property to the `Entities/User.php` file:
.. code-block:: php
namespace Entities;
/** @Entity @Table(name="users") */
class User
{
/**
* @var string $test
*/
private $test;
// ...
}
Now if you run the diff task you will get a nicely generated migration with the
changes required to update your database!
.. code-block:: bash
$ ./doctrine migrations:diff
Generated new migration class to "/path/to/migrations/DoctrineMigrations/Version20100416130459.php" from schema differences.
The migration class that is generated contains the SQL statements required to
update your database:
.. code-block:: php
namespace DoctrineMigrations;
use Doctrine\DBAL\Migrations\AbstractMigration,
Doctrine\DBAL\Schema\Schema;
class Version20100416130459 extends AbstractMigration
{
public function up(Schema $schema)
{
$this->addSql('ALTER TABLE users ADD test VARCHAR(255) NOT NULL');
}
public function down(Schema $schema)
{
$this->addSql('ALTER TABLE users DROP test');
}
}
The SQL generated here is the exact same SQL that would be executed if you were
using the `orm:schema-tool` task and the `--update` option. This just allows you to
capture that SQL and maybe tweak it or add to it and trigger the deployment
later across multiple database servers.
Without the ORM
---------------
Internally the diff command generates a ``Doctrine\DBAL\Schema\Schema`` object
from your entity's metadata using an implementation of
``Doctrine\DBAL\Migrations\Provider\SchemaProvider``. To use the Schema representation
directly, without the ORM, you must implement this interface yourself.
.. code-block:: php
<?php
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Migrations\Provider\SchemaProvider;
final class CustomSchemaProvider implements SchemaProvider
{
/**
* The schema provider only has one method: `createSchema`. This should
* return an Schema object that represents the state to which you'd like
* to migrate your database.
* {@inheritdoc}
*/
public function createSchema()
{
$schema = new Schema();
$table = $schema->createTable('foo');
$table->addColumn('id', 'integer', array(
'autoincrement' => true,
));
$table->setPrimaryKey(array('id'));
return $schema;
}
}
The ``StubSchemaProvider`` provided with the migrations library is another option.
It simply takes a schema object to its constructor and returns it from ``createSchema``.
.. code-block:: php
<?php
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Migrations\Provider\StubSchemaProvider;
$schema = new Schema();
$table = $schema->createTable('foo');
$table->addColumn('id', 'integer', array(
'autoincrement' => true,
));
$table->setPrimaryKey(array('id'));
$provider = new StubSchemaProvider($schema);
$provider->createSchema() === $schema; // true
By default the ``doctrine-migrations`` command line tool will only add the diff
command if the ORM is present. Without the ORM, you'll have to add the diff command
to your `console application <http://symfony.com/doc/current/components/console/introduction.html>`_
manually, passing in your schema provider implementation to the diff command's constructor.
.. code-block:: php
<?php
use Doctrine\DBAL\Migrations\Tools\Console\Command\DiffCommand;
$schemaProvider = new CustomSchemaProvider();
/** @var Symfony\Component\Console\Application */
$app->add(new DiffCommand($schemaProvider));
// ...
$app->run();
With the custom provider in place the diff command will compare the current database
state to the one provided. If there's a mismatch, the differences will be put
into the generated migration just like the ORM examples above.
Ignoring Custom Tables
----------------------
If you have custom tables which are not managed by doctrine you might face the situation
that with every diff task you are executing you get the remove statements for those tables
added to the migration class.
Therefore you can configure doctrine with a schema filter.
.. code-block:: php
$connection->getConfiguration()->setFilterSchemaAssetsExpression("~^(?!t_)~");
With this expression all tables prefixed with t_ will ignored by the schema tool.
If you use the DoctrineBundle with Symfony2 you can set the schema_filter option
in your configuration. You can find more information in the documentation of the
DoctrineMigationsBundle.
Creating Empty Migrations
-------------------------
Use the ``migrations:generate`` command to create an empty migration class.
.. code-block:: bash
$ ./doctrine migrations:generate
Generated new migration class to /path/to/migrations/DoctrineMigrations/Version20180107080000.php

View File

@@ -0,0 +1,41 @@
Input - Output Customization
============================
Behind the scenes Doctrine Migration uses ``\Symfony\Component\Console\Input\ArgvInput``
to capture and parse values from ``$_SERVER['argv']``.
You can customize the input like the following:
.. code-block:: php
require 'vendor/autoload.php'
$input = new \Symfony\Component\Console\Input\ArgvInput;
$input->setArgument('verbose', true);
return $input;
And the output similarly:
.. code-block:: php
require 'vendor/autoload.php'
// This is what Doctrine Migrations uses by default
$output = new \Symfony\Component\Console\Output\ConsoleOutput;
// Enable styling for HTML tags, which would otherwise throw errors
$htmlTags = array('p', 'ul', 'li', 'ol', 'dl', 'dt', 'dd', 'b', 'i', 'strong', 'em', 'hr', 'br');
foreach ($htmlTags as $tag) {
$output->setStyle($tag); // Each tag gets default styling
}
return $output;
If you are using the phar it is still possible to customize the input and output but you
need to require the autoloader that's in the phar.
.. code-block:: php
require 'phar://migrations.phar/vendor/autoload.php';

View File

@@ -0,0 +1,114 @@
.. index::
single: Installation
Installation
============
You have two options, you can either use composer to install doctrine migrations or you can use the standalone Phar package.
Composer
~~~~~~~~
Install it with composer:
.. code-block:: sh
composer require doctrine/migrations
PHP Binary / PHAR
~~~~~~~~~~~~~~~~~
You can download the Migrations PHP Binary, which is a standalone PHAR package
file with all the required dependencies. You can drop that single file onto any server
and start using the Doctrine Migrations.
To register a system command for the migrations you can create a simple batch
script, for example on a \*nix Environment creating a `/usr/local/bin/doctrine-migrations`:
.. code-block:: bash
#!/bin/sh
php /path/to/doctrine-migrations.phar "$@"
You could now go and use the migrations like:
.. code-block:: bash
[shell]
myshell> doctrine-migrations
Because the PHAR file is standalone it does not rely on the Symfony Console 'db' Helper,
but you have to pass a `--db-configuration` parameter that points to a PHP file
which returns the parameters for `Doctrine\DBAL\DriverManager::getConnection($dbParams)`.
If you don't specify this option Doctrine Migrations will look for a `migrations-db.php`
file returning that parameters in your current directory and only throw an error if
that is not found.
Configuration
-------------
The last thing you need to do is to configure your migrations. You can do so
by using the *--configuration* option to manually specify the path
to a configuration file. If you don't specify any configuration file the tasks will
look for a file named *migrations.xml* or *migrations.yml* at the root of
your command line. For the upcoming examples you can use a *migrations.xml*
file like the following:
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-migrations xmlns="http://doctrine-project.org/schemas/migrations/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/migrations/configuration
http://doctrine-project.org/schemas/migrations/configuration.xsd">
<name>Doctrine Sandbox Migrations</name>
<migrations-namespace>DoctrineMigrations</migrations-namespace>
<table name="doctrine_migration_versions" />
<migrations-directory>/path/to/migrations/classes/DoctrineMigrations</migrations-directory>
</doctrine-migrations>
Of course you could do the same thing with a *migrations.yml* file:
.. code-block:: yaml
name: Doctrine Sandbox Migrations
migrations_namespace: DoctrineMigrations
table_name: doctrine_migration_versions
migrations_directory: /path/to/migrations/classes/DoctrineMigrations
With the above example, the migrations tool will search the ``migrations_directory``
recursively for files that begin with ``Version`` followed one to 255 characters
and a ``.php`` suffix. ``Version.{1,255}\.php`` is the regular expression that's
used.
Everything after ``Version`` will be treated as the actual version in
the database. Take the file name ``VersionSomeVersion.php``, ``SomeVersion`` would
be the version *number* stored in the migrations database table. Since versions
are ordered, doctrine :doc:`generates </reference/generating_migrations>` version
numbers with a date time like ``Version20150505120000.php``. This ensures that
the migrations are executed in the correct order.
While you *can* use custom filenames, it's probably a good idea to the Doctrine
:doc:`generate migration files </reference/generating_migrations>` for you.
And if you want to specify each migration manually in YAML you can:
.. code-block:: yaml
table_name: doctrine_migration_versions
migrations_directory: /path/to/migrations/classes/DoctrineMigrations
migrations:
migration1:
version: 20100704000000
class: DoctrineMigrations\NewMigration
If you specify your own migration classes (like `DoctrineMigrations\NewMigration` in the previous
example) you will need an autoloader unless all those classes begin with the prefix Version*,
for example path/to/migrations/classes/VersionNewMigration.php.

View File

@@ -0,0 +1,14 @@
Integrations
============
If you want to start quickly, there are integrations for a lot of frameworks already:
* `symfony 2 <https://packagist.org/packages/doctrine/doctrine-migrations-bundle>`_
* `ZF2 <https://packagist.org/packages/doctrine/doctrine-orm-module>`_
* `laravel <https://packagist.org/packages/laravel-doctrine/migrations>`_
* `Silex <https://packagist.org/packages/kurl/silex-doctrine-migrations-provider>`_
* `Silex <https://packagist.org/packages/dbtlr/silex-doctrine-migrations>`_
* `nette <https://packagist.org/packages/zenify/doctrine-migrations>`_
Don't hesitate to make a `Pull Request <https://github.com/doctrine/migrations>`_
if you want to add your integration to this list.

View File

@@ -0,0 +1,202 @@
.. index::
single: Introduction
Introduction
============
The Doctrine Migrations offer additional functionality on top of the database
abstraction layer (DBAL) for versioning your database schema and easily deploying
changes to it. It is a very easy to use and powerful tool.
In order to use migrations you need to do some setup first.
Installation
------------
There are two ways to use the Doctrine Migrations project. Either as a supplement
to your already existing Doctrine DBAL (+ ORM) setup or as a standalone "PHP Binary"
(also known as PHAR).
Use as Supplement
~~~~~~~~~~~~~~~~~
To use the Migrations as supplement you have to get the sources from the GitHub
repository, either by downloading them, checking them out as SVN external or as Git Submodule.
Then you have to setup the class loader to load the classes for the `Doctrine\DBAL\Migrations`
namespace in your project:
.. code-block:: php
require_once '/path/to/migrations/lib/vendor/doctrine-common/Doctrine/Common/ClassLoader';
use Doctrine\Common\ClassLoader;
$classLoader = new ClassLoader('Doctrine\DBAL\Migrations', '/path/to/migrations/lib');
$classLoader->register();
Now the above autoloader is able to load a class like the following:
.. code-block:: bash
/path/to/migrations/lib/Doctrine/DBAL/Migrations/Migrations/Migration.php
Register Console Commands
~~~~~~~~~~~~~~~~~~~~~~~~~
Now that we have setup the autoloaders we are ready to add the migration console
commands to our `Doctrine Command Line Interface <http://doctrine-orm.readthedocs.org/en/latest/reference/tools.html#adding-own-commands>`_:
.. code-block:: php
// ...
$cli->addCommands(array(
// ...
// Migrations Commands
new \Doctrine\DBAL\Migrations\Tools\Console\Command\DiffCommand(),
new \Doctrine\DBAL\Migrations\Tools\Console\Command\ExecuteCommand(),
new \Doctrine\DBAL\Migrations\Tools\Console\Command\GenerateCommand(),
new \Doctrine\DBAL\Migrations\Tools\Console\Command\MigrateCommand(),
new \Doctrine\DBAL\Migrations\Tools\Console\Command\StatusCommand(),
new \Doctrine\DBAL\Migrations\Tools\Console\Command\VersionCommand()
));
Additionally you have to make sure the 'db' and 'dialog' Helpers are added to your Symfony
Console HelperSet.
.. code-block:: php
$db = \Doctrine\DBAL\DriverManager::getConnection($params);
// or
$em = \Doctrine\ORM\EntityManager::create($params);
$db = $em->getConnection();
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($db),
'dialog' => new \Symfony\Component\Console\Helper\QuestionHelper(),
));
You will see that you have a few new commands when you execute the following command:
.. code-block:: bash
$ ./doctrine list migrations
Doctrine Command Line Interface version 2.0.0BETA3-DEV
Usage:
[options] command [arguments]
Options:
--help -h Display this help message.
--quiet -q Do not output any message.
--verbose -v Increase verbosity of messages.
--version -V Display this program version.
--color -c Force ANSI color output.
--no-interaction -n Do not ask any interactive question.
Available commands for the "migrations" namespace:
:diff Generate a migration by comparing your current database to your mapping information.
:execute Execute a single migration version up or down manually.
:generate Generate a blank migration class.
:migrate Execute a migration to a specified version or the latest available version.
:status View the status of a set of migrations.
:version Manually add and delete migration versions from the version table.
PHP Binary / PHAR
~~~~~~~~~~~~~~~~~
You can download the Migrations PHP Binary, which is a standalone PHAR package
file with all the required dependencies. You can drop that single file onto any server
and start using the Doctrine Migrations.
To register a system command for the migrations you can create a simple batch
script, for example on a \*nix Environment creating a `/usr/local/bin/doctrine-migrations`:
.. code-block:: bash
#!/bin/sh
php /path/to/doctrine-migrations.phar "$@"
You could now go and use the migrations like:
.. code-block:: bash
[shell]
myshell> doctrine-migrations
Because the PHAR file is standalone it does not rely on the Symfony Console 'db' Helper,
but you have to pass a `--db-configuration` parameter that points to a PHP file
which returns the parameters for `Doctrine\DBAL\DriverManager::getConnection($dbParams)`.
If you don't specify this option Doctrine Migrations will look for a `migrations-db.php`
file returning that parameters in your current directory and only throw an error if
that is not found.
Configuration
-------------
The last thing you need to do is to configure your migrations. You can do so
by using the *--configuration* option to manually specify the path
to a configuration file. If you don't specify any configuration file the tasks will
look for a file named *migrations.xml* or *migrations.yml* at the root of
your command line. For the upcoming examples you can use a *migrations.xml*
file like the following:
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-migrations xmlns="http://doctrine-project.org/schemas/migrations/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/migrations/configuration
http://doctrine-project.org/schemas/migrations/configuration.xsd">
<name>Doctrine Sandbox Migrations</name>
<migrations-namespace>DoctrineMigrations</migrations-namespace>
<table name="doctrine_migration_versions" />
<migrations-directory>/path/to/migrations/classes/DoctrineMigrations</migrations-directory>
</doctrine-migrations>
Of course you could do the same thing with a *configuration.yml* file:
.. code-block:: yaml
name: Doctrine Sandbox Migrations
migrations_namespace: DoctrineMigrations
table_name: doctrine_migration_versions
migrations_directory: /path/to/migrations/classes/DoctrineMigrations
With the above example, the migrations tool will search the ``migrations_directory``
recursively for files that begin with ``Version`` followed one to 255 characters
and a ``.php`` suffix. ``Version.{1,255}\.php`` is the regular expression that's
used.
Everything after ``Version`` will be treated as the actual version in
the database. Take the file name ``VersionSomeVersion.php``, ``SomeVersion`` would
be the version *number* stored in the migrations database table. Since versions
are ordered, doctrine :doc:`generates </reference/generating_migrations>` version
numbers with a date time like ``Version20150505120000.php``. This ensures that
the migrations are executed in the correct order.
While you *can* use custom filenames, it's probably a good idea to let Doctrine
:doc:`generate migration files </reference/generating_migrations>` for you.
And if you want to specify each migration manually in YAML you can:
.. code-block:: yaml
table_name: doctrine_migration_versions
migrations_directory: /path/to/migrations/classes/DoctrineMigrations
migrations:
migration1:
version: 20100704000000
class: DoctrineMigrations\NewMigration
If you specify your own migration classes (like `DoctrineMigrations\NewMigration` in the previous
example) you will need an autoloader unless all those classes begin with the prefix Version*,
for example path/to/migrations/classes/VersionNewMigration.php.

View File

@@ -0,0 +1,250 @@
.. index::
single: Managing Migrations
Managing Migrations
===================
Now that we have a new migration class present, lets run the status task to see
if it is there:
.. code-block:: bash
$ ./doctrine migrations:status
== Configuration
>> Name: Doctrine Sandbox Migrations
>> Database Driver: pdo_mysql
>> Database Name: testdb
>> Configuration Source: /Users/jwage/Sites/doctrine2git/tools/sandbox/migrations.xml
>> Version Table Name: doctrine_migration_versions
>> Migrations Namespace: DoctrineMigrations
>> Migrations Directory: /Users/jwage/Sites/doctrine2git/tools/sandbox/DoctrineMigrations
>> Current Version: 2010-04-16 13:04:22 (20100416130422)
>> Latest Version: 2010-04-16 13:04:22 (20100416130422)
>> Executed Migrations: 0
>> Available Migrations: 1
>> New Migrations: 1
== Migration Versions
>> 2010-04-16 13:04:01 (20100416130401) not migrated
As you can see we have a new version present and it is ready to be executed. The
problem is it does not have anything in it so nothing would be executed! Let's
add some code to it and add a new table:
.. code-block:: php
namespace DoctrineMigrations;
use Doctrine\DBAL\Migrations\AbstractMigration,
Doctrine\DBAL\Schema\Schema;
class Version20100416130401 extends AbstractMigration
{
public function up(Schema $schema)
{
$table = $schema->createTable('users');
$table->addColumn('username', 'string');
$table->addColumn('password', 'string');
}
public function down(Schema $schema)
{
$schema->dropTable('users');
}
}
Now we are ready to give it a test! First lets just do a dry-run to make sure
it produces the SQL we expect:
.. code-block:: bash
$ ./doctrine migrations:migrate --dry-run
Are you sure you wish to continue?
y
Executing dry run of migration up to 20100416130452 from 0
>> migrating 20100416130452
-> CREATE TABLE users (username VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL) ENGINE = InnoDB
Everything looks good so we can remove the *--dry-run* option and actually execute
the migration:
.. code-block:: bash
$ ./doctrine migrations:migrate
Are you sure you wish to continue?
y
Migrating up to 20100416130452 from 0
>> migrating 20100416130452
-> CREATE TABLE users (username VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL) ENGINE = InnoDB
>> migrated
Alternately, if you wish to run the migrations in an unattended mode, we can add the *--no--interaction* option and then
execute the migrations without any extra prompting from Doctrine.
.. code-block:: bash
$ ./doctrine migrations:migrate --no-interaction
Migrating up to 20100416130452 from 0
>> migrating 20100416130452
-> CREATE TABLE users (username VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL) ENGINE = InnoDB
>> migrated
By checking the status again after using either method you will see everything is updated:
.. code-block:: bash
$ ./doctrine migrations:status
== Configuration
>> Name: Doctrine Sandbox Migrations
>> Database Driver: pdo_mysql
>> Database Name: testdb
>> Configuration Source: /Users/jwage/Sites/doctrine2git/tools/sandbox/migrations.xml
>> Version Table Name: doctrine_migration_versions
>> Migrations Namespace: DoctrineMigrations
>> Migrations Directory: /Users/jwage/Sites/doctrine2git/tools/sandbox/DoctrineMigrations
>> Current Version: 2010-04-16 13:04:52 (20100416130452)
>> Latest Version: 2010-04-16 13:04:52 (20100416130452)
>> Executed Migrations: 1
>> Available Migrations: 1
>> New Migrations: 0
== Migration Versions
>> 2010-04-16 13:04:01 (20100416130452) migrated
Reverting Migrations
--------------------
You maybe noticed in the last example that we defined a *down()* method which
drops the users table that we created. This method allows us to easily revert
changes the schema has been migrated to. The *migrate* command takes a *version*
argument which you can use to roll back your schema to a specific version of
your migrations:
.. code-block:: bash
$ ./doctrine migrations:migrate first
Are you sure you wish to continue?
y
Migrating down to 0 from 20100416130422
-- reverting 20100416130422
-> DROP TABLE addresses
-- reverted
-- reverting 20100416130401
-> DROP TABLE users
-- reverted
Now our database is back to where we originally started. Give it a check with
the status command:
.. code-block:: bash
$ ./doctrine migrations:status
== Configuration
>> Name: Doctrine Sandbox Migrations
>> Database Driver: pdo_mysql
>> Database Name: testdb
>> Configuration Source: /Users/jwage/Sites/doctrine2git/tools/sandbox/migrations.xml
>> Version Table Name: doctrine_migration_versions
>> Migrations Namespace: DoctrineMigrations
>> Migrations Directory: /Users/jwage/Sites/doctrine2git/tools/sandbox/DoctrineMigrations
>> Current Version: 0
>> Latest Version: 2010-04-16 13:04:22 (20100416130422)
>> Executed Migrations: 0
>> Available Migrations: 2
>> New Migrations: 2
== Migration Versions
>> 2010-04-16 13:04:01 (20100416130401) not migrated
>> 2010-04-16 13:04:22 (20100416130422) not migrated
Aliases
-------
There are some shortcut for convenience (first, prev, next, latest).
So that you don't have to know the name of the migration.
You can just call
.. code-block:: bash
$ ./doctrine migrations:migrate prev
Writing Migration SQL Files
---------------------------
You can optionally choose to not execute a migration directly on a database and
instead output all the SQL statements to a file. This is possible by using the
*--write-sql* option of the *migrate* command:
.. code-block:: bash
$ ./doctrine migrations:migrate --write-sql
Executing dry run of migration up to 20100416130422 from 0
>> migrating 20100416130401
-> CREATE TABLE users (username VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL) ENGINE = InnoDB
>> migrating 20100416130422
-> CREATE TABLE addresses (id INT NOT NULL, street VARCHAR(255) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB
Writing migration file to "/path/to/sandbox/doctrine_migration_20100416130405.sql"
Now if you have a look at the *doctrine_migration_20100416130405.sql* file you will see the would be
executed SQL outputted in a nice format:
.. code-block:: bash
# Doctrine Migration File Generated on 2010-04-16 13:04:05
# Migrating from 0 to 20100416130422
# Version 20100416130401
CREATE TABLE users (username VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL) ENGINE = InnoDB;
# Version 20100416130422
CREATE TABLE addresses (id INT NOT NULL, street VARCHAR(255) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB;
.. _managing-versions-table:
Managing the Version Table
--------------------------
Sometimes you may need to manually change something in the database table which
manages the versions for some migrations. For this you can use the version task.
You can easily add a version like this:
.. code-block:: bash
$ ./doctrine migrations:version YYYYMMDDHHMMSS --add
Or you can delete that version:
.. code-block:: bash
$ ./doctrine migrations:version YYYYMMDDHHMMSS --delete
The command does not execute any migrations code, it simply adds the specified
version to the database.

View File

@@ -0,0 +1,89 @@
Migration Classes
=================
As now everything is setup and configured you are ready to start writing
migration classes. You can easily generate your first migration class with the
following command:
.. code-block:: bash
$ ./doctrine migrations:generate
Generated new migration class to "/path/to/migrations/classes/DoctrineMigrations/Version20100416130401.php"
Have a look and you will see a new class at the above location that looks like
the following:
.. code-block:: php
namespace DoctrineMigrations;
use Doctrine\DBAL\Migrations\AbstractMigration,
Doctrine\DBAL\Schema\Schema;
class Version20100416130401 extends AbstractMigration
{
public function up(Schema $schema)
{
}
public function down(Schema $schema)
{
}
}
You can now use the *addSql()* method within the up and down method.
Internally the addSql call are passed to the `dbal executeQuery method`_.
This means that you can use the power of the prepared statement easilly and that you don't need to copy paste the same
query with different parameters. You can just pass those differents parameters to the addSql method as parameters.
.. code-block:: php
public function up(Schema $schema)
{
$users = array(
array('name' => 'mike', 'id' => 1),
array('name' => 'jwage', 'id' => 2),
array('name' => 'ocramius', 'id' => 3),
);
foreach ($users as $user) {
$this->addSql('UPDATE user SET happy = true WHERE name = :name AND id = :id', $user);
}
}
For more infos on `how the doctrine dbal executeQuery method works go tho the doctrine dbal documentation`_.
Additionally, there is also the preUp, postUp and preDown, postDown method, that are respectivelly called before and
after the up and down method are called.
First you need to generate a new migration class:
.. code-block:: bash
$ ./doctrine migrations:generate
Generated new migration class to "/path/to/migrations/DoctrineMigrations/Version20100416130422.php"
This newly generated migration class is the place where you can add your own
custom SQL queries:
.. code-block:: php
namespace DoctrineMigrations;
use Doctrine\DBAL\Migrations\AbstractMigration,
Doctrine\DBAL\Schema\Schema;
class Version20100416130422 extends AbstractMigration
{
public function up(Schema $schema)
{
$this->addSql('CREATE TABLE addresses (id INT NOT NULL, street VARCHAR(255) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB');
}
public function down(Schema $schema)
{
$this->addSql('DROP TABLE addresses');
}
}

View File

@@ -0,0 +1,76 @@
Version Numbers
===============
When :doc:`Generating Migrations <generating_migrations>` the newly created
classes are generated with the name ``Version{date}`` with ``{date}`` having a
``YmdHis`` `format <http://php.net/manual/en/function.date.php>`_. This format
is important as it allows the migrations to be correctly ordered.
Starting with version 1.5 when loading migration classes, Doctrine Migrations
does a ``sort($versions, SORT_STRING)`` on version numbers. This can cause
problems with custom version numbers:
.. code-block:: php
<?php
$versions = [
'Version1',
'Version2',
// ...
'Version10',
];
sort($versions, SORT_STRING);
var_dump($versions);
/*
array(3) {
[0] =>
string(8) "Version1"
[1] =>
string(9) "Version10"
[2] =>
string(8) "Version2"
}
*/
The custom version numbers above end up out of order which may cause damage
to a database.
It's **strongly recommended** that the ``Version{date}`` migration class name
format is kept to and that the various :doc:`tools for generating migrations <generating_migrations>`
be used.
Should some custom migration numbers be necessary, keeping the version number
the same length as the date format (14 total characters) and padding it to the
left with zeros should work.
.. code-block:: php
<?php
$versions = [
'Version00000000000001',
'Version00000000000002',
// ...
'Version00000000000010',
'Version20180107070000', // generated version
];
sort($versions, SORT_STRING);
var_dump($versions);
/*
array(4) {
[0] =>
string(21) "Version00000000000001"
[1] =>
string(21) "Version00000000000002"
[2] =>
string(21) "Version00000000000010"
[3] =>
string(21) "Version20180107070000"
}
*/
Please note that migrating to this new, zero-padded format may require
:ref:`manual version table intervention <managing-versions-table>` if the
versions have previously been applied.

View File

@@ -0,0 +1,14 @@
.. toctree::
:depth: 3
reference/introduction
reference/installation
reference/integration
reference/building_framework_specific_integration
reference/migration_classes
reference/managing_migrations
reference/generating_migrations
reference/custom_configuration
reference/input_output_customization
reference/events
reference/version_numbers

View File

@@ -0,0 +1,7 @@
<?php
namespace Doctrine\DBAL\Migrations;
class AbortMigrationException extends MigrationException
{
}

View File

@@ -0,0 +1,178 @@
<?php
namespace Doctrine\DBAL\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration as NewAbstractMigration;
/**
* Abstract class for individual migrations to extend from.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan H. Wage <jonwage@gmail.com>
* @deprecated Please use Doctrine\Migrations\AbstractMigration
*/
abstract class AbstractMigration
{
/**
* Reference to the Version instance representing this migration
*
* @var Version
*/
protected $version;
/**
* The Doctrine\DBAL\Connection instance we are migrating
*
* @var \Doctrine\DBAL\Connection
*/
protected $connection;
/**
* Reference to the SchemaManager instance referenced by $_connection
*
* @var \Doctrine\DBAL\Schema\AbstractSchemaManager
*/
protected $sm;
/**
* Reference to the DatabasePlatform instance referenced by $_connection
*
* @var \Doctrine\DBAL\Platforms\AbstractPlatform
*/
protected $platform;
/**
* The OutputWriter object instance used for outputting information
*
* @var OutputWriter
*/
private $outputWriter;
public function __construct(Version $version)
{
if ( ! $this instanceof NewAbstractMigration) {
@trigger_error(sprintf('The "%s" class is deprecated since Doctrine Migrations 2.0. Use %s instead.', AbstractMigration::class, NewAbstractMigration::class), E_USER_DEPRECATED);
}
$config = $version->getConfiguration();
$this->version = $version;
$this->connection = $config->getConnection();
$this->sm = $this->connection->getSchemaManager();
$this->platform = $this->connection->getDatabasePlatform();
$this->outputWriter = $config->getOutputWriter();
}
/**
* Indicates the transactional mode of this migration.
* If this function returns true (default) the migration will be executed in one transaction,
* otherwise non-transactional state will be used to execute each of the migration SQLs.
*
* Extending class should override this function to alter the return value
*
* @return bool TRUE by default.
*/
public function isTransactional()
{
return true;
}
/**
* Get migration description
*
* @return string
*/
public function getDescription()
{
return '';
}
/**
* Print a warning message if the condition evaluates to TRUE.
*
* @param boolean $condition
* @param string $message
*/
public function warnIf($condition, $message = '')
{
if ($condition) {
$message = $message ?: 'Unknown Reason';
$this->outputWriter->write(sprintf(
' <comment>Warning during %s: %s</comment>',
$this->version->getExecutionState(),
$message
));
}
}
/**
* Abort the migration if the condition evaluates to TRUE.
*
* @param boolean $condition
* @param string $message
*
* @throws AbortMigrationException
*/
public function abortIf($condition, $message = '')
{
if ($condition) {
throw new AbortMigrationException($message ?: 'Unknown Reason');
}
}
/**
* Skip this migration (but not the next ones) if condition evaluates to TRUE.
*
* @param boolean $condition
* @param string $message
*
* @throws SkipMigrationException
*/
public function skipIf($condition, $message = '')
{
if ($condition) {
throw new SkipMigrationException($message ?: 'Unknown Reason');
}
}
public function preUp(Schema $schema)
{
}
public function postUp(Schema $schema)
{
}
public function preDown(Schema $schema)
{
}
public function postDown(Schema $schema)
{
}
abstract public function up(Schema $schema);
abstract public function down(Schema $schema);
protected function addSql($sql, array $params = [], array $types = [])
{
$this->version->addSql($sql, $params, $types);
}
protected function write($message)
{
$this->outputWriter->write($message);
}
protected function throwIrreversibleMigrationException($message = null)
{
if (null === $message) {
$message = 'This migration is irreversible and cannot be reverted.';
}
throw new IrreversibleMigrationException($message);
}
}

View File

@@ -0,0 +1,133 @@
<?php
namespace Doctrine\DBAL\Migrations\Configuration;
use Doctrine\DBAL\Migrations\MigrationException;
/**
* Abstract Migration Configuration class for loading configuration information
* from a configuration file (xml or yml).
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
abstract class AbstractFileConfiguration extends Configuration
{
/**
* The configuration file used to load configuration information
*
* @var string
*/
private $file;
/**
* Whether or not the configuration file has been loaded yet or not
*
* @var boolean
*/
private $loaded = false;
/**
* @var array of possible configuration properties in migrations configuration.
*/
private $configurationProperties = [
'migrations_namespace' => 'setMigrationsNamespace',
'table_name' => 'setMigrationsTableName',
'column_name' => 'setMigrationsColumnName',
'organize_migrations' => 'setMigrationOrganisation',
'name' => 'setName',
'migrations_directory' => 'loadMigrationsFromDirectory',
'migrations' => 'loadMigrations',
'custom_template' => 'setCustomTemplate',
];
protected function setConfiguration(array $config)
{
foreach ($config as $configurationKey => $configurationValue) {
if ( ! isset($this->configurationProperties[$configurationKey])) {
$msg = sprintf('Migrations configuration key "%s" does not exist.', $configurationKey);
throw MigrationException::configurationNotValid($msg);
}
}
foreach ($this->configurationProperties as $configurationKey => $configurationSetter) {
if (isset($config[$configurationKey])) {
$this->{$configurationSetter}($config[$configurationKey]);
}
}
}
private function loadMigrationsFromDirectory($migrationsDirectory)
{
$this->setMigrationsDirectory($migrationsDirectory);
$this->registerMigrationsFromDirectory($migrationsDirectory);
}
private function loadMigrations($migrations)
{
if (is_array($migrations)) {
foreach ($migrations as $migration) {
$this->registerMigration($migration['version'], $migration['class']);
}
}
}
private function setMigrationOrganisation($migrationOrganisation)
{
if (strcasecmp($migrationOrganisation, static::VERSIONS_ORGANIZATION_BY_YEAR) == 0) {
$this->setMigrationsAreOrganizedByYear();
} elseif (strcasecmp($migrationOrganisation, static::VERSIONS_ORGANIZATION_BY_YEAR_AND_MONTH) == 0) {
$this->setMigrationsAreOrganizedByYearAndMonth();
} else {
$msg = 'Unknown ' . var_export($migrationOrganisation, true) . ' for configuration "organize_migrations".';
throw MigrationException::configurationNotValid($msg);
}
}
/**
* Load the information from the passed configuration file
*
* @param string $file The path to the configuration file
*
* @throws MigrationException Throws exception if configuration file was already loaded
*/
public function load($file)
{
if ($this->loaded) {
throw MigrationException::configurationFileAlreadyLoaded();
}
if (file_exists($path = getcwd() . '/' . $file)) {
$file = $path;
}
$this->file = $file;
if ( ! file_exists($file)) {
throw new \InvalidArgumentException('Given config file does not exist');
}
$this->doLoad($file);
$this->loaded = true;
}
protected function getDirectoryRelativeToFile($file, $input)
{
$path = realpath(dirname($file) . '/' . $input);
return ($path !== false) ? $path : $input;
}
public function getFile()
{
return $this->file;
}
/**
* Abstract method that each file configuration driver must implement to
* load the given configuration file whether it be xml, yaml, etc. or something
* else.
*
* @param string $file The path to a configuration file.
*/
abstract protected function doLoad($file);
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Doctrine\DBAL\Migrations\Configuration;
/**
* Load migration configuration information from a PHP configuration file.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author David Havl <contact@davidhavl.com>
*/
class ArrayConfiguration extends AbstractFileConfiguration
{
/**
* @inheritdoc
*/
protected function doLoad($file)
{
$config = require $file;
if (isset($config['migrations_directory'])) {
$config['migrations_directory'] = $this->getDirectoryRelativeToFile($file, $config['migrations_directory']);
}
$this->setConfiguration($config);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
<?php
namespace Doctrine\DBAL\Migrations\Configuration\Connection;
use Doctrine\DBAL\Connection;
interface ConnectionLoaderInterface
{
/**
* read the input and return a Configuration, returns `false` if the config
* is not supported
* @return Connection|null
*/
public function chosen();
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Doctrine\DBAL\Migrations\Configuration\Connection\Loader;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Migrations\Configuration\Connection\ConnectionLoaderInterface;
class ArrayConnectionConfigurationLoader implements ConnectionLoaderInterface
{
private $filename;
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* read the input and return a Configuration, returns `false` if the config
* is not supported
* @return Connection|null
*/
public function chosen()
{
if (empty($this->filename)) {
return null;
}
if ( ! file_exists($this->filename)) {
return null;
}
$params = include $this->filename;
if ( ! is_array($params)) {
throw new \InvalidArgumentException('The connection file has to return an array with database configuration parameters.');
}
return DriverManager::getConnection($params);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Doctrine\DBAL\Migrations\Configuration\Connection\Loader;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Migrations\Configuration\Connection\ConnectionLoaderInterface;
final class ConnectionConfigurationChainLoader implements ConnectionLoaderInterface
{
/** @var ConnectionLoaderInterface[] */
private $loaders;
public function __construct(array $loaders)
{
$this->loaders = $loaders;
}
/**
* read the input and return a Configuration, returns `false` if the config
* is not supported
* @return Connection|null
*/
public function chosen()
{
foreach ($this->loaders as $loader) {
if (null !== $confObj = $loader->chosen()) {
return $confObj;
}
}
return null;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Doctrine\DBAL\Migrations\Configuration\Connection\Loader;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Migrations\Configuration\Connection\ConnectionLoaderInterface;
class ConnectionConfigurationLoader implements ConnectionLoaderInterface
{
/** @var Configuration */
private $configuration;
public function __construct(Configuration $configuration = null)
{
if ($configuration !== null) {
$this->configuration = $configuration;
}
}
/**
* read the input and return a Configuration, returns `false` if the config
* is not supported
* @return Connection|null
*/
public function chosen()
{
if ($this->configuration) {
return $this->configuration->getConnection();
}
return null;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Doctrine\DBAL\Migrations\Configuration\Connection\Loader;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Migrations\Configuration\Connection\ConnectionLoaderInterface;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
use Symfony\Component\Console\Helper\HelperSet;
class ConnectionHelperLoader implements ConnectionLoaderInterface
{
/**
* @var string
*/
private $helperName;
/** @var HelperSet */
private $helperSet;
/**
* ConnectionHelperLoader constructor.
* @param HelperSet $helperSet
* @param string $helperName
*/
public function __construct(HelperSet $helperSet = null, $helperName)
{
$this->helperName = $helperName;
if ($helperSet === null) {
$helperSet = new HelperSet();
}
$this->helperSet = $helperSet;
}
/**
* read the input and return a Configuration, returns `false` if the config
* is not supported
* @return Connection|null
*/
public function chosen()
{
if ($this->helperSet->has($this->helperName)) {
$connectionHelper = $this->helperSet->get($this->helperName);
if ($connectionHelper instanceof ConnectionHelper) {
return $connectionHelper->getConnection();
}
}
return null;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Doctrine\DBAL\Migrations\Configuration;
/**
* Load migration configuration information from a PHP configuration file.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author David Havl <contact@davidhavl.com>
*/
class JsonConfiguration extends AbstractFileConfiguration
{
/**
* @inheritdoc
*/
protected function doLoad($file)
{
$config = json_decode(file_get_contents($file), true);
if (isset($config['migrations_directory'])) {
$config['migrations_directory'] = $this->getDirectoryRelativeToFile($file, $config['migrations_directory']);
}
$this->setConfiguration($config);
}
}

View File

@@ -0,0 +1,40 @@
<xs:schema
attributeFormDefault="unqualified"
elementFormDefault="qualified"
targetNamespace="http://doctrine-project.org/schemas/migrations/configuration"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="doctrine-migrations">
<xs:complexType>
<xs:all minOccurs="0">
<xs:element type="xs:string" name="name" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="migrations-namespace" minOccurs="0" maxOccurs="1"/>
<xs:element name="table" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute type="xs:string" name="name"/>
<xs:attribute type="xs:string" name="column"/>
</xs:complexType>
</xs:element>
<xs:element name="organize-migrations" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="((y|Y)(e|E)(a|A)(r|R))|((y|Y)(e|E)(a|A)(r|R)_(a|A)(n|N)(d|D)_(m|M)(o|O)(n|N)(t|T)(h|H))"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element type="xs:string" name="migrations-directory" minOccurs="0" maxOccurs="1"/>
<xs:element name="migrations" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="migration">
<xs:complexType mixed="true">
<xs:attribute name="version" type="xs:string"/>
<xs:attribute name="class" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -0,0 +1,57 @@
<?php
namespace Doctrine\DBAL\Migrations\Configuration;
use Doctrine\DBAL\Migrations\MigrationException;
/**
* Load migration configuration information from a XML configuration file.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
class XmlConfiguration extends AbstractFileConfiguration
{
/**
* @inheritdoc
*/
protected function doLoad($file)
{
libxml_use_internal_errors(true);
$xml = new \DOMDocument();
$xml->load($file);
if ( ! $xml->schemaValidate(__DIR__ . DIRECTORY_SEPARATOR . "XML" . DIRECTORY_SEPARATOR . "configuration.xsd")) {
libxml_clear_errors();
throw MigrationException::configurationNotValid('XML configuration did not pass the validation test.');
}
$xml = simplexml_load_file($file, "SimpleXMLElement", LIBXML_NOCDATA);
$config = [];
if (isset($xml->name)) {
$config['name'] = (string) $xml->name;
}
if (isset($xml->table['name'])) {
$config['table_name'] = (string) $xml->table['name'];
}
if (isset($xml->table['column'])) {
$config['column_name'] = (string) $xml->table['column'];
}
if (isset($xml->{'migrations-namespace'})) {
$config['migrations_namespace'] = (string) $xml->{'migrations-namespace'};
}
if (isset($xml->{'organize-migrations'})) {
$config['organize_migrations'] = $xml->{'organize-migrations'};
}
if (isset($xml->{'migrations-directory'})) {
$config['migrations_directory'] = $this->getDirectoryRelativeToFile($file, (string) $xml->{'migrations-directory'});
}
if (isset($xml->migrations->migration)) {
$config['migrations'] = $xml->migrations->migration;
}
$this->setConfiguration($config);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Doctrine\DBAL\Migrations\Configuration;
use Symfony\Component\Yaml\Yaml;
use Doctrine\DBAL\Migrations\MigrationException;
/**
* Load migration configuration information from a YAML configuration file.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
class YamlConfiguration extends AbstractFileConfiguration
{
/**
* @inheritdoc
*/
protected function doLoad($file)
{
if ( ! class_exists(Yaml::class)) {
throw MigrationException::yamlConfigurationNotAvailable();
}
$config = Yaml::parse(file_get_contents($file));
if ( ! is_array($config)) {
throw new \InvalidArgumentException('Not valid configuration.');
}
if (isset($config['migrations_directory'])) {
$config['migrations_directory'] = $this->getDirectoryRelativeToFile($file, $config['migrations_directory']);
}
$this->setConfiguration($config);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Doctrine\DBAL\Migrations\Event\Listeners;
use Doctrine\Common\EventSubscriber;
use Doctrine\DBAL\Migrations\Events;
use Doctrine\DBAL\Migrations\Event\MigrationsEventArgs;
/**
* Listens for `onMigrationsMigrated` and, if the conneciton is has autocommit
* makes sure to do the final commit to ensure changes stick around.
*
* @since 1.6
*/
final class AutoCommitListener implements EventSubscriber
{
public function onMigrationsMigrated(MigrationsEventArgs $args)
{
$conn = $args->getConnection();
if ( ! $args->isDryRun() && ! $conn->isAutoCommit()) {
$conn->commit();
}
}
/**
* {@inheritdoc}
*/
public function getSubscribedEvents()
{
return [Events::onMigrationsMigrated];
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Doctrine\DBAL\Migrations\Event;
use Doctrine\Common\EventArgs;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
class MigrationsEventArgs extends EventArgs
{
/**
* @var Configuration
*/
private $config;
/**
* The direction of the migration.
*
* @var string (up|down)
*/
private $direction;
/**
* Whether or not the migrations are executing in dry run mode.
*
* @var bool
*/
private $dryRun;
public function __construct(Configuration $config, $direction, $dryRun)
{
$this->config = $config;
$this->direction = $direction;
$this->dryRun = (bool) $dryRun;
}
public function getConfiguration()
{
return $this->config;
}
public function getConnection()
{
return $this->config->getConnection();
}
public function getDirection()
{
return $this->direction;
}
public function isDryRun()
{
return $this->dryRun;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Doctrine\DBAL\Migrations\Event;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Migrations\Version;
class MigrationsVersionEventArgs extends MigrationsEventArgs
{
/**
* The version the event pertains to.
*
* @var Version
*/
private $version;
public function __construct(Version $version, Configuration $config, $direction, $dryRun)
{
parent::__construct($config, $direction, $dryRun);
$this->version = $version;
}
public function getVersion()
{
return $this->version;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Doctrine\DBAL\Migrations;
final class Events
{
/**
* Private constructor. This class cannot be instantiated.
*/
private function __construct()
{
}
const onMigrationsMigrating = 'onMigrationsMigrating';
const onMigrationsMigrated = 'onMigrationsMigrated';
const onMigrationsVersionExecuting = 'onMigrationsVersionExecuting';
const onMigrationsVersionExecuted = 'onMigrationsVersionExecuted';
const onMigrationsVersionSkipped = 'onMigrationsVersionSkipped';
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Migrations;
/**
* @since 1.6.0
* @author Luís Cobucci <lcobucci@gmail.com>
*/
final class FileQueryWriter implements QueryWriter
{
/**
* @var string
*/
private $columnName;
/**
* @var string
*/
private $tableName;
/**
* @var null|OutputWriter
*/
private $outputWriter;
public function __construct(string $columnName, string $tableName, ?OutputWriter $outputWriter)
{
$this->columnName = $columnName;
$this->tableName = $tableName;
$this->outputWriter = $outputWriter;
}
/**
* TODO: move SqlFileWriter's behaviour to this class - and kill it with fire (on the next major release)
* @param string $path
* @param string $direction
* @param array $queriesByVersion
* @return bool
*/
public function write(string $path, string $direction, array $queriesByVersion) : bool
{
$writer = new SqlFileWriter(
$this->columnName,
$this->tableName,
$path,
$this->outputWriter
);
// SqlFileWriter#write() returns `bool|int` but all clients expect it to be `bool` only
return (bool) $writer->write($queriesByVersion, $direction);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Doctrine\DBAL\Migrations\Finder;
/**
* Abstract base class for MigrationFinders
*
* @since 1.0.0-alpha3
*/
abstract class AbstractFinder implements MigrationFinderInterface
{
protected static function requireOnce($path)
{
require_once $path;
}
protected function getRealPath($directory)
{
$dir = realpath($directory);
if (false === $dir || ! is_dir($dir)) {
throw new \InvalidArgumentException(sprintf(
'Cannot load migrations from "%s" because it is not a valid directory',
$directory
));
}
return $dir;
}
/**
* Load the migrations and return an array of thoses loaded migrations
* @param array $files array of migration filename found
* @param string $namespace namespace of thoses migrations
* @return array constructed with the migration name as key and the value is the fully qualified name of the migration
*/
protected function loadMigrations($files, $namespace)
{
$migrations = [];
uasort($files, $this->getFileSortCallback());
foreach ($files as $file) {
static::requireOnce($file);
$className = basename($file, '.php');
$version = (string) substr($className, 7);
if ($version === '0') {
throw new \InvalidArgumentException(sprintf(
'Cannot load a migrations with the name "%s" because it is a reserved number by doctrine migrations' . PHP_EOL .
'It\'s used to revert all migrations including the first one.',
$version
));
}
$migrations[$version] = sprintf('%s\\%s', $namespace, $className);
}
return $migrations;
}
/**
* Return callable for files basename uasort
*
* @return callable
*/
protected function getFileSortCallback()
{
return function ($a, $b) {
return (basename($a) < basename($b)) ? -1 : 1;
};
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Doctrine\DBAL\Migrations\Finder;
/**
* A MigrationFinderInterface implementation that uses `glob` and some special file and
* class names to load migrations from a directory.
*
* The migrations are expected to reside in files with the filename
* `VersionYYYYMMDDHHMMSS.php`. Each file should contain one class named
* `VersionYYYYMMDDHHMMSS`.
*
* @since 1.0.0-alpha3
*/
final class GlobFinder extends AbstractFinder
{
/**
* {@inheritdoc}
*/
public function findMigrations($directory, $namespace = null)
{
$dir = $this->getRealPath($directory);
$files = glob(rtrim($dir, '/') . '/Version*.php');
return $this->loadMigrations($files, $namespace);
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Doctrine\DBAL\Migrations\Finder;
/**
* A MigrationDeepFinderInterface is a MigrationFinderInterface, which locates
* migrations not only in a directory itself, but in subdirectories of this directory,
* too.
*/
interface MigrationDeepFinderInterface extends MigrationFinderInterface
{
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Doctrine\DBAL\Migrations\Finder;
/**
* MigrationFinderInterface implementations locate migrations (classes that extend
* `Doctrine\DBAL\Migrations\AbstractMigration`) in a directory.
*
* @since 1.0.0-alpha3
*/
interface MigrationFinderInterface
{
/**
* Find all the migrations in a directory for the given path and namespace.
*
* @param string $directory The directory in which to look for migrations
* @param string|null $namespace The namespace of the classes to load
* @throws \InvalidArgumentException if the directory does not exist
* @return string[] An array of class names that were found with the version
* as keys.
*/
public function findMigrations($directory, $namespace = null);
}

View File

@@ -0,0 +1,60 @@
<?php
namespace Doctrine\DBAL\Migrations\Finder;
/**
* A MigrationFinderInterface implementation that uses a RegexIterator along with a
* RecursiveDirectoryIterator.
*
* @since 1.0.0-alpha3
*/
final class RecursiveRegexFinder extends AbstractFinder implements MigrationDeepFinderInterface
{
/**
* {@inheritdoc}
*/
public function findMigrations($directory, $namespace = null)
{
$dir = $this->getRealPath($directory);
return $this->loadMigrations($this->getMatches($this->createIterator($dir)), $namespace);
}
/**
* Create a recursive iterator to find all the migrations in the subdirectories.
* @param string $dir
* @return \RegexIterator
*/
private function createIterator($dir)
{
return new \RegexIterator(
new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
\RecursiveIteratorIterator::LEAVES_ONLY
),
$this->getPattern(),
\RegexIterator::GET_MATCH
);
}
private function getPattern()
{
return sprintf('#^.+\\%sVersion[^\\%s]{1,255}\\.php$#i', DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR);
}
/**
* Transform the recursiveIterator result array of array into the expected array of migration file
* @param iterable $iteratorFilesMatch
* @return array
*/
private function getMatches($iteratorFilesMatch)
{
$files = [];
foreach ($iteratorFilesMatch as $file) {
$files[] = $file[0];
}
return $files;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Doctrine\DBAL\Migrations;
/**
* Exception to be thrown in the down() methods of migrations that signifies it
* is an irreversible migration and stops execution.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
class IrreversibleMigrationException extends \Exception
{
}

View File

@@ -0,0 +1,201 @@
<?php
namespace Doctrine\DBAL\Migrations;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Migrations\Event\MigrationsEventArgs;
use const COUNT_RECURSIVE;
/**
* Class for running migrations to the current version or a manually specified version.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
class Migration
{
/**
* The OutputWriter object instance used for outputting information
*
* @var OutputWriter
*/
private $outputWriter;
/**
* @var Configuration
*/
private $configuration;
/**
* @var boolean
*/
private $noMigrationException;
/**
* Construct a Migration instance
*
* @param Configuration $configuration A migration Configuration instance
*/
public function __construct(Configuration $configuration)
{
$this->configuration = $configuration;
$this->outputWriter = $configuration->getOutputWriter();
$this->noMigrationException = false;
}
/**
* Get the array of versions and SQL queries that would be executed for
* each version but do not execute anything.
*
* @param string $to The version to migrate to.
*
* @return array $sql The array of SQL queries.
*/
public function getSql($to = null)
{
return $this->migrate($to, true);
}
/**
* Write a migration SQL file to the given path
*
* @param string $path The path to write the migration SQL file.
* @param string $to The version to migrate to.
*
* @return boolean $written
*/
public function writeSqlFile($path, $to = null)
{
$sql = $this->getSql($to);
$from = $this->configuration->getCurrentVersion();
if ($to === null) {
$to = $this->configuration->getLatestVersion();
}
$direction = $from > $to ? Version::DIRECTION_DOWN : Version::DIRECTION_UP;
$this->outputWriter->write(sprintf("-- Migrating from %s to %s\n", $from, $to));
/*
* Since the configuration object changes during the creation we cannot inject things
* properly, so I had to violate LoD here (so please, let's find a way to solve it on v2).
*/
return $this->configuration->getQueryWriter()
->write($path, $direction, $sql);
}
/**
* @param boolean $noMigrationException Throw an exception or not if no migration is found. Mostly for Continuous Integration.
*/
public function setNoMigrationException($noMigrationException = false)
{
$this->noMigrationException = $noMigrationException;
}
/**
* Run a migration to the current version or the given target version.
*
* @param string $to The version to migrate to.
* @param boolean $dryRun Whether or not to make this a dry run and not execute anything.
* @param boolean $timeAllQueries Measuring or not the execution time of each SQL query.
* @param callable|null $confirm A callback to confirm whether the migrations should be executed.
*
* @return array An array of migration sql statements. This will be empty if the the $confirm callback declines to execute the migration
*
* @throws MigrationException
*/
public function migrate($to = null, $dryRun = false, $timeAllQueries = false, callable $confirm = null)
{
/**
* If no version to migrate to is given we default to the last available one.
*/
if ($to === null) {
$to = $this->configuration->getLatestVersion();
}
$from = (string) $this->configuration->getCurrentVersion();
$to = (string) $to;
/**
* Throw an error if we can't find the migration to migrate to in the registered
* migrations.
*/
$migrations = $this->configuration->getMigrations();
if ( ! isset($migrations[$to]) && $to > 0) {
throw MigrationException::unknownMigrationVersion($to);
}
$direction = $from > $to ? Version::DIRECTION_DOWN : Version::DIRECTION_UP;
$migrationsToExecute = $this->configuration->getMigrationsToExecute($direction, $to);
/**
* If
* there are no migrations to execute
* and there are migrations,
* and the migration from and to are the same
* means we are already at the destination return an empty array()
* to signify that there is nothing left to do.
*/
if ($from === $to && empty($migrationsToExecute) && ! empty($migrations)) {
return $this->noMigrations();
}
if ( ! $dryRun && false === $this->migrationsCanExecute($confirm)) {
return [];
}
$output = $dryRun ? 'Executing dry run of migration' : 'Migrating';
$output .= ' <info>%s</info> to <comment>%s</comment> from <comment>%s</comment>';
$this->outputWriter->write(sprintf($output, $direction, $to, $from));
/**
* If there are no migrations to execute throw an exception.
*/
if (empty($migrationsToExecute) && ! $this->noMigrationException) {
throw MigrationException::noMigrationsToExecute();
} elseif (empty($migrationsToExecute)) {
return $this->noMigrations();
}
$this->configuration->dispatchEvent(
Events::onMigrationsMigrating,
new MigrationsEventArgs($this->configuration, $direction, $dryRun)
);
$sql = [];
$time = 0;
foreach ($migrationsToExecute as $version) {
$versionSql = $version->execute($direction, $dryRun, $timeAllQueries);
$sql[$version->getVersion()] = $versionSql;
$time += $version->getTime();
}
$this->configuration->dispatchEvent(
Events::onMigrationsMigrated,
new MigrationsEventArgs($this->configuration, $direction, $dryRun)
);
$this->outputWriter->write("\n <comment>------------------------</comment>\n");
$this->outputWriter->write(sprintf(" <info>++</info> finished in %ss", $time));
$this->outputWriter->write(sprintf(" <info>++</info> %s migrations executed", count($migrationsToExecute)));
$this->outputWriter->write(sprintf(" <info>++</info> %s sql queries", count($sql, COUNT_RECURSIVE) - count($sql)));
return $sql;
}
private function noMigrations() : array
{
$this->outputWriter->write('<comment>No migrations to execute.</comment>');
return [];
}
private function migrationsCanExecute(callable $confirm = null) : bool
{
return null === $confirm ? true : (bool) $confirm();
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace Doctrine\DBAL\Migrations;
use \Doctrine\DBAL\Migrations\Finder\MigrationFinderInterface;
/**
* Class for Migrations specific exceptions
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
class MigrationException extends \Exception
{
public static function migrationsNamespaceRequired()
{
return new self('Migrations namespace must be configured in order to use Doctrine migrations.', 2);
}
public static function migrationsDirectoryRequired()
{
return new self('Migrations directory must be configured in order to use Doctrine migrations.', 3);
}
public static function noMigrationsToExecute()
{
return new self('Could not find any migrations to execute.', 4);
}
public static function unknownMigrationVersion($version)
{
return new self(sprintf('Could not find migration version %s', $version), 5);
}
public static function alreadyAtVersion($version)
{
return new self(sprintf('Database is already at version %s', $version), 6);
}
public static function duplicateMigrationVersion($version, $class)
{
return new self(sprintf('Migration version %s already registered with class %s', $version, $class), 7);
}
public static function configurationFileAlreadyLoaded()
{
return new self(sprintf('Migrations configuration file already loaded'), 8);
}
public static function yamlConfigurationNotAvailable() : self
{
return new self('Unable to load yaml configuration files, please `composer require symfony/yaml` load yaml configuration files');
}
public static function configurationIncompatibleWithFinder(
$configurationParameterName,
MigrationFinderInterface $finder
) {
return new self(
sprintf(
'Configuration-parameter "%s" cannot be used with finder of type "%s"',
$configurationParameterName,
get_class($finder)
),
9
);
}
public static function configurationNotValid($msg)
{
return new self($msg, 10);
}
/**
* @param string $migrationClass
* @param string $migrationNamespace
* @return MigrationException
*/
public static function migrationClassNotFound($migrationClass, $migrationNamespace)
{
return new self(
sprintf(
'Migration class "%s" was not found. Is it placed in "%s" namespace?',
$migrationClass,
$migrationNamespace
)
);
}
/**
* @param string $migrationClass
* @return MigrationException
*/
public static function migrationNotConvertibleToSql($migrationClass)
{
return new self(
sprintf(
'Migration class "%s" contains a prepared statement.
Unfortunately there is no cross platform way of outputing it as an sql string.
Do you want to write a PR for it ?',
$migrationClass
)
);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Doctrine\DBAL\Migrations;
class MigrationsVersion
{
private static $version = 'v1.8.0';
public static function VERSION()
{
$gitversion = '@git-version@';
if (self::isACustomPharBuild($gitversion)) {
return $gitversion;
}
return self::$version;
}
/**
* @param string $gitversion
* @return bool
*
* Check if doctrine migration is installed by composer or
* in a modified (not tagged) phar version.
*/
private static function isACustomPharBuild($gitversion)
{
return $gitversion !== '@' . 'git-version@';
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Doctrine\DBAL\Migrations;
/**
* Simple class for outputting information from migrations.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
class OutputWriter
{
private $closure;
public function __construct(\Closure $closure = null)
{
if ($closure === null) {
$closure = function ($message) {
};
}
$this->closure = $closure;
}
/**
* Write output using the configured closure.
*
* @param string $message The message to write.
*/
public function write($message)
{
$closure = $this->closure;
$closure($message);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Doctrine\DBAL\Migrations\Provider;
use Doctrine\DBAL\Schema\Schema;
use ProxyManager\Configuration;
use ProxyManager\Factory\LazyLoadingValueHolderFactory;
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
use ProxyManager\Proxy\LazyLoadingInterface;
class LazySchemaDiffProvider implements SchemaDiffProviderInterface
{
/** @var LazyLoadingValueHolderFactory */
private $proxyFactory;
/** @var SchemaDiffProviderInterface */
private $originalSchemaManipulator;
public function __construct(LazyLoadingValueHolderFactory $proxyFactory, SchemaDiffProviderInterface $originalSchemaManipulator)
{
$this->proxyFactory = $proxyFactory;
$this->originalSchemaManipulator = $originalSchemaManipulator;
}
public static function fromDefaultProxyFacyoryConfiguration(SchemaDiffProviderInterface $originalSchemaManipulator)
{
$message = 'Function %s::fromDefaultProxyFacyoryConfiguration() deprecated due to typo.'
. 'Use %s::fromDefaultProxyFactoryConfiguration() instead';
trigger_error(
sprintf($message, self::class),
E_USER_DEPRECATED
);
return self::fromDefaultProxyFactoryConfiguration($originalSchemaManipulator);
}
public static function fromDefaultProxyFactoryConfiguration(SchemaDiffProviderInterface $originalSchemaManipulator)
{
$proxyConfig = new Configuration();
$proxyConfig->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
$proxyFactory = new LazyLoadingValueHolderFactory($proxyConfig);
return new LazySchemaDiffProvider($proxyFactory, $originalSchemaManipulator);
}
/**
* @return Schema
*/
public function createFromSchema()
{
$originalSchemaManipulator = $this->originalSchemaManipulator;
return $this->proxyFactory->createProxy(
Schema::class,
function (& $wrappedObject, $proxy, $method, array $parameters, & $initializer) use ($originalSchemaManipulator) {
$initializer = null;
$wrappedObject = $originalSchemaManipulator->createFromSchema();
return true;
}
);
}
/**
* @param Schema $fromSchema
* @return Schema
*/
public function createToSchema(Schema $fromSchema)
{
$originalSchemaManipulator = $this->originalSchemaManipulator;
if ($fromSchema instanceof LazyLoadingInterface && ! $fromSchema->isProxyInitialized()) {
return $this->proxyFactory->createProxy(
Schema::class,
function (& $wrappedObject, $proxy, $method, array $parameters, & $initializer) use ($originalSchemaManipulator, $fromSchema) {
$initializer = null;
$wrappedObject = $originalSchemaManipulator->createToSchema($fromSchema);
return true;
}
);
}
return $this->originalSchemaManipulator->createToSchema($fromSchema);
}
/**
* @param Schema $fromSchema
* @param Schema $toSchema
*
* @return string[]
*/
public function getSqlDiffToMigrate(Schema $fromSchema, Schema $toSchema)
{
if ($toSchema instanceof LazyLoadingInterface
&& ! $toSchema->isProxyInitialized()) {
return [];
}
return $this->originalSchemaManipulator->getSqlDiffToMigrate($fromSchema, $toSchema);
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Doctrine\DBAL\Migrations\Provider;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\SchemaTool;
/**
* A schema provider that uses the doctrine ORM to generate schemas.
*
* @since 1.0.0-alpha3
*/
final class OrmSchemaProvider implements SchemaProviderInterface
{
/**
* @var EntityManagerInterface
*/
private $entityManager;
public function __construct($em)
{
if ( ! $this->isEntityManager($em)) {
throw new \InvalidArgumentException(sprintf(
'$em is not a valid Doctrine ORM Entity Manager, got "%s"',
is_object($em) ? get_class($em) : gettype($em)
));
}
$this->entityManager = $em;
}
/**
* {@inheritdoc}
*/
public function createSchema()
{
$metadata = $this->entityManager->getMetadataFactory()->getAllMetadata();
if (empty($metadata)) {
throw new \UnexpectedValueException('No mapping information to process');
}
$tool = new SchemaTool($this->entityManager);
return $tool->getSchemaFromMetadata($metadata);
}
/**
* Doctrine's EntityManagerInterface was introduced in version 2.4, since this
* library allows those older version we need to be able to check for those
* old ORM versions. Hence the helper method.
*
* No need to check to see if EntityManagerInterface exists first here, PHP
* doesn't care.
*
* @param mixed $manager Hopefully an entity manager, but it may be anything
* @return boolean
*/
private function isEntityManager($manager)
{
return $manager instanceof EntityManagerInterface || $manager instanceof EntityManager;
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Doctrine\DBAL\Migrations\Provider;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Schema;
class SchemaDiffProvider implements SchemaDiffProviderInterface
{
/** @var AbstractPlatform */
private $platform;
/** @var AbstractSchemaManager */
private $schemaManager;
public function __construct(AbstractSchemaManager $schemaManager, AbstractPlatform $platform)
{
$this->schemaManager = $schemaManager;
$this->platform = $platform;
}
/**
* @return Schema
*/
public function createFromSchema()
{
return $this->schemaManager->createSchema();
}
/**
* @param Schema $fromSchema
* @return Schema
*/
public function createToSchema(Schema $fromSchema)
{
return clone $fromSchema;
}
/**
* @param Schema $fromSchema
* @param Schema $toSchema
* @return string[]
*/
public function getSqlDiffToMigrate(Schema $fromSchema, Schema $toSchema)
{
return $fromSchema->getMigrateToSql($toSchema, $this->platform);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Doctrine\DBAL\Migrations\Provider;
use Doctrine\DBAL\Schema\Schema;
/**
* Generates `Schema` objects to be passed to the migrations class.
*
* @since 1.3
*/
interface SchemaDiffProviderInterface
{
/**
* Create the schema that represent the current state of the database.
*
* @return Schema
*/
public function createFromSchema();
/**
* Create the schema that will represent the future state of the database
*
* @param Schema $fromSchema
* @return Schema
*/
public function createToSchema(Schema $fromSchema);
/**
* Return an array of sql statement that migrate the database state from the
* fromSchema to the toSchema.
*
* @param Schema $fromSchema
* @param Schema $toSchema
*
* @return string[]
*/
public function getSqlDiffToMigrate(Schema $fromSchema, Schema $toSchema);
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Doctrine\DBAL\Migrations\Provider;
/**
* Generates `Schema` objects for the diff command. A schema provider should
* return the schema to which the database should be migrated.
*
* @since 1.0.0-alpha3
*/
interface SchemaProviderInterface
{
/**
* Create the schema to which the database should be migrated.
*
* @return \Doctrine\DBAL\Schema\Schema
*/
public function createSchema();
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Doctrine\DBAL\Migrations\Provider;
use Doctrine\DBAL\Schema\Schema;
/**
* A schemea provider implementation that just returns the schema its given.
*
* @since 1.0.0-alpha3
*/
final class StubSchemaProvider implements SchemaProviderInterface
{
/**
* @var Schema
*/
private $toSchema;
public function __construct(Schema $schema)
{
$this->toSchema = $schema;
}
/**
* {@inheritdoc}
*/
public function createSchema()
{
return $this->toSchema;
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Migrations;
/**
* @since 1.6.0
* @author Luís Cobucci <lcobucci@gmail.com>
*/
interface QueryWriter
{
/**
* @param string $path
* @param string $direction
* @param array $queriesByVersion
* @return bool
*/
public function write(string $path, string $direction, array $queriesByVersion) : bool;
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Doctrine\DBAL\Migrations;
class SkipMigrationException extends MigrationException
{
}

View File

@@ -0,0 +1,115 @@
<?php
namespace Doctrine\DBAL\Migrations;
use Doctrine\DBAL\Exception\InvalidArgumentException;
/**
* @deprecated
*
* @see \Doctrine\DBAL\Migrations\FileQueryWriter
*/
class SqlFileWriter
{
private $migrationsColumnName;
private $migrationsTableName;
private $destPath;
/** @var null|OutputWriter */
private $outputWriter;
/**
* @param string $migrationsColumnName
* @param string $migrationsTableName
* @param string $destPath
* @param \Doctrine\DBAL\Migrations\OutputWriter $outputWriter
*/
public function __construct(
$migrationsColumnName,
$migrationsTableName,
$destPath,
OutputWriter $outputWriter = null
) {
if (empty($migrationsColumnName)) {
$this->throwInvalidArgumentException('Migrations column name cannot be empty.');
}
if (empty($migrationsTableName)) {
$this->throwInvalidArgumentException('Migrations table name cannot be empty.');
}
if (empty($destPath)) {
$this->throwInvalidArgumentException('Destination file must be specified.');
}
$this->migrationsColumnName = $migrationsColumnName;
$this->migrationsTableName = $migrationsTableName;
$this->destPath = $destPath;
$this->outputWriter = $outputWriter;
}
/**
* @param array $queriesByVersion array Keys are versions and values are arrays of SQL queries (they must be castable to string)
* @param string $direction
* @return int|bool
*/
public function write(array $queriesByVersion, $direction)
{
$path = $this->buildMigrationFilePath();
$string = $this->buildMigrationFile($queriesByVersion, $direction);
if ($this->outputWriter) {
$this->outputWriter->write("\n" . sprintf('Writing migration file to "<info>%s</info>"', $path));
}
return file_put_contents($path, $string);
}
private function buildMigrationFile(array $queriesByVersion, string $direction) : string
{
$string = sprintf("-- Doctrine Migration File Generated on %s\n", date('Y-m-d H:i:s'));
foreach ($queriesByVersion as $version => $queries) {
$string .= "\n-- Version " . $version . "\n";
foreach ($queries as $query) {
$string .= $query . ";\n";
}
$string .= $this->getVersionUpdateQuery($version, $direction);
}
return $string;
}
private function getVersionUpdateQuery(string $version, string $direction) : string
{
if ($direction === Version::DIRECTION_DOWN) {
$query = "DELETE FROM %s WHERE %s = '%s';\n";
} else {
$query = "INSERT INTO %s (%s) VALUES ('%s');\n";
}
return sprintf($query, $this->migrationsTableName, $this->migrationsColumnName, $version);
}
private function buildMigrationFilePath() : string
{
$path = $this->destPath;
if (is_dir($path)) {
$path = realpath($path);
$path = $path . '/doctrine_migration_' . date('YmdHis') . '.sql';
}
return $path;
}
protected function throwInvalidArgumentException($message)
{
throw new InvalidArgumentException($message);
}
}

View File

@@ -0,0 +1,158 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Command;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Migrations\Configuration\Connection\Loader\ArrayConnectionConfigurationLoader;
use Doctrine\DBAL\Migrations\Configuration\Connection\Loader\ConnectionConfigurationLoader;
use Doctrine\DBAL\Migrations\Configuration\Connection\Loader\ConnectionHelperLoader;
use Doctrine\DBAL\Migrations\Configuration\Connection\Loader\ConnectionConfigurationChainLoader;
use Doctrine\DBAL\Migrations\OutputWriter;
use Doctrine\DBAL\Migrations\Tools\Console\Helper\ConfigurationHelper;
use Doctrine\DBAL\Migrations\Tools\Console\Helper\ConfigurationHelperInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
/**
* CLI Command for adding and deleting migration versions from the version table.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan Wage <jonwage@gmail.com>
*/
abstract class AbstractCommand extends Command
{
/**
* The configuration property only contains the configuration injected by the setter.
*
* @var Configuration
*/
private $configuration;
/**
* The migrationConfiguration property contains the configuration
* created taking into account the command line options.
*
* @var Configuration
*/
private $migrationConfiguration;
/**
* @var OutputWriter
*/
private $outputWriter;
/**
* @var \Doctrine\DBAL\Connection
*/
private $connection;
protected function configure()
{
$this->addOption('configuration', null, InputOption::VALUE_OPTIONAL, 'The path to a migrations configuration file.');
$this->addOption('db-configuration', null, InputOption::VALUE_OPTIONAL, 'The path to a database connection configuration file.');
}
protected function outputHeader(Configuration $configuration, OutputInterface $output)
{
$name = $configuration->getName();
$name = $name ? $name : 'Doctrine Database Migrations';
$name = str_repeat(' ', 20) . $name . str_repeat(' ', 20);
$output->writeln('<question>' . str_repeat(' ', strlen($name)) . '</question>');
$output->writeln('<question>' . $name . '</question>');
$output->writeln('<question>' . str_repeat(' ', strlen($name)) . '</question>');
$output->writeln('');
}
public function setMigrationConfiguration(Configuration $config)
{
$this->configuration = $config;
}
/**
* When any (config) command line option is passed to the migration the migrationConfiguration
* property is set with the new generated configuration.
* If no (config) option is passed the migrationConfiguration property is set to the value
* of the configuration one (if any).
* Else a new configuration is created and assigned to the migrationConfiguration property.
*
* @param InputInterface $input
* @param OutputInterface $output
*
* @return Configuration
*/
protected function getMigrationConfiguration(InputInterface $input, OutputInterface $output)
{
if ( ! $this->migrationConfiguration) {
if ($this->getHelperSet()->has('configuration')
&& $this->getHelperSet()->get('configuration') instanceof ConfigurationHelperInterface) {
$configHelper = $this->getHelperSet()->get('configuration');
} else {
$configHelper = new ConfigurationHelper($this->getConnection($input), $this->configuration);
}
$this->migrationConfiguration = $configHelper->getMigrationConfig($input, $this->getOutputWriter($output));
}
return $this->migrationConfiguration;
}
/**
* @param string $question
* @param InputInterface $input
* @param OutputInterface $output
* @return mixed
*/
protected function askConfirmation($question, InputInterface $input, OutputInterface $output)
{
return $this->getHelper('question')->ask($input, $output, new ConfirmationQuestion($question));
}
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return \Doctrine\DBAL\Migrations\OutputWriter
*/
private function getOutputWriter(OutputInterface $output)
{
if ( ! $this->outputWriter) {
$this->outputWriter = new OutputWriter(function ($message) use ($output) {
return $output->writeln($message);
});
}
return $this->outputWriter;
}
/**
* @param \Symfony\Component\Console\Input\InputInterface $input
*
* @return \Doctrine\DBAL\Connection
* @throws \Doctrine\DBAL\DBALException
*/
private function getConnection(InputInterface $input)
{
if ($this->connection) {
return $this->connection;
}
$chainLoader = new ConnectionConfigurationChainLoader(
[
new ArrayConnectionConfigurationLoader($input->getOption('db-configuration')),
new ArrayConnectionConfigurationLoader('migrations-db.php'),
new ConnectionHelperLoader($this->getHelperSet(), 'connection'),
new ConnectionConfigurationLoader($this->configuration),
]
);
$connection = $chainLoader->chosen();
if ($connection) {
return $this->connection = $connection;
}
throw new \InvalidArgumentException('You have to specify a --db-configuration file or pass a Database Connection as a dependency to the Migrations.');
}
}

View File

@@ -0,0 +1,182 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Command;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Version as DbalVersion;
use Doctrine\DBAL\Migrations\Provider\SchemaProviderInterface;
use Doctrine\DBAL\Migrations\Provider\OrmSchemaProvider;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
/**
* Command for generate migration classes by comparing your current database schema
* to your mapping information.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan Wage <jonwage@gmail.com>
*/
class DiffCommand extends GenerateCommand
{
/**
* @var SchemaProviderInterface
*/
protected $schemaProvider;
public function __construct(SchemaProviderInterface $schemaProvider = null)
{
$this->schemaProvider = $schemaProvider;
parent::__construct();
}
protected function configure()
{
parent::configure();
$this
->setName('migrations:diff')
->setDescription('Generate a migration by comparing your current database to your mapping information.')
->setHelp(<<<EOT
The <info>%command.name%</info> command generates a migration by comparing your current database to your mapping information:
<info>%command.full_name%</info>
You can optionally specify a <comment>--editor-cmd</comment> option to open the generated file in your favorite editor:
<info>%command.full_name% --editor-cmd=mate</info>
EOT
)
->addOption('filter-expression', null, InputOption::VALUE_OPTIONAL, 'Tables which are filtered by Regular Expression.')
->addOption('formatted', null, InputOption::VALUE_NONE, 'Format the generated SQL.')
->addOption('line-length', null, InputOption::VALUE_OPTIONAL, 'Max line length of unformatted lines.', 120)
;
}
public function execute(InputInterface $input, OutputInterface $output)
{
$isDbalOld = (DbalVersion::compare('2.2.0') > 0);
$configuration = $this->getMigrationConfiguration($input, $output);
$this->loadCustomTemplate($configuration, $output);
$conn = $configuration->getConnection();
$platform = $conn->getDatabasePlatform();
if ($filterExpr = $input->getOption('filter-expression')) {
if ($isDbalOld) {
throw new \InvalidArgumentException('The "--filter-expression" option can only be used as of Doctrine DBAL 2.2');
}
$conn->getConfiguration()
->setFilterSchemaAssetsExpression($filterExpr);
}
$fromSchema = $conn->getSchemaManager()->createSchema();
$toSchema = $this->getSchemaProvider()->createSchema();
//Not using value from options, because filters can be set from config.yml
if ( ! $isDbalOld && $filterExpr = $conn->getConfiguration()->getFilterSchemaAssetsExpression()) {
foreach ($toSchema->getTables() as $table) {
$tableName = $table->getName();
if ( ! preg_match($filterExpr, $this->resolveTableName($tableName))) {
$toSchema->dropTable($tableName);
}
}
}
$up = $this->buildCodeFromSql(
$configuration,
$fromSchema->getMigrateToSql($toSchema, $platform),
$input->getOption('formatted'),
$input->getOption('line-length')
);
$down = $this->buildCodeFromSql(
$configuration,
$fromSchema->getMigrateFromSql($toSchema, $platform),
$input->getOption('formatted'),
$input->getOption('line-length')
);
if ( ! $up && ! $down) {
$output->writeln('No changes detected in your mapping information.');
return;
}
$version = $configuration->generateVersionNumber();
$path = $this->generateMigration($configuration, $input, $version, $up, $down);
$output->writeln(sprintf('Generated new migration class to "<info>%s</info>" from schema differences.', $path));
$output->writeln(file_get_contents($path), OutputInterface::VERBOSITY_VERBOSE);
}
private function buildCodeFromSql(Configuration $configuration, array $sql, $formatted = false, $lineLength = 120)
{
$currentPlatform = $configuration->getConnection()->getDatabasePlatform()->getName();
$code = [];
foreach ($sql as $query) {
if (stripos($query, $configuration->getMigrationsTableName()) !== false) {
continue;
}
if ($formatted) {
if ( ! class_exists('\SqlFormatter')) {
throw new \InvalidArgumentException(
'The "--formatted" option can only be used if the sql formatter is installed.' .
'Please run "composer require jdorn/sql-formatter".'
);
}
$maxLength = $lineLength - 18 - 8; // max - php code length - indentation
if (strlen($query) > $maxLength) {
$query = \SqlFormatter::format($query, false);
}
}
$code[] = sprintf("\$this->addSql(%s);", var_export($query, true));
}
if ( ! empty($code)) {
array_unshift(
$code,
sprintf(
"\$this->abortIf(\$this->connection->getDatabasePlatform()->getName() !== %s, %s);",
var_export($currentPlatform, true),
var_export(sprintf("Migration can only be executed safely on '%s'.", $currentPlatform), true)
),
""
);
}
return implode("\n", $code);
}
private function getSchemaProvider()
{
if ( ! $this->schemaProvider) {
$this->schemaProvider = new OrmSchemaProvider($this->getHelper('entityManager')->getEntityManager());
}
return $this->schemaProvider;
}
/**
* Resolve a table name from its fully qualified name. The `$name` argument
* comes from Doctrine\DBAL\Schema\Table#getName which can sometimes return
* a namespaced name with the form `{namespace}.{tableName}`. This extracts
* the table name from that.
*
* @param string $name
* @return string
*/
private function resolveTableName($name)
{
$pos = strpos($name, '.');
return false === $pos ? $name : substr($name, $pos + 1);
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
/**
* Command for executing single migrations up or down manually.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan Wage <jonwage@gmail.com>
*/
class ExecuteCommand extends AbstractCommand
{
protected function configure()
{
$this
->setName('migrations:execute')
->setDescription('Execute a single migration version up or down manually.')
->addArgument('version', InputArgument::REQUIRED, 'The version to execute.', null)
->addOption('write-sql', null, InputOption::VALUE_NONE, 'The path to output the migration SQL file instead of executing it.')
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Execute the migration as a dry run.')
->addOption('up', null, InputOption::VALUE_NONE, 'Execute the migration up.')
->addOption('down', null, InputOption::VALUE_NONE, 'Execute the migration down.')
->addOption('query-time', null, InputOption::VALUE_NONE, 'Time all the queries individually.')
->setHelp(<<<EOT
The <info>%command.name%</info> command executes a single migration version up or down manually:
<info>%command.full_name% YYYYMMDDHHMMSS</info>
If no <comment>--up</comment> or <comment>--down</comment> option is specified it defaults to up:
<info>%command.full_name% YYYYMMDDHHMMSS --down</info>
You can also execute the migration as a <comment>--dry-run</comment>:
<info>%command.full_name% YYYYMMDDHHMMSS --dry-run</info>
You can output the would be executed SQL statements to a file with <comment>--write-sql</comment>:
<info>%command.full_name% YYYYMMDDHHMMSS --write-sql</info>
Or you can also execute the migration without a warning message which you need to interact with:
<info>%command.full_name% YYYYMMDDHHMMSS --no-interaction</info>
EOT
);
parent::configure();
}
public function execute(InputInterface $input, OutputInterface $output)
{
$version = $input->getArgument('version');
$direction = $input->getOption('down') ? 'down' : 'up';
$configuration = $this->getMigrationConfiguration($input, $output);
$version = $configuration->getVersion($version);
$timeAllqueries = $input->getOption('query-time');
if ($path = $input->getOption('write-sql')) {
$path = is_bool($path) ? getcwd() : $path;
$version->writeSqlFile($path, $direction);
} else {
if ($input->isInteractive()) {
$question = 'WARNING! You are about to execute a database migration that could result in schema changes and data lost. Are you sure you wish to continue? (y/n)';
$execute = $this->askConfirmation($question, $input, $output);
} else {
$execute = true;
}
if ($execute) {
$version->execute($direction, (boolean) $input->getOption('dry-run'), $timeAllqueries);
} else {
$output->writeln('<error>Migration cancelled!</error>');
}
}
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Command;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Migrations\Tools\Console\Helper\MigrationDirectoryHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
/**
* Command for generating new blank migration classes
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan Wage <jonwage@gmail.com>
*/
class GenerateCommand extends AbstractCommand
{
private static $_template =
'<?php declare(strict_types=1);
namespace <namespace>;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version<version> extends AbstractMigration
{
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
<up>
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
<down>
}
}
';
private $instanceTemplate;
protected function configure()
{
$this
->setName('migrations:generate')
->setDescription('Generate a blank migration class.')
->addOption('editor-cmd', null, InputOption::VALUE_OPTIONAL, 'Open file with this command upon creation.')
->setHelp(<<<EOT
The <info>%command.name%</info> command generates a blank migration class:
<info>%command.full_name%</info>
You can optionally specify a <comment>--editor-cmd</comment> option to open the generated file in your favorite editor:
<info>%command.full_name% --editor-cmd=mate</info>
EOT
);
parent::configure();
}
public function execute(InputInterface $input, OutputInterface $output)
{
$configuration = $this->getMigrationConfiguration($input, $output);
$this->loadCustomTemplate($configuration, $output);
$version = $configuration->generateVersionNumber();
$path = $this->generateMigration($configuration, $input, $version);
$output->writeln(sprintf('Generated new migration class to "<info>%s</info>"', $path));
}
protected function getTemplate()
{
if ($this->instanceTemplate === null) {
$this->instanceTemplate = self::$_template;
}
return $this->instanceTemplate;
}
protected function generateMigration(Configuration $configuration, InputInterface $input, $version, $up = null, $down = null)
{
$placeHolders = [
'<namespace>',
'<version>',
'<up>',
'<down>',
];
$replacements = [
$configuration->getMigrationsNamespace(),
$version,
$up ? " " . implode("\n ", explode("\n", $up)) : null,
$down ? " " . implode("\n ", explode("\n", $down)) : null,
];
$code = str_replace($placeHolders, $replacements, $this->getTemplate());
$code = preg_replace('/^ +$/m', '', $code);
$directoryHelper = new MigrationDirectoryHelper($configuration);
$dir = $directoryHelper->getMigrationDirectory();
$path = $dir . '/Version' . $version . '.php';
file_put_contents($path, $code);
if ($editorCmd = $input->getOption('editor-cmd')) {
proc_open($editorCmd . ' ' . escapeshellarg($path), [], $pipes);
}
return $path;
}
protected function loadCustomTemplate(Configuration $configuration, OutputInterface $output) : void
{
$customTemplate = $configuration->getCustomTemplate();
if ($customTemplate === null) {
return;
}
if ( ! is_file($customTemplate) || ! is_readable($customTemplate)) {
throw new \InvalidArgumentException(
'The specified template "' . $customTemplate . '" cannot be found or is not readable.'
);
}
$content = file_get_contents($customTemplate);
if ($content === false) {
throw new \InvalidArgumentException('The specified template "' . $customTemplate . '" could not be read.');
}
$output->writeln(sprintf('Using custom migration template "<info>%s</info>"', $customTemplate));
$this->instanceTemplate = $content;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Outputs the latest version number.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class LatestCommand extends AbstractCommand
{
protected function configure()
{
$this
->setName('migrations:latest')
->setDescription('Outputs the latest version number')
;
parent::configure();
}
public function execute(InputInterface $input, OutputInterface $output)
{
$configuration = $this->getMigrationConfiguration($input, $output);
$output->writeln($configuration->getLatestVersion());
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Command;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Migrations\Migration;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
/**
* Command for executing a migration to a specified version or the latest available version.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan Wage <jonwage@gmail.com>
*/
class MigrateCommand extends AbstractCommand
{
protected function configure()
{
$this
->setName('migrations:migrate')
->setDescription('Execute a migration to a specified version or the latest available version.')
->addArgument('version', InputArgument::OPTIONAL, 'The version number (YYYYMMDDHHMMSS) or alias (first, prev, next, latest) to migrate to.', 'latest')
->addOption('write-sql', null, InputOption::VALUE_OPTIONAL, 'The path to output the migration SQL file instead of executing it. Default to current working directory.')
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Execute the migration as a dry run.')
->addOption('query-time', null, InputOption::VALUE_NONE, 'Time all the queries individually.')
->addOption('allow-no-migration', null, InputOption::VALUE_NONE, 'Don\'t throw an exception if no migration is available (CI).')
->setHelp(<<<EOT
The <info>%command.name%</info> command executes a migration to a specified version or the latest available version:
<info>%command.full_name%</info>
You can optionally manually specify the version you wish to migrate to:
<info>%command.full_name% YYYYMMDDHHMMSS</info>
You can specify the version you wish to migrate to using an alias:
<info>%command.full_name% prev</info>
<info>These alias are defined : first, latest, prev, current and next</info>
You can specify the version you wish to migrate to using an number against the current version:
<info>%command.full_name% current+3</info>
You can also execute the migration as a <comment>--dry-run</comment>:
<info>%command.full_name% YYYYMMDDHHMMSS --dry-run</info>
You can output the would be executed SQL statements to a file with <comment>--write-sql</comment>:
<info>%command.full_name% YYYYMMDDHHMMSS --write-sql</info>
Or you can also execute the migration without a warning message which you need to interact with:
<info>%command.full_name% --no-interaction</info>
You can also time all the different queries if you wanna know which one is taking so long:
<info>%command.full_name% --query-time</info>
EOT
);
parent::configure();
}
public function execute(InputInterface $input, OutputInterface $output)
{
$configuration = $this->getMigrationConfiguration($input, $output);
$migration = $this->createMigration($configuration);
$this->outputHeader($configuration, $output);
$timeAllqueries = $input->getOption('query-time');
$dryRun = (boolean) $input->getOption('dry-run');
$configuration->setIsDryRun($dryRun);
$executedMigrations = $configuration->getMigratedVersions();
$availableMigrations = $configuration->getAvailableVersions();
$version = $this->getVersionNameFromAlias($input->getArgument('version'), $output, $configuration);
if ($version === false) {
return 1;
}
$executedUnavailableMigrations = array_diff($executedMigrations, $availableMigrations);
if ( ! empty($executedUnavailableMigrations)) {
$output->writeln(sprintf(
'<error>WARNING! You have %s previously executed migrations'
. ' in the database that are not registered migrations.</error>',
count($executedUnavailableMigrations)
));
foreach ($executedUnavailableMigrations as $executedUnavailableMigration) {
$output->writeln(sprintf(
' <comment>>></comment> %s (<comment>%s</comment>)',
$configuration->getDateTime($executedUnavailableMigration),
$executedUnavailableMigration
));
}
$question = 'Are you sure you wish to continue? (y/n)';
if ( ! $this->canExecute($question, $input, $output)) {
$output->writeln('<error>Migration cancelled!</error>');
return 1;
}
}
if ($path = $input->getOption('write-sql')) {
$path = is_bool($path) ? getcwd() : $path;
$migration->writeSqlFile($path, $version);
return 0;
}
$cancelled = false;
$migration->setNoMigrationException($input->getOption('allow-no-migration'));
$result = $migration->migrate($version, $dryRun, $timeAllqueries, function () use ($input, $output, &$cancelled) {
$question = 'WARNING! You are about to execute a database migration'
. ' that could result in schema changes and data loss.'
. ' Are you sure you wish to continue? (y/n)';
$canContinue = $this->canExecute($question, $input, $output);
$cancelled = ! $canContinue;
return $canContinue;
});
if ($cancelled) {
$output->writeln('<error>Migration cancelled!</error>');
return 1;
}
}
/**
* Create a new migration instance to execute the migrations.
*
* @param Configuration $configuration The configuration with which the migrations will be executed
* @return Migration a new migration instance
*/
protected function createMigration(Configuration $configuration)
{
return new Migration($configuration);
}
/**
* @param string $question
* @param InputInterface $input
* @param OutputInterface $output
* @return bool
*/
private function canExecute($question, InputInterface $input, OutputInterface $output)
{
if ($input->isInteractive() && ! $this->askConfirmation($question, $input, $output)) {
return false;
}
return true;
}
/**
* @param string $versionAlias
* @param OutputInterface $output
* @param Configuration $configuration
* @return bool|string
*/
private function getVersionNameFromAlias($versionAlias, OutputInterface $output, Configuration $configuration)
{
$version = $configuration->resolveVersionAlias($versionAlias);
if ($version === null) {
if ($versionAlias == 'prev') {
$output->writeln('<error>Already at first version.</error>');
return false;
}
if ($versionAlias == 'next') {
$output->writeln('<error>Already at latest version.</error>');
return false;
}
if (substr($versionAlias, 0, 7) == 'current') {
$output->writeln('<error>The delta couldn\'t be reached.</error>');
return false;
}
$output->writeln(sprintf(
'<error>Unknown version: %s</error>',
OutputFormatter::escape($versionAlias)
));
return false;
}
return $version;
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Command;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Migrations\Tools\Console\Helper\MigrationStatusInfosHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
/**
* Command to view the status of a set of migrations.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan Wage <jonwage@gmail.com>
*/
class StatusCommand extends AbstractCommand
{
protected function configure()
{
$this
->setName('migrations:status')
->setDescription('View the status of a set of migrations.')
->addOption('show-versions', null, InputOption::VALUE_NONE, 'This will display a list of all available migrations and their status')
->setHelp(<<<EOT
The <info>%command.name%</info> command outputs the status of a set of migrations:
<info>%command.full_name%</info>
You can output a list of all available migrations and their status with <comment>--show-versions</comment>:
<info>%command.full_name% --show-versions</info>
EOT
);
parent::configure();
}
public function execute(InputInterface $input, OutputInterface $output)
{
$configuration = $this->getMigrationConfiguration($input, $output);
$infos = new MigrationStatusInfosHelper($configuration);
$output->writeln("\n <info>==</info> Configuration\n");
foreach ($infos->getMigrationsInfos() as $name => $value) {
if ($name == 'New Migrations') {
$value = $value > 0 ? '<question>' . $value . '</question>' : 0;
}
if ($name == 'Executed Unavailable Migrations') {
$value = $value > 0 ? '<error>' . $value . '</error>' : 0;
}
$this->writeStatusInfosLineAligned($output, $name, $value);
}
if ($input->getOption('show-versions')) {
if ($migrations = $configuration->getMigrations()) {
$output->writeln("\n <info>==</info> Available Migration Versions\n");
$this->showVersions($migrations, $configuration, $output);
}
if (count($infos->getExecutedUnavailableMigrations())) {
$output->writeln("\n <info>==</info> Previously Executed Unavailable Migration Versions\n");
foreach ($infos->getExecutedUnavailableMigrations() as $executedUnavailableMigration) {
$output->writeln(' <comment>>></comment> ' . $configuration->getDateTime($executedUnavailableMigration) .
' (<comment>' . $executedUnavailableMigration . '</comment>)');
}
}
}
}
private function writeStatusInfosLineAligned(OutputInterface $output, $title, $value)
{
$output->writeln(' <comment>>></comment> ' . $title . ': ' . str_repeat(' ', 50 - strlen($title)) . $value);
}
private function showVersions($migrations, Configuration $configuration, OutputInterface $output)
{
$migratedVersions = $configuration->getMigratedVersions();
foreach ($migrations as $version) {
$isMigrated = in_array($version->getVersion(), $migratedVersions, true);
$status = $isMigrated ? '<info>migrated</info>' : '<error>not migrated</error>';
$migrationDescription = $version->getMigration()->getDescription()
? str_repeat(' ', 5) . $version->getMigration()->getDescription()
: '';
$formattedVersion = $configuration->getDateTime($version->getVersion());
$output->writeln(' <comment>>></comment> ' . $formattedVersion .
' (<comment>' . $version->getVersion() . '</comment>)' .
str_repeat(' ', max(1, 49 - strlen($formattedVersion) - strlen($version->getVersion()))) .
$status . $migrationDescription);
}
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command to show if your schema is up-to-date.
*/
class UpToDateCommand extends AbstractCommand
{
protected function configure()
{
$this
->setName('migrations:up-to-date')
->setDescription('Tells you if your schema is up-to-date.')
->setHelp(<<<EOT
The <info>%command.name%</info> command tells you if your schema is up-to-date:
<info>%command.full_name%</info>
EOT
);
parent::configure();
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int|null
*/
public function execute(InputInterface $input, OutputInterface $output)
{
$configuration = $this->getMigrationConfiguration($input, $output);
$migrations = count($configuration->getMigrations());
$migratedVersions = count($configuration->getMigratedVersions());
$availableMigrations = $migrations - $migratedVersions;
if ($availableMigrations === 0) {
$output->writeln('<comment>Up-to-date! No migrations to execute.</comment>');
return 0;
}
$output->writeln(sprintf(
'<comment>Out-of-date! %u migration%s available to execute.</comment>',
$availableMigrations,
$availableMigrations > 1 ? 's are' : ' is'
));
return 1;
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Command;
use Doctrine\DBAL\Migrations\MigrationException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
/**
* Command for manually adding and deleting migration versions from the version table.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan Wage <jonwage@gmail.com>
*/
class VersionCommand extends AbstractCommand
{
/**
* The Migrations Configuration instance
*
* @var \Doctrine\DBAL\Migrations\Configuration\Configuration
*/
private $configuration;
/**
* Whether or not the versions have to be marked as migrated or not
*
* @var boolean
*/
private $markMigrated;
protected function configure()
{
$this
->setName('migrations:version')
->setDescription('Manually add and delete migration versions from the version table.')
->addArgument('version', InputArgument::OPTIONAL, 'The version to add or delete.', null)
->addOption('add', null, InputOption::VALUE_NONE, 'Add the specified version.')
->addOption('delete', null, InputOption::VALUE_NONE, 'Delete the specified version.')
->addOption('all', null, InputOption::VALUE_NONE, 'Apply to all the versions.')
->addOption('range-from', null, InputOption::VALUE_OPTIONAL, 'Apply from specified version.')
->addOption('range-to', null, InputOption::VALUE_OPTIONAL, 'Apply to specified version.')
->setHelp(<<<EOT
The <info>%command.name%</info> command allows you to manually add, delete or synchronize migration versions from the version table:
<info>%command.full_name% YYYYMMDDHHMMSS --add</info>
If you want to delete a version you can use the <comment>--delete</comment> option:
<info>%command.full_name% YYYYMMDDHHMMSS --delete</info>
If you want to synchronize by adding or deleting all migration versions available in the version table you can use the <comment>--all</comment> option:
<info>%command.full_name% --add --all</info>
<info>%command.full_name% --delete --all</info>
If you want to synchronize by adding or deleting some range of migration versions available in the version table you can use the <comment>--range-from/--range-to</comment> option:
<info>%command.full_name% --add --range-from=YYYYMMDDHHMMSS --range-to=YYYYMMDDHHMMSS</info>
<info>%command.full_name% --delete --range-from=YYYYMMDDHHMMSS --range-to=YYYYMMDDHHMMSS</info>
You can also execute this command without a warning message which you need to interact with:
<info>%command.full_name% --no-interaction</info>
EOT
);
parent::configure();
}
public function execute(InputInterface $input, OutputInterface $output)
{
$this->configuration = $this->getMigrationConfiguration($input, $output);
if ( ! $input->getOption('add') && ! $input->getOption('delete')) {
throw new \InvalidArgumentException('You must specify whether you want to --add or --delete the specified version.');
}
$this->markMigrated = (boolean) $input->getOption('add');
if ($input->isInteractive()) {
$question = 'WARNING! You are about to add, delete or synchronize migration versions from the version table that could result in data lost. Are you sure you wish to continue? (y/n)';
$confirmation = $this->askConfirmation($question, $input, $output);
if ($confirmation) {
$this->markVersions($input);
} else {
$output->writeln('<error>Migration cancelled!</error>');
}
} else {
$this->markVersions($input);
}
}
private function markVersions(InputInterface $input)
{
$affectedVersion = $input->getArgument('version');
$allOption = $input->getOption('all');
$rangeFromOption = $input->getOption('range-from');
$rangeToOption = $input->getOption('range-to');
if ($allOption && ($rangeFromOption !== null || $rangeToOption !== null)) {
throw new \InvalidArgumentException('Options --all and --range-to/--range-from both used. You should use only one of them.');
}
if ($rangeFromOption !== null ^ $rangeToOption !== null) {
throw new \InvalidArgumentException('Options --range-to and --range-from should be used together.');
}
if ($allOption === true) {
$availableVersions = $this->configuration->getAvailableVersions();
foreach ($availableVersions as $version) {
$this->mark($version, true);
}
} elseif ($rangeFromOption !== null && $rangeToOption !== null) {
$availableVersions = $this->configuration->getAvailableVersions();
foreach ($availableVersions as $version) {
if ($version >= $rangeFromOption && $version <= $rangeToOption) {
$this->mark($version, true);
}
}
} else {
$this->mark($affectedVersion);
}
}
private function mark($version, $all = false)
{
if ( ! $this->configuration->hasVersion($version)) {
throw MigrationException::unknownMigrationVersion($version);
}
$version = $this->configuration->getVersion($version);
if ($this->markMigrated && $this->configuration->hasVersionMigrated($version)) {
if ( ! $all) {
throw new \InvalidArgumentException(sprintf('The version "%s" already exists in the version table.', $version));
}
$marked = true;
}
if ( ! $this->markMigrated && ! $this->configuration->hasVersionMigrated($version)) {
if ( ! $all) {
throw new \InvalidArgumentException(sprintf('The version "%s" does not exist in the version table.', $version));
}
$marked = false;
}
if ( ! isset($marked)) {
if ($this->markMigrated) {
$version->markMigrated();
} else {
$version->markNotMigrated();
}
}
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console;
use Doctrine\DBAL\Migrations\MigrationsVersion;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
/**
* Handles running the Console Tools inside Symfony Console context.
*/
class ConsoleRunner
{
/**
* Runs console with the given helperset.
*
* @param \Symfony\Component\Console\Helper\HelperSet $helperSet
* @param \Symfony\Component\Console\Command\Command[] $commands
*
* @return void
*/
public static function run(HelperSet $helperSet, $commands = [])
{
$cli = self::createApplication($helperSet, $commands);
$cli->run();
}
/**
* Creates a console application with the given helperset and
* optional commands.
*
* @param \Symfony\Component\Console\Helper\HelperSet $helperSet
* @param array $commands
*
* @return \Symfony\Component\Console\Application
*/
public static function createApplication(HelperSet $helperSet, $commands = [])
{
$cli = new Application('Doctrine Migrations', MigrationsVersion::VERSION());
$cli->setCatchExceptions(true);
$cli->setHelperSet($helperSet);
self::addCommands($cli);
$cli->addCommands($commands);
return $cli;
}
/**
* @param Application $cli
*
* @return void
*/
public static function addCommands(Application $cli)
{
$cli->addCommands([
new Command\ExecuteCommand(),
new Command\GenerateCommand(),
new Command\LatestCommand(),
new Command\MigrateCommand(),
new Command\StatusCommand(),
new Command\VersionCommand(),
new Command\UpToDateCommand(),
]);
if ($cli->getHelperSet()->has('em')) {
$cli->add(new Command\DiffCommand());
}
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Helper;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Migrations\Configuration\ArrayConfiguration;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Migrations\Configuration\JsonConfiguration;
use Doctrine\DBAL\Migrations\Configuration\XmlConfiguration;
use Doctrine\DBAL\Migrations\Configuration\YamlConfiguration;
use Doctrine\DBAL\Migrations\OutputWriter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Helper\Helper;
/**
* Class ConfigurationHelper
* @package Doctrine\DBAL\Migrations\Tools\Console\Helper
* @internal
*/
class ConfigurationHelper extends Helper implements ConfigurationHelperInterface
{
/**
* @var Connection
*/
private $connection;
/**
* @var Configuration
*/
private $configuration;
public function __construct(Connection $connection = null, Configuration $configuration = null)
{
$this->connection = $connection;
$this->configuration = $configuration;
}
public function getMigrationConfig(InputInterface $input, OutputWriter $outputWriter)
{
/**
* If a configuration option is passed to the command line, use that configuration
* instead of any other one.
*/
if ($input->getOption('configuration')) {
$outputWriter->write("Loading configuration from command option: " . $input->getOption('configuration'));
return $this->loadConfig($input->getOption('configuration'), $outputWriter);
}
/**
* If a configuration has already been set using DI or a Setter use it.
*/
if ($this->configuration) {
$outputWriter->write("Loading configuration from the integration code of your framework (setter).");
$this->configuration->setOutputWriter($outputWriter);
return $this->configuration;
}
/**
* If no any other config has been found, look for default config file in the path.
*/
$defaultConfig = [
'migrations.xml',
'migrations.yml',
'migrations.yaml',
'migrations.json',
'migrations.php',
];
foreach ($defaultConfig as $config) {
if ($this->configExists($config)) {
$outputWriter->write("Loading configuration from file: $config");
return $this->loadConfig($config, $outputWriter);
}
}
return new Configuration($this->connection, $outputWriter);
}
private function configExists($config)
{
return file_exists($config);
}
private function loadConfig($config, OutputWriter $outputWriter)
{
$map = [
'xml' => XmlConfiguration::class,
'yaml' => YamlConfiguration::class,
'yml' => YamlConfiguration::class,
'php' => ArrayConfiguration::class,
'json' => JsonConfiguration::class,
];
$info = pathinfo($config);
// check we can support this file type
if (empty($map[$info['extension']])) {
throw new \InvalidArgumentException('Given config file type is not supported');
}
$class = $map[$info['extension']];
$configuration = new $class($this->connection, $outputWriter);
$configuration->load($config);
return $configuration;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'configuration';
}
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types = 1);
namespace Doctrine\DBAL\Migrations\Tools\Console\Helper;
use Doctrine\DBAL\Migrations\OutputWriter;
use Symfony\Component\Console\Input\InputInterface;
interface ConfigurationHelperInterface
{
public function getMigrationConfig(InputInterface $input, OutputWriter $outputWriter);
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Helper;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Symfony\Component\Console\Helper\Helper;
/**
* Class ConfigurationHelper
* @package Doctrine\DBAL\Migrations\Tools\Console\Helper
* @internal
*/
class MigrationDirectoryHelper extends Helper
{
/**
* @var Configuration
*/
private $configuration;
public function __construct(Configuration $configuration = null)
{
$this->configuration = $configuration;
}
public function getMigrationDirectory()
{
$dir = $this->configuration->getMigrationsDirectory();
$dir = $dir ? $dir : getcwd();
$dir = rtrim($dir, '/');
if ( ! file_exists($dir)) {
throw new \InvalidArgumentException(sprintf('Migrations directory "%s" does not exist.', $dir));
}
if ($this->configuration->areMigrationsOrganizedByYear()) {
$dir .= $this->appendDir(date('Y'));
}
if ($this->configuration->areMigrationsOrganizedByYearAndMonth()) {
$dir .= $this->appendDir(date('m'));
}
$this->createDirIfNotExists($dir);
return $dir;
}
private function appendDir($dir)
{
return DIRECTORY_SEPARATOR . $dir;
}
private function createDirIfNotExists($dir)
{
if ( ! file_exists($dir)) {
mkdir($dir, 0755, true);
}
}
/**
* Returns the canonical name of this helper.
*
* @return string The canonical name
*
* @api
*/
public function getName()
{
return 'MigrationDirectory';
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Doctrine\DBAL\Migrations\Tools\Console\Helper;
use Doctrine\DBAL\Migrations\Configuration\AbstractFileConfiguration;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Migrations\Version;
class MigrationStatusInfosHelper
{
/** @var Version[] */
private $executedMigrations;
/** @var Version[] */
private $availableMigrations;
/** @var Version[] */
private $executedUnavailableMigrations;
/** @var Configuration */
private $configuration;
public function __construct(Configuration $configuration)
{
$this->configuration = $configuration;
$this->executedMigrations = $this->configuration->getMigratedVersions();
$this->availableMigrations = $this->configuration->getAvailableVersions();
$this->executedUnavailableMigrations = array_diff($this->executedMigrations, $this->availableMigrations);
}
public function getMigrationsInfos()
{
$numExecutedUnavailableMigrations = count($this->executedUnavailableMigrations);
$numNewMigrations = count(array_diff($this->availableMigrations, $this->executedMigrations));
$infos = [
'Name' => $this->configuration->getName() ? $this->configuration->getName() : 'Doctrine Database Migrations',
'Database Driver' => $this->configuration->getConnection()->getDriver()->getName(),
'Database Name' => $this->configuration->getConnection()->getDatabase(),
'Configuration Source' => $this->configuration instanceof AbstractFileConfiguration ? $this->configuration->getFile() : 'manually configured',
'Version Table Name' => $this->configuration->getMigrationsTableName(),
'Version Column Name' => $this->configuration->getMigrationsColumnName(),
'Migrations Namespace' => $this->configuration->getMigrationsNamespace(),
'Migrations Directory' => $this->configuration->getMigrationsDirectory(),
'Previous Version' => $this->getFormattedVersionAlias('prev'),
'Current Version' => $this->getFormattedVersionAlias('current'),
'Next Version' => $this->getFormattedVersionAlias('next'),
'Latest Version' => $this->getFormattedVersionAlias('latest'),
'Executed Migrations' => count($this->executedMigrations),
'Executed Unavailable Migrations' => $numExecutedUnavailableMigrations,
'Available Migrations' => count($this->availableMigrations),
'New Migrations' => $numNewMigrations,
];
return $infos;
}
private function getFormattedVersionAlias($alias)
{
$version = $this->configuration->resolveVersionAlias($alias);
//No version found
if ($version === null) {
if ($alias === 'next') {
return 'Already at latest version';
}
if ($alias === 'prev') {
return 'Already at first version';
}
}
//Before first version "virtual" version number
if ($version === '0') {
return '<comment>0</comment>';
}
//Show normal version number
return $this->configuration->getDateTime($version) . ' (<comment>' . $version . '</comment>)';
}
/**
* @return Version[]
*/
public function getExecutedUnavailableMigrations()
{
return $this->executedUnavailableMigrations;
}
}

View File

@@ -0,0 +1,504 @@
<?php
namespace Doctrine\DBAL\Migrations;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\DBAL\Migrations\Event\MigrationsVersionEventArgs;
use Doctrine\DBAL\Migrations\Provider\LazySchemaDiffProvider;
use Doctrine\DBAL\Migrations\Provider\SchemaDiffProvider;
use Doctrine\DBAL\Migrations\Provider\SchemaDiffProviderInterface;
use Doctrine\DBAL\Types\Type;
/**
* Class which wraps a migration version and allows execution of the
* individual migration version up or down method.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
class Version
{
const STATE_NONE = 0;
const STATE_PRE = 1;
const STATE_EXEC = 2;
const STATE_POST = 3;
const DIRECTION_UP = 'up';
const DIRECTION_DOWN = 'down';
/**
* The Migrations Configuration instance for this migration
*
* @var Configuration
*/
private $configuration;
/**
* The OutputWriter object instance used for outputting information
*
* @var OutputWriter
*/
private $outputWriter;
/**
* The version in timestamp format (YYYYMMDDHHMMSS)
*
* @var string
*/
private $version;
/**
* The migration instance for this version
*
* @var AbstractMigration
*/
private $migration;
/**
* @var \Doctrine\DBAL\Connection
*/
private $connection;
/**
* @var string
*/
private $class;
/** The array of collected SQL statements for this version */
private $sql = [];
/** The array of collected parameters for SQL statements for this version */
private $params = [];
/** The array of collected types for SQL statements for this version */
private $types = [];
/** The time in seconds that this migration version took to execute */
private $time;
/**
* @var int
*/
private $state = self::STATE_NONE;
/** @var SchemaDiffProviderInterface */
private $schemaProvider;
public function __construct(Configuration $configuration, $version, $class, SchemaDiffProviderInterface $schemaProvider = null)
{
$this->configuration = $configuration;
$this->outputWriter = $configuration->getOutputWriter();
$this->class = $class;
$this->connection = $configuration->getConnection();
$this->migration = new $class($this);
$this->version = $version;
if ($schemaProvider !== null) {
$this->schemaProvider = $schemaProvider;
}
if ($schemaProvider === null) {
$schemaProvider = new SchemaDiffProvider(
$this->connection->getSchemaManager(),
$this->connection->getDatabasePlatform()
);
$this->schemaProvider = LazySchemaDiffProvider::fromDefaultProxyFactoryConfiguration($schemaProvider);
}
}
/**
* Returns the string version in the format YYYYMMDDHHMMSS
*
* @return string $version
*/
public function getVersion()
{
return $this->version;
}
/**
* Returns the Migrations Configuration object instance
*
* @return Configuration $configuration
*/
public function getConfiguration()
{
return $this->configuration;
}
/**
* Check if this version has been migrated or not.
*
* @return boolean
*/
public function isMigrated()
{
return $this->configuration->hasVersionMigrated($this);
}
public function markMigrated()
{
$this->markVersion('up');
}
private function markVersion($direction)
{
$action = $direction === 'up' ? 'insert' : 'delete';
$this->configuration->createMigrationTable();
$this->connection->$action(
$this->configuration->getMigrationsTableName(),
[$this->configuration->getQuotedMigrationsColumnName() => $this->version]
);
}
public function markNotMigrated()
{
$this->markVersion('down');
}
/**
* Add some SQL queries to this versions migration
*
* @param array|string $sql
* @param array $params
* @param array $types
*/
public function addSql($sql, array $params = [], array $types = [])
{
if (is_array($sql)) {
foreach ($sql as $key => $query) {
$this->sql[] = $query;
if ( ! empty($params[$key])) {
$queryTypes = $types[$key] ?? [];
$this->addQueryParams($params[$key], $queryTypes);
}
}
} else {
$this->sql[] = $sql;
if ( ! empty($params)) {
$this->addQueryParams($params, $types);
}
}
}
/**
* @param mixed[] $params Array of prepared statement parameters
* @param string[] $types Array of the types of each statement parameters
*/
private function addQueryParams($params, $types)
{
$index = count($this->sql) - 1;
$this->params[$index] = $params;
$this->types[$index] = $types;
}
/**
* Write a migration SQL file to the given path
*
* @param string $path The path to write the migration SQL file.
* @param string $direction The direction to execute.
*
* @return boolean $written
* @throws MigrationException
*/
public function writeSqlFile($path, $direction = self::DIRECTION_UP)
{
$queries = $this->execute($direction, true);
if ( ! empty($this->params)) {
throw MigrationException::migrationNotConvertibleToSql($this->class);
}
$this->outputWriter->write("\n-- Version " . $this->version . "\n");
$sqlQueries = [$this->version => $queries];
/*
* Since the configuration object changes during the creation we cannot inject things
* properly, so I had to violate LoD here (so please, let's find a way to solve it on v2).
*/
return $this->configuration->getQueryWriter()
->write($path, $direction, $sqlQueries);
}
/**
* @return AbstractMigration
*/
public function getMigration()
{
return $this->migration;
}
/**
* Execute this migration version up or down and and return the SQL.
* We are only allowing the addSql call and the schema modification to take effect in the up and down call.
* This is necessary to ensure that the migration is revertable.
* The schema is passed to the pre and post method only to be able to test the presence of some table, And the
* connection that can get used trough it allow for the test of the presence of records.
*
* @param string $direction The direction to execute the migration.
* @param boolean $dryRun Whether to not actually execute the migration SQL and just do a dry run.
* @param boolean $timeAllQueries Measuring or not the execution time of each SQL query.
*
* @return array $sql
*
* @throws \Exception when migration fails
*/
public function execute($direction, $dryRun = false, $timeAllQueries = false)
{
$this->dispatchEvent(Events::onMigrationsVersionExecuting, $direction, $dryRun);
$this->sql = [];
$transaction = $this->migration->isTransactional();
if ($transaction) {
//only start transaction if in transactional mode
$this->connection->beginTransaction();
}
try {
$migrationStart = microtime(true);
$this->state = self::STATE_PRE;
$fromSchema = $this->schemaProvider->createFromSchema();
$this->migration->{'pre' . ucfirst($direction)}($fromSchema);
if ($direction === self::DIRECTION_UP) {
$this->outputWriter->write("\n" . sprintf(' <info>++</info> migrating <comment>%s</comment>', $this->version) . "\n");
} else {
$this->outputWriter->write("\n" . sprintf(' <info>--</info> reverting <comment>%s</comment>', $this->version) . "\n");
}
$this->state = self::STATE_EXEC;
$toSchema = $this->schemaProvider->createToSchema($fromSchema);
$this->migration->$direction($toSchema);
$this->addSql($this->schemaProvider->getSqlDiffToMigrate($fromSchema, $toSchema));
$this->executeRegisteredSql($dryRun, $timeAllQueries);
$this->state = self::STATE_POST;
$this->migration->{'post' . ucfirst($direction)}($toSchema);
if ( ! $dryRun) {
if ($direction === self::DIRECTION_UP) {
$this->markMigrated();
} else {
$this->markNotMigrated();
}
}
$migrationEnd = microtime(true);
$this->time = round($migrationEnd - $migrationStart, 2);
if ($direction === self::DIRECTION_UP) {
$this->outputWriter->write(sprintf("\n <info>++</info> migrated (%ss)", $this->time));
} else {
$this->outputWriter->write(sprintf("\n <info>--</info> reverted (%ss)", $this->time));
}
if ($transaction) {
//commit only if running in transactional mode
$this->connection->commit();
}
$this->state = self::STATE_NONE;
$this->dispatchEvent(Events::onMigrationsVersionExecuted, $direction, $dryRun);
return $this->sql;
} catch (SkipMigrationException $e) {
if ($transaction) {
//only rollback transaction if in transactional mode
$this->connection->rollBack();
}
if ($dryRun === false) {
// now mark it as migrated
if ($direction === self::DIRECTION_UP) {
$this->markMigrated();
} else {
$this->markNotMigrated();
}
}
$this->outputWriter->write(sprintf("\n <info>SS</info> skipped (Reason: %s)", $e->getMessage()));
$this->state = self::STATE_NONE;
$this->dispatchEvent(Events::onMigrationsVersionSkipped, $direction, $dryRun);
return [];
} catch (\Exception $e) {
$this->outputWriter->write(sprintf(
'<error>Migration %s failed during %s. Error %s</error>',
$this->version,
$this->getExecutionState(),
$e->getMessage()
));
if ($transaction) {
//only rollback transaction if in transactional mode
$this->connection->rollBack();
}
$this->state = self::STATE_NONE;
throw $e;
}
}
public function getExecutionState()
{
switch ($this->state) {
case self::STATE_PRE:
return 'Pre-Checks';
case self::STATE_POST:
return 'Post-Checks';
case self::STATE_EXEC:
return 'Execution';
default:
return 'No State';
}
}
private function outputQueryTime($queryStart, $timeAllQueries = false)
{
if ($timeAllQueries !== false) {
$queryEnd = microtime(true);
$queryTime = round($queryEnd - $queryStart, 4);
$this->outputWriter->write(sprintf(" <info>%ss</info>", $queryTime));
}
}
/**
* Returns the time this migration version took to execute
*
* @return integer $time The time this migration version took to execute
*/
public function getTime()
{
return $this->time;
}
public function __toString()
{
return $this->version;
}
private function executeRegisteredSql($dryRun = false, $timeAllQueries = false)
{
if ( ! $dryRun) {
if ( ! empty($this->sql)) {
foreach ($this->sql as $key => $query) {
$queryStart = microtime(true);
$this->outputSqlQuery($key, $query);
if ( ! isset($this->params[$key])) {
$this->connection->executeQuery($query);
} else {
$this->connection->executeQuery($query, $this->params[$key], $this->types[$key]);
}
$this->outputQueryTime($queryStart, $timeAllQueries);
}
} else {
$this->outputWriter->write(sprintf(
'<error>Migration %s was executed but did not result in any SQL statements.</error>',
$this->version
));
}
} else {
foreach ($this->sql as $idx => $query) {
$this->outputSqlQuery($idx, $query);
}
}
}
/**
* Outputs a SQL query via the `OutputWriter`.
*
* @param int $idx The SQL query index. Used to look up params.
* @param string $query the query to output
* @return void
*/
private function outputSqlQuery($idx, $query)
{
$params = $this->formatParamsForOutput(
$this->params[$idx] ?? [],
$this->types[$idx] ?? []
);
$this->outputWriter->write(rtrim(sprintf(
' <comment>-></comment> %s %s',
$query,
$params
)));
}
/**
* Formats a set of sql parameters for output with dry run.
*
* @param array $params The query parameters
* @param array $types The types of the query params. Default type is a string
* @return string|null a string of the parameters present.
*/
private function formatParamsForOutput(array $params, array $types)
{
if (empty($params)) {
return '';
}
$out = [];
foreach ($params as $key => $value) {
$type = $types[$key] ?? 'string';
$outval = '[' . $this->formatParameter($value, $type) . ']';
$out[] = is_string($key) ? sprintf(':%s => %s', $key, $outval) : $outval;
}
return sprintf('with parameters (%s)', implode(', ', $out));
}
private function dispatchEvent($eventName, $direction, $dryRun)
{
$this->configuration->dispatchEvent($eventName, new MigrationsVersionEventArgs(
$this,
$this->configuration,
$direction,
$dryRun
));
}
private function formatParameter($value, string $type) : ?string
{
if (Type::hasType($type)) {
return Type::getType($type)->convertToDatabaseValue(
$value,
$this->connection->getDatabasePlatform()
);
}
return $this->parameterToString($value);
}
private function parameterToString($value) : string
{
if (is_array($value)) {
return implode(', ', array_map([$this, 'parameterToString'], $value));
}
if (is_int($value) || is_string($value)) {
return (string) $value;
}
if (is_bool($value)) {
return $value === true ? 'true' : 'false';
}
return '?';
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Doctrine\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration as BaseAbstractMigration;
abstract class AbstractMigration extends BaseAbstractMigration
{
}

View File

@@ -0,0 +1,5 @@
parameters:
ignoreErrors:
# Ignore proxy manager magic
- '#ProxyManager\\Proxy\\VirtualProxyInterface#'