/**
  * Migrate a scope in either up or down direction.
  * steps -> the number of migration steps to perform (optional)
  * version -> up until what version you want to migrate (ignored if steps are provided)
  * forceAll -> force to run all migrations (optional) (skips version check)
  *
  * @param array $options
  * @throws InvalidArgumentException
  * @throws Exception
  * @return void
  */
 public function migrate($options = array())
 {
     if (!isset($options['direction'])) {
         throw new InvalidArgumentException(__d('migration', 'Missing "direction" option. Provide it with either "up" or "down".'));
     }
     if (isset($options['direction']) && !in_array($options['direction'], array('up', 'down'))) {
         throw new InvalidArgumentException(__d('migration', 'Invalid migration direction "%s". Use either "up" or "down".', $options['direction']));
     }
     if (isset($options['scope']) && $options['scope'] !== 'app') {
         $this->_checkScope($options['scope']);
     }
     /**
      * @var string  $direction
      * @var string  $scope
      * @var integer $steps
      * @var string  $version
      * @var boolean $forceAll
      */
     extract(array_merge($this->_defaults, $options));
     $availableMigrations = $this->_getAvailableMigrations($scope, $direction);
     $offset = 0;
     if ($forceAll === true) {
         $steps = false;
     } else {
         $currentVersion = $this->SchemaMigration->getCurrentVersion($scope);
         if ($currentVersion !== false) {
             $offset = array_search($currentVersion, array_keys($availableMigrations), true) + 1;
         }
     }
     if ($direction === 'down') {
         $offset--;
     }
     if ($version !== false && $version !== 'latest') {
         if (!array_key_exists($version, $availableMigrations)) {
             throw new InvalidArgumentException(__d('migration', 'Invalid migration version "%s".', $version));
         }
         $steps = array_search($version, array_keys($availableMigrations));
         if ($direction === 'up') {
             $steps++;
         }
     }
     if ($steps >= 1) {
         $migrationsToRun = array_slice($availableMigrations, $offset, $steps, true);
     } else {
         $migrationsToRun = array_slice($availableMigrations, $offset, null, true);
     }
     /** @var DboSource $db */
     $db = ConnectionManager::getDataSource($this->connection);
     $db->begin();
     try {
         foreach ($migrationsToRun as $v => $m) {
             echo 'Migrating to: ' . $v . "\n\n";
             $migration = $this->_getMigrationInstance($m);
             $migration->{$direction}();
             $this->SchemaMigration->{$direction}($scope, $v, $m['className']);
         }
     } catch (Exception $e) {
         $db->rollback();
         throw $e;
     }
     $db->commit();
 }