ludofleury
3/22/2011 - 11:25 AM

Custom migrations using schema.yml revision.

Custom migrations using schema.yml revision.

<?php

class sdzDoctrineMigrateTask extends sfDoctrineBaseTask
{
  /**
   * @see sfTask
   */
  protected function configure()
  {
    $this->addArguments(array(
      new sfCommandArgument('version', sfCommandArgument::OPTIONAL, 'The version to migrate to'),
    ));

    $this->addOptions(array(
      new sfCommandOption('application', null, sfCommandOption::PARAMETER_OPTIONAL, 'The application name', true),
      new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'The environment', 'dev'),
      new sfCommandOption('up', null, sfCommandOption::PARAMETER_NONE, 'Migrate up one version'),
      new sfCommandOption('down', null, sfCommandOption::PARAMETER_NONE, 'Migrate down one version'),
      new sfCommandOption('dry-run', null, sfCommandOption::PARAMETER_NONE, 'Do not persist migrations'),
    ));

    $this->namespace = 'sdz';
    $this->name = 'migrate';
    $this->briefDescription = 'Migrates database to current/specified version';

    $this->detailedDescription = <<<EOF
The [doctrine:migrate|INFO] task migrates the database:

  [./symfony doctrine:migrate|INFO]

Provide a version argument to migrate to a specific version:

  [./symfony doctrine:migrate 10|INFO]

To migration up or down one migration, use the [--up|COMMENT] or [--down|COMMENT] options:

  [./symfony doctrine:migrate --down|INFO]

If your database supports rolling back DDL statements, you can run migrations
in dry-run mode using the [--dry-run|COMMENT] option:

  [./symfony doctrine:migrate --dry-run|INFO]
EOF;
  }

  /**
   * @see sfTask
   */
  protected function execute($arguments = array(), $options = array())
  {
    $config = $this->getCliConfig();
    $reposiroty = sfFinder::type('file')->name('*.yml')->sort_by_name()->follow_link()->in($config['migrations_path'].'/schema');
    if(empty($reposiroty))
    {
        $this->logSection('sdz','Empty migration repository : initializing migrations');
        $task = new sdzDoctrineGenerateMigrationsDiffTask($this->dispatcher, $this->formatter);
        $task->setCommandApplication($this->commandApplication);
        $task->setConfiguration($this->configuration);
        $ret = $task->run();
    }
    else
    {
        $databaseManager = new sfDatabaseManager($this->configuration);
        $migration = new Doctrine_Migration($config['migrations_path']);
        $from = $version = $migration->getCurrentVersion();
        
        foreach($reposiroty as $stableVersion)
        {
            $stableVersions[] = str_replace(array($config['migrations_path'].'/schema/version','.yml'),'',$stableVersion);
        }
        $currentVersionIndex = array_search($from,$stableVersions);

        # is_numeric still not safe for handy/debug reasons
        if (is_numeric($arguments['version']))
        {
          $version = $arguments['version'];
        }
        else if ($options['up'])
        {
          if($currentVersionIndex < count($stableVersions))
          {
            $version = $stableVersions[$currentVersionIndex+1];
          }
        }
        else if ($options['down'])
        {
          if(0 < $currentVersionIndex)
          {
            $version = $stableVersions[$currentVersionIndex-1];
          }
        }
        else
        {
          $version = $migration->getLatestVersion();
        }

        if ($from == $version)
        {
          $this->logSection('doctrine', sprintf('Already at migration version %s', $version));
          return;
        }

        $this->logSection('doctrine', sprintf('Migrating from version %s to %s%s', $from, $version, $options['dry-run'] ? ' (dry run)' : ''));
        try
        {
          $migration->migrate($version, $options['dry-run']);
        }
        catch (Exception $e)
        {
        }

        // render errors
        if ($migration->hasErrors())
        {
          if ($this->commandApplication && $this->commandApplication->withTrace())
          {
            $this->logSection('doctrine', 'The following errors occurred:');
            foreach ($migration->getErrors() as $error)
            {
              $this->commandApplication->renderException($error);
            }
          }
          else
          {
            $this->logBlock(array_merge(
              array('The following errors occurred:', ''),
              array_map(create_function('$e', 'return \' - \'.$e->getMessage();'), $migration->getErrors())
            ), 'ERROR_LARGE');
          }

          return 1;
        }
        $this->logSection('doctrine', 'Migration complete');
     }
  }
}
 ?>
<?php

class sdzDoctrineGenerateMigrationsDiffTask extends sfDoctrineBaseTask
{
  /**
   * @see sfTask
   */
  protected function configure()
  {
    $this->addOptions(array(
      new sfCommandOption('application', null, sfCommandOption::PARAMETER_OPTIONAL, 'The application name', true),
      new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'The environment', 'dev'),
    ));

    $this->namespace = 'sdz';
    $this->name = 'generate-migrations-diff';
    $this->briefDescription = 'Generate migration classes by producing a diff between your old and new schema.';

    $this->detailedDescription = <<<EOF
The [doctrine:generate-migrations-diff|INFO] task generates migration classes by
producing a diff between your old and new schema.

  [./symfony doctrine:generate-migrations-diff|INFO]
EOF;
  }

  /**
   * @see sfTask
   */
  protected function execute($arguments = array(), $options = array())
  {
    $databaseManager = new sfDatabaseManager($this->configuration);
    $config = $this->getCliConfig();

    $this->logSection('doctrine', 'generating migration diff');

    if (!is_dir($config['migrations_path']))
    {
      $this->getFilesystem()->mkdirs($config['migrations_path']);
    }

    spl_autoload_register(array('Doctrine_Core', 'modelsAutoload'));

    $schemaFileHistory = sfFinder::type('file')->name('*.yml')->sort_by_name()->follow_link()->in($config['migrations_path'].'/schema');

    if(empty($schemaFileHistory))
    {
       $this->logSection('sdz','Empty migration schema repository');
       $initFile = $this->prepareSchemaFile($config['yaml_schema_path']);
       $this->commitSchemaFile($initFile, 0);
       $this->logSection('sdz','Initialized migration schema repository');
    }
    else
    {
       $lastestVersion = end($schemaFileHistory);
       $actualVersion  = $this->prepareSchemaFile($config['yaml_schema_path']);

       try
       {
            $from = $lastestVersion;
            $to = $actualVersion;

            $migration = new Doctrine_Migration($config['migrations_path']);
            $diff = new Doctrine_Migration_Diff($from, $to, $migration);
            $changes = $diff->generateMigrationClasses();

            $numChanges = count($changes, true) - count($changes);

            if ( ! $numChanges)
            {
                throw new Doctrine_Task_Exception('Could not generate migration classes from difference (seems to be up-to-date)');
            }
            else
            {
                $this->logSection('sdz', 'Generated migration classes successfully from difference '.'('.$numChanges.')');
            }

           $versions = sfFinder::type('file')->name('*.php')->sort_by_name()->follow_link()->in($config['migrations_path']);

           $this->commitSchemaFile($actualVersion,count($versions));
           $this->logSection('sdz', 'Processed migration generation successfully');
       }
       catch(Exception $e)
       {
           throw $e;
       }
    }
  }

  /**
   *
   * @param string $file
   * @param int    $id
   */
  protected function commitSchemaFile($file, $id)
  {
    $config = $this->getCliConfig();
    $this->logSection('file+', $file);

    $commitPath = $config['migrations_path'].'/schema/';
    $commitFileName = ($id > 0 ? 'version'.$id.'.yml' : 'version0.yml');

    if(file_exists($commitPath.$commitFileName))
    {
        throw new Exception ('Unbable to commit : invalid id');
    }
    
    $this->getFilesystem()->copy($file,$commitPath.$commitFileName);

    if(!file_exists($commitPath.$commitFileName))
    {
        throw new Exception ('Unable to commit schema');
    }
    $this->logSection('sdz', 'Schema '.$commitFileName.' versionned');
  }
}

?>