* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Flarum\Migrations; use Illuminate\Support\Str; use Illuminate\Filesystem\Filesystem; use Illuminate\Database\ConnectionResolverInterface as Resolver; class Migrator { /** * The migration repository implementation. * * @var \Flarum\Migrations\MigrationRepositoryInterface */ protected $repository; /** * The filesystem instance. * * @var \Illuminate\Filesystem\Filesystem */ protected $files; /** * The connection resolver instance. * * @var \Illuminate\Database\ConnectionResolverInterface */ protected $resolver; /** * The name of the default connection. * * @var string */ protected $connection; /** * The notes for the current operation. * * @var array */ protected $notes = []; /** * Create a new migrator instance. * * @param \Flarum\Migrations\MigrationRepositoryInterface $repository * @param \Illuminate\Database\ConnectionResolverInterface $resolver * @param \Illuminate\Filesystem\Filesystem $files * @return void */ public function __construct( MigrationRepositoryInterface $repository, Resolver $resolver, Filesystem $files ) { $this->files = $files; $this->resolver = $resolver; $this->repository = $repository; } /** * Run the outstanding migrations at a given path. * * @param string $path * @param string $extension * @return void */ public function run($path, $extension = null) { $this->notes = []; $files = $this->getMigrationFiles($path); $ran = $this->repository->getRan($extension); $migrations = array_diff($files, $ran); $this->requireFiles($path, $migrations); $this->runMigrationList($migrations, $extension); } /** * Run an array of migrations. * * @param array $migrations * @param bool $pretend * @return void */ public function runMigrationList($migrations, $extension) { // 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; } // 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, $extension); } } /** * Run "up" a migration instance. * * @param string $file * @param string $extension * @return void */ protected function runUp($file, $extension) { // 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, $extension); $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, $extension); $this->note("Migrated: $file"); } /** * Rolls all of the currently applied migrations back. * * @param bool $pretend * @return int */ public function reset($path, $extension = null) { $this->notes = []; $migrations = array_reverse($this->repository->getRan($extension)); $this->requireFiles($path, $migrations); $count = count($migrations); if ($count === 0) { $this->note('Nothing to rollback.'); } else { foreach ($migrations as $migration) { $this->runDown($migration, $extension); } } return $count; } /** * Run "down" a migration instance. * * @param string $file * @param string $extension * @return void */ protected function runDown($file, $extension = null) { // 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, $extension); $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($file, $extension); $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 []; } $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'); } } /** * Resolve a migration instance from a file. * * @param string $file * @return object */ public function resolve($file, $extension = null) { $file = implode('_', array_slice(explode('_', $file), 4)); $class = 'Flarum\\Migrations\\' . ($extension ? Str::studly($extension) : 'Core') . '\\'; $class .= Str::studly($file); return app()->make($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; } }