├── Knp ├── Command │ └── MigrationCommand.php ├── Migration │ ├── AbstractMigration.php │ └── Manager.php ├── Provider │ └── MigrationServiceProvider.php └── Resources │ └── bin │ └── console ├── README.md └── composer.json /Knp/Command/MigrationCommand.php: -------------------------------------------------------------------------------- 1 | setName('knp:migration:migrate'); 17 | } 18 | 19 | public function execute(InputInterface $input, OutputInterface $output) 20 | { 21 | $app = $this->getSilexApplication(); 22 | $manager = $app['migration']; 23 | 24 | if (!$manager->hasVersionInfo()) { 25 | $manager->createVersionInfo(); 26 | } 27 | 28 | $res = $manager->migrate(); 29 | 30 | switch($res) { 31 | case true: 32 | $output->writeln(sprintf('Succesfully executed %d migration(s)!', $manager->getMigrationExecuted())); 33 | foreach ($manager->getMigrationInfos() as $info) { 34 | $output->writeln(sprintf(' - %s', $info)); 35 | } 36 | break; 37 | case null: 38 | $output->writeln('No migrations to execute, you are up to date!'); 39 | break; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Knp/Migration/AbstractMigration.php: -------------------------------------------------------------------------------- 1 | getFileName()), $matches)) { 15 | return (int) ltrim($matches[1], 0); 16 | } 17 | 18 | throw new RuntimeError(sprintf('Could not get version from "%"', basename($rc->getFileName()))); 19 | } 20 | 21 | public function getMigrationInfo() 22 | { 23 | return null; 24 | } 25 | 26 | public function schemaUp(Schema $schema) {} 27 | 28 | public function schemaDown(Schema $schema) {} 29 | 30 | public function appUp(Application $app) {} 31 | 32 | public function appDown(Application $app) {} 33 | } -------------------------------------------------------------------------------- /Knp/Migration/Manager.php: -------------------------------------------------------------------------------- 1 | schema = $connection->getSchemaManager()->createSchema(); 29 | $this->toSchema = clone($this->schema); 30 | $this->connection = $connection; 31 | $this->finder = $finder; 32 | $this->application = $application; 33 | 34 | if(isset($application['migration.migrations_table_name'])) { 35 | $this->migrationsTableName = $application['migration.migrations_table_name']; 36 | } 37 | } 38 | 39 | private function buildSchema(Schema $schema) 40 | { 41 | $queries = $this->schema->getMigrateToSql($schema, $this->connection->getDatabasePlatform()); 42 | 43 | foreach ($queries as $query) { 44 | $this->connection->exec($query); 45 | } 46 | } 47 | 48 | private function findMigrations($from) 49 | { 50 | $finder = clone($this->finder); 51 | $migrations = array(); 52 | 53 | $finder 54 | ->files() 55 | ->name('*Migration.php') 56 | ->sortByName() 57 | ; 58 | 59 | foreach ($finder as $migration) { 60 | if (preg_match('/^(\d+)_(.*Migration).php$/', basename($migration), $matches)) { 61 | 62 | list(, $version, $class) = $matches; 63 | 64 | if ((int) ltrim($version, 0) > $from) { 65 | require_once $migration; 66 | 67 | $fqcn = '\\Migration\\'.$class; 68 | 69 | if (!class_exists($fqcn)) { 70 | throw new \RuntimeException(sprintf('Could not find class "%s" in "%s"', $fqcn, $migration)); 71 | } 72 | 73 | $migrations[] = new $fqcn(); 74 | } 75 | } 76 | } 77 | 78 | return $migrations; 79 | } 80 | 81 | public function getMigrationInfos() 82 | { 83 | return $this->migrationInfos; 84 | } 85 | 86 | public function getMigrationExecuted() 87 | { 88 | return $this->migrationExecuted; 89 | } 90 | 91 | public function getCurrentVersion() 92 | { 93 | if (is_null($this->currentVersion)) { 94 | $this->currentVersion = $this->conn->fetchColumn('SELECT ' . $this->migrationsTableName . ' FROM schema_version'); 95 | } 96 | 97 | return $this->currentVersion; 98 | } 99 | 100 | public function setCurrentVersion($version) 101 | { 102 | $this->currentVersion = $version; 103 | $this->connection->executeUpdate('UPDATE ' . $this->migrationsTableName . ' SET schema_version = ?', array($version)); 104 | } 105 | 106 | public function hasVersionInfo() 107 | { 108 | return $this->schema->hasTable($this->migrationsTableName); 109 | } 110 | 111 | public function createVersionInfo() 112 | { 113 | $schema = clone($this->schema); 114 | 115 | $schemaVersion = $schema->createTable($this->migrationsTableName); 116 | $schemaVersion->addColumn($this->migrationsTableName, 'integer', array('unsigned' => true, 'default' => 0)); 117 | 118 | $this->buildSchema($schema); 119 | 120 | $this->connection->insert($this->migrationsTableName, array('schema_version' => 0)); 121 | } 122 | 123 | public function migrate() 124 | { 125 | $from = $this->connection->fetchColumn('SELECT ' . $this->migrationsTableName . ' FROM schema_version'); 126 | $queries = array(); 127 | 128 | $migrations = $this->findMigrations($from); 129 | 130 | if (count($migrations) == 0) { 131 | return null; 132 | } 133 | 134 | foreach ($migrations as $migration) { 135 | $migration->schemaUp($this->toSchema); 136 | } 137 | 138 | $this->buildSchema($this->toSchema); 139 | 140 | foreach ($migrations as $migration) { 141 | $migration->appUp($this->application); 142 | } 143 | 144 | $migrationInfos = array(); 145 | 146 | foreach ($migrations as $migration) { 147 | if (null !== $migration->getMigrationInfo()) { 148 | $migrationInfos[$migration->getVersion()] = $migration->getMigrationInfo(); 149 | } 150 | 151 | $this->migrationExecuted++; 152 | } 153 | 154 | $this->migrationInfos = $migrationInfos; 155 | 156 | $this->setCurrentVersion($migration->getVersion()); 157 | 158 | return true; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Knp/Provider/MigrationServiceProvider.php: -------------------------------------------------------------------------------- 1 | share(function() use ($app) { 22 | return new MigrationManager($app['db'], $app, Finder::create()->in($app['migration.path'])); 23 | }); 24 | 25 | $app['dispatcher']->addListener(ConsoleEvents::INIT, function(ConsoleEvent $event) { 26 | $application = $event->getApplication(); 27 | $application->add(new MigrationCommand()); 28 | }); 29 | 30 | if (isset($app['migration.register_before_handler']) && $app['migration.register_before_handler']) { 31 | $this->registerBeforeHandler($app); 32 | } 33 | } 34 | 35 | private function registerBeforeHandler($app) 36 | { 37 | $app->before(function() use ($app) { 38 | $manager = $app['migration']; 39 | 40 | if (!$manager->hasVersionInfo()) { 41 | $manager->createVersionInfo(); 42 | } 43 | 44 | if (true === $manager->migrate() && isset($app['twig'])) { 45 | $app['twig']->addGlobal('migration_infos', $manager->getMigrationInfos()); 46 | } 47 | }); 48 | } 49 | 50 | public function boot(Application $app) 51 | { 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Knp/Resources/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | Unfortunately we decided to not maintain this project anymore ([see why](https://knplabs.com/en/blog/news-for-our-foss-projects-maintenance)). 3 | If you want to mark another package as a replacement for this one please send an email to [hello@knplabs.com](mailto:hello@knplabs.com). 4 | 5 | # Migrations 6 | 7 | This is a simple homebrew schema migration system for silex and doctrine. 8 | 9 | ## Install 10 | 11 | As usual, just include `knplabs/migration-service-provider` in your `composer.json` (don't tell me you don't have one, it's 2012 already), and register the service. You will have to pass the `migration.path` option, which should contain the path to your migrations files: 12 | 13 | ```php 14 | $app->register(new \Knp\Provider\MigrationServiceProvider(), array( 15 | 'migration.path' => __DIR__.'/../src/Resources/migration' 16 | )); 17 | ``` 18 | 19 | ## Enough small talk, I want to write migrations! 20 | 21 | And I am too lazy to write a comprehensive documentation right now, so you will have to rely on two external resources: 22 | 23 | 1. [The marketplace's migrations](https://github.com/KnpLabs/marketplace/tree/master/src/Resources/migrations) 24 | 2. [The official documentation for Doctrine's DBAL Schema Manager](http://readthedocs.org/docs/doctrine-dbal/en/latest/reference/schema-manager.html) 25 | 26 | ## Running migrations 27 | 28 | There are two ways of running migrations 29 | 30 | ### Using the `before` handler 31 | 32 | If you pass a `migration.register_before_handler` (set to `true`) when registering the service, then a `before` handler will be registered for migration to be run. It means that the migration manager will be run for each hit to your application. 33 | 34 | You might want to enable this behavior for development mode, but please don't do that in production! 35 | 36 | ### Using the `knp:migration:migrate` command 37 | 38 | If you installed the console service provider right, you can use the `knp:migration:migrate` command. 39 | 40 | ## Writing migrations 41 | 42 | A migration consist of a single file, holding a migration class. By design, the migration file must be named something like `_Migration.php` and located in `src/Resources/migrations`, and the class `Migration`. For example, if your migration adds a `bar` field to the `foo` table, and is the 5th migration of your schema, you should name your file `05_FooBarMigration.php`, and the class would be named `FooBarMigration`. 43 | 44 | In addition to these naming conventions, your migration class must extends `Knp\Migration\AbstractMigration`, which provides a few helping method such as `getVersion` and default implementations for migration methods. 45 | 46 | The migration methods consist of 4 methods: 47 | 48 | * `schemaUp` 49 | * `schemaDown` 50 | * `appUp` 51 | * `appDown` 52 | 53 | The names are pretty self-explanatory. Each `schema*` method is fed a `Doctrine\DBAL\Schema\Schema` instance of which you're expected to work to add, remove or modify fields and/or tables. The `app*` method are given a `Silex\Application` instance, actually your very application. You can see an example of useful `appUp` migration in the marketplace's [CommentMarkdownCacheMigration](https://github.com/knplabs/marketplace/blob/master/src/Resources/migrations/04_CommentMarkdownCacheMigration.php). 54 | 55 | ## Migration infos 56 | 57 | There's one last method you should know about: `getMigrationInfo`. This method should return a self-explanatory description of the migration (it is optional though, and you can skip its implementation). When a migration implementing the `getMigrationInfo` method is run, and if you use twig, a global variable is set in your twig environment containing an array of all run migration informations. 58 | 59 | You can then use it with something like that: 60 | 61 | ```html 62 | {% if migration_infos is defined %} 63 |
64 |

Some migrations have been run:

65 |
    66 | {% for info in migration_infos %} 67 |
  • {{ info }}
  • 68 | {% endfor %} 69 |
70 | {% endif %} 71 | ``` 72 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "knplabs/migration-service-provider", 3 | "type": "library", 4 | "description": "Doctrine migration service provider for Silex", 5 | "keywords": [ "doctrine2", "silex", "migration" ], 6 | "homepage": "http://knplabs.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "KnpLabs", 11 | "homepage": "http://knplabs.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.3.2", 16 | "doctrine/dbal": "~2.1", 17 | "symfony/finder": "2.1.*", 18 | "knplabs/console-service-provider": "1.1.*" 19 | }, 20 | "autoload": { 21 | "psr-0": { 22 | "Knp\\": "" 23 | } 24 | } 25 | } 26 | --------------------------------------------------------------------------------