files = $files; $this->resolver = $resolver; $this->repository = $repository; } /** * Run the outstanding migrations at a given path. * * @param string $path * @param bool $pretend * @return void */ public function run($path, $pretend = false) { $this->notes = array(); $files = $this->getMigrationFiles($path); // Once we grab all of the migration files for the path, we will compare them // against the migrations that have already been run for this package then // run each of the outstanding migrations against a database connection. $ran = $this->repository->getRan(); $migrations = array_diff($files, $ran); $this->requireFiles($path, $migrations); $this->runMigrationList($migrations, $pretend); } /** * Run an array of migrations. * * @param array $migrations * @param bool $pretend * @return void */ public function runMigrationList($migrations, $pretend = false) { // First we will just make sure that there are any migrations to run. If there // aren't, we will just make a note of it to the developer so they're aware // that all of the migrations have been run against this database system. if (count($migrations) == 0) { $this->note('Nothing to migrate.'); return; } $batch = $this->repository->getNextBatchNumber(); // Once we have the array of migrations, we will spin through them and run the // migrations "up" so the changes are made to the databases. We'll then log // that the migration was run so we don't repeat it next time we execute. foreach ($migrations as $file) { $this->runUp($file, $batch, $pretend); } } /** * Run "up" a migration instance. * * @param string $file * @param int $batch * @param bool $pretend * @return void */ protected function runUp($file, $batch, $pretend) { // First we will resolve a "real" instance of the migration class from this // migration file name. Once we have the instances we can run the actual // command such as "up" or "down", or we can just simulate the action. $migration = $this->resolve($file); if ($pretend) { return $this->pretendToRun($migration, 'up'); } $migration->up(); // Once we have run a migrations class, we will log that it was run in this // repository so that we don't try to run it next time we do a migration // in the application. A migration repository keeps the migrate order. $this->repository->log($file, $batch); $this->note("Migrated: $file"); } /** * Rollback the last migration operation. * * @param bool $pretend * @return int */ public function rollback($pretend = false) { $this->notes = array(); // We want to pull in the last batch of migrations that ran on the previous // migration operation. We'll then reverse those migrations and run each // of them "down" to reverse the last migration "operation" which ran. $migrations = $this->repository->getLast(); if (count($migrations) == 0) { $this->note('Nothing to rollback.'); return count($migrations); } // We need to reverse these migrations so that they are "downed" in reverse // to what they run on "up". It lets us backtrack through the migrations // and properly reverse the entire database schema operation that ran. foreach ($migrations as $migration) { $this->runDown((object) $migration, $pretend); } return count($migrations); } /** * Rolls all of the currently applied migrations back. * * @param bool $pretend * @return int */ public function reset($pretend = false) { $this->notes = []; $migrations = array_reverse($this->repository->getRan()); if (count($migrations) == 0) { $this->note('Nothing to rollback.'); return count($migrations); } foreach ($migrations as $migration) { $this->runDown((object) ['migration' => $migration], $pretend); } return count($migrations); } /** * Run "down" a migration instance. * * @param object $migration * @param bool $pretend * @return void */ protected function runDown($migration, $pretend) { $file = $migration->migration; // First we will get the file name of the migration so we can resolve out an // instance of the migration. Once we get an instance we can either run a // pretend execution of the migration or we can run the real migration. $instance = $this->resolve($file); if ($pretend) { return $this->pretendToRun($instance, 'down'); } $instance->down(); // Once we have successfully run the migration "down" we will remove it from // the migration repository so it will be considered to have not been run // by the application then will be able to fire by any later operation. $this->repository->delete($migration); $this->note("Rolled back: $file"); } /** * Get all of the migration files in a given path. * * @param string $path * @return array */ public function getMigrationFiles($path) { $files = $this->files->glob($path.'/*_*.php'); // Once we have the array of files in the directory we will just remove the // extension and take the basename of the file which is all we need when // finding the migrations that haven't been run against the databases. if ($files === false) return array(); $files = array_map(function($file) { return str_replace('.php', '', basename($file)); }, $files); // Once we have all of the formatted file names we will sort them and since // they all start with a timestamp this should give us the migrations in // the order they were actually created by the application developers. sort($files); return $files; } /** * Require in all the migration files in a given path. * * @param string $path * @param array $files * @return void */ public function requireFiles($path, array $files) { foreach ($files as $file) $this->files->requireOnce($path.'/'.$file.'.php'); } /** * Pretend to run the migrations. * * @param object $migration * @param string $method * @return void */ protected function pretendToRun($migration, $method) { foreach ($this->getQueries($migration, $method) as $query) { $name = get_class($migration); $this->note("{$name}: {$query['query']}"); } } /** * Get all of the queries that would be run for a migration. * * @param object $migration * @param string $method * @return array */ protected function getQueries($migration, $method) { $connection = $migration->getConnection(); // Now that we have the connections we can resolve it and pretend to run the // queries against the database returning the array of raw SQL statements // that would get fired against the database system for this migration. $db = $this->resolveConnection($connection); return $db->pretend(function() use ($migration, $method) { $migration->$method(); }); } /** * Resolve a migration instance from a file. * * @param string $file * @return object */ public function resolve($file) { $file = implode('_', array_slice(explode('_', $file), 4)); $class = studly_case($file); return new $class; } /** * Raise a note event for the migrator. * * @param string $message * @return void */ protected function note($message) { $this->notes[] = $message; } /** * Get the notes for the last operation. * * @return array */ public function getNotes() { return $this->notes; } /** * Resolve the database connection instance. * * @param string $connection * @return \Illuminate\Database\Connection */ public function resolveConnection($connection) { return $this->resolver->connection($connection); } /** * Set the default connection name. * * @param string $name * @return void */ public function setConnection($name) { if ( ! is_null($name)) { $this->resolver->setDefaultConnection($name); } $this->repository->setSource($name); $this->connection = $name; } /** * Get the migration repository instance. * * @return \Illuminate\Database\Migrations\MigrationRepositoryInterface */ public function getRepository() { return $this->repository; } /** * Determine if the migration repository exists. * * @return bool */ public function repositoryExists() { return $this->repository->repositoryExists(); } /** * Get the file system instance. * * @return \Illuminate\Filesystem\Filesystem */ public function getFilesystem() { return $this->files; } }