247 lines
6.3 KiB
PHP
247 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace Engelsystem\Database\Migration;
|
|
|
|
use Engelsystem\Application;
|
|
use Exception;
|
|
use Illuminate\Database\Query\Builder;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Database\Schema\Builder as SchemaBuilder;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Str;
|
|
use Throwable;
|
|
|
|
class Migrate
|
|
{
|
|
/** @var callable */
|
|
protected $output;
|
|
|
|
protected string $table = 'migrations';
|
|
|
|
/**
|
|
* Migrate constructor
|
|
*/
|
|
public function __construct(protected SchemaBuilder $schema, protected Application $app)
|
|
{
|
|
$this->output = function (): void {
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Run a migration
|
|
*/
|
|
public function run(
|
|
string $path,
|
|
Direction $direction = Direction::UP,
|
|
bool $oneStep = false,
|
|
bool $forceMigration = false
|
|
): void {
|
|
$this->initMigration();
|
|
|
|
$this->lockTable($forceMigration);
|
|
$migrations = $this->mergeMigrations(
|
|
$this->getMigrations($path),
|
|
$this->getMigrated()
|
|
);
|
|
|
|
if ($direction === Direction::DOWN) {
|
|
$migrations = $migrations->reverse();
|
|
}
|
|
|
|
try {
|
|
foreach ($migrations as $migration) {
|
|
/** @var array $migration */
|
|
$name = $migration['migration'];
|
|
|
|
if (
|
|
($direction === Direction::UP && isset($migration['id']))
|
|
|| ($direction === Direction::DOWN && !isset($migration['id']))
|
|
) {
|
|
($this->output)('Skipping ' . $name);
|
|
continue;
|
|
}
|
|
|
|
($this->output)('Migrating ' . $name . ' (' . $direction->value . ')');
|
|
|
|
if (isset($migration['path'])) {
|
|
$this->migrate($migration['path'], $name, $direction);
|
|
}
|
|
$this->setMigrated($name, $direction);
|
|
|
|
if ($oneStep) {
|
|
break;
|
|
}
|
|
}
|
|
} catch (Throwable $e) {
|
|
$this->unlockTable();
|
|
|
|
throw $e;
|
|
}
|
|
|
|
$this->unlockTable();
|
|
}
|
|
|
|
/**
|
|
* Setup migration tables
|
|
*/
|
|
public function initMigration(): void
|
|
{
|
|
if ($this->schema->hasTable($this->table)) {
|
|
return;
|
|
}
|
|
|
|
$this->schema->create($this->table, function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->string('migration');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Merge file migrations with already migrated tables
|
|
*/
|
|
protected function mergeMigrations(Collection $migrations, Collection $migrated): Collection
|
|
{
|
|
$return = $migrated;
|
|
$return->transform(function ($migration) use ($migrations) {
|
|
$migration = (array) $migration;
|
|
if ($migrations->contains('migration', $migration['migration'])) {
|
|
$migration += $migrations
|
|
->where('migration', $migration['migration'])
|
|
->first();
|
|
}
|
|
|
|
return $migration;
|
|
});
|
|
|
|
$migrations->each(function ($migration) use ($return): void {
|
|
if ($return->contains('migration', $migration['migration'])) {
|
|
return;
|
|
}
|
|
|
|
$return->add($migration);
|
|
});
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Get all migrated migrations
|
|
*/
|
|
protected function getMigrated(): Collection
|
|
{
|
|
return $this->getTableQuery()
|
|
->orderBy('id')
|
|
->where('migration', '!=', 'lock')
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* Migrate a migration
|
|
*/
|
|
protected function migrate(string $file, string $migration, Direction $direction = Direction::UP): void
|
|
{
|
|
require_once $file;
|
|
|
|
$className = Str::studly(preg_replace('/\d+_/', '', $migration));
|
|
/** @var Migration $class */
|
|
$class = $this->app->make('Engelsystem\\Migrations\\' . $className);
|
|
|
|
if (method_exists($class, $direction->value)) {
|
|
$class->{$direction->value}();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set a migration to migrated
|
|
*/
|
|
protected function setMigrated(string $migration, Direction $direction = Direction::UP): void
|
|
{
|
|
$table = $this->getTableQuery();
|
|
|
|
if ($direction === Direction::DOWN) {
|
|
$table->where(['migration' => $migration])->delete();
|
|
return;
|
|
}
|
|
|
|
$table->insert(['migration' => $migration]);
|
|
}
|
|
|
|
/**
|
|
* Lock the migrations table
|
|
*
|
|
*
|
|
* @throws Throwable
|
|
*/
|
|
protected function lockTable(bool $forceMigration = false): void
|
|
{
|
|
$this->schema->getConnection()->transaction(function () use ($forceMigration): void {
|
|
$lock = $this->getTableQuery()
|
|
->where('migration', 'lock')
|
|
->lockForUpdate()
|
|
->first();
|
|
|
|
if ($lock && !$forceMigration) {
|
|
throw new Exception('Unable to acquire migration table lock');
|
|
}
|
|
|
|
$this->getTableQuery()
|
|
->insert(['migration' => 'lock']);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Unlock a previously locked table
|
|
*/
|
|
protected function unlockTable(): void
|
|
{
|
|
$this->getTableQuery()
|
|
->where('migration', 'lock')
|
|
->delete();
|
|
}
|
|
|
|
/**
|
|
* Get a list of migration files
|
|
*/
|
|
protected function getMigrations(string $dir): Collection
|
|
{
|
|
$files = $this->getMigrationFiles($dir);
|
|
|
|
$migrations = new Collection();
|
|
foreach ($files as $dir) {
|
|
$name = str_replace('.php', '', basename($dir));
|
|
$migrations[] = [
|
|
'migration' => $name,
|
|
'path' => $dir,
|
|
];
|
|
}
|
|
|
|
return $migrations->sortBy(function ($value) {
|
|
return $value['migration'];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* List all migration files from the given directory
|
|
*/
|
|
protected function getMigrationFiles(string $dir): array
|
|
{
|
|
return glob($dir . '/*_*.php');
|
|
}
|
|
|
|
/**
|
|
* Init a table query
|
|
*/
|
|
protected function getTableQuery(): Builder
|
|
{
|
|
return $this->schema->getConnection()->table($this->table);
|
|
}
|
|
|
|
/**
|
|
* Set the output function
|
|
*/
|
|
public function setOutput(callable $output): void
|
|
{
|
|
$this->output = $output;
|
|
}
|
|
}
|