diff --git a/src/Contracts/RenamesMigrations.php b/src/Contracts/RenamesMigrations.php new file mode 100644 index 0000000..907db52 --- /dev/null +++ b/src/Contracts/RenamesMigrations.php @@ -0,0 +1,10 @@ +upgrade; + } + + public function priority(): int + { + return $this->upgrade instanceof Prioritization + ? $this->upgrade->priority() + : Prioritization::Default; + } + + public function migratePostDataMigration(): void + { + if ($this->upgrade instanceof MigratesPostDataMigration) { + $this->upgrade->migratePostDataMigration(); + } + } + + public function applicable(): bool + { + return ! $this->upgrade instanceof Applicable + || $this->upgrade->applicable(); + } +} diff --git a/src/Services/Finder.php b/src/Services/Finder.php index 8daf7c4..fc21d1d 100644 --- a/src/Services/Finder.php +++ b/src/Services/Finder.php @@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\File; use LaravelEnso\Upgrade\Contracts\MigratesStructure; +use LaravelEnso\Upgrade\Contracts\RenamesMigrations; class Finder { @@ -30,10 +31,19 @@ private function upgradePackages(): Collection private function upgradeClasses(Package $package): Collection { - return $package->upgradeClasses() - ->map(fn ($class) => new $class) - ->map(fn ($upgrade) => $upgrade instanceof MigratesStructure - ? new Structure($upgrade) - : new $upgrade); + return $package->upgradeClasses()->map(fn ($class) => $this->upgrade($class)); + } + + private function upgrade(string $class) + { + $upgrade = new $class(); + + if ($upgrade instanceof MigratesStructure) { + return new Structure($upgrade); + } elseif ($upgrade instanceof RenamesMigrations) { + return new Migrations($upgrade); + } + + return $upgrade; } } diff --git a/src/Services/Migrations.php b/src/Services/Migrations.php new file mode 100644 index 0000000..b7ff44d --- /dev/null +++ b/src/Services/Migrations.php @@ -0,0 +1,38 @@ +class()->to()) + ->every(fn ($to) => DB::table('migrations')->whereMigration($to)->exists()); + } + + public function migrateData(): void + { + $to = Collection::wrap($this->class()->to())->sortKeys(); + $from = Collection::wrap($this->class()->from())->sortKeys(); + + $invalidMapping = $to->count() !== $from->count() + || $to->keys()->diff($from->keys())->isNotEmpty(); + + if ($invalidMapping) { + $message = 'Invalid number of elements or distinct keys in "from" and "to" arrays'; + throw new EnsoException($message); + } + + $to->combine($from) + ->filter(fn ($from) => DB::table('migrations') + ->whereMigration($from) + ->exists()) + ->each(fn ($from, $to) => DB::table('migrations') + ->whereMigration($from) + ->update(['migration' => $to])); + } +} diff --git a/src/Services/Package.php b/src/Services/Package.php index 80ddddc..83165cd 100644 --- a/src/Services/Package.php +++ b/src/Services/Package.php @@ -7,6 +7,7 @@ use Illuminate\Support\Str; use LaravelEnso\Helpers\Services\JsonReader; use LaravelEnso\Upgrade\Contracts\MigratesStructure; +use LaravelEnso\Upgrade\Contracts\RenamesMigrations; use LaravelEnso\Upgrade\Contracts\Upgrade; use ReflectionClass; use Symfony\Component\Finder\SplFileInfo; @@ -87,6 +88,7 @@ private function isUpgrade($class): bool $reflection = new ReflectionClass($class); return $reflection->implementsInterface(MigratesStructure::class) + || $reflection->implementsInterface(RenamesMigrations::class) || $reflection->implementsInterface(Upgrade::class); } } diff --git a/src/Services/Reflection.php b/src/Services/Reflection.php index fa1dd14..6954f0b 100644 --- a/src/Services/Reflection.php +++ b/src/Services/Reflection.php @@ -12,8 +12,8 @@ class Reflection { public static function reflection(Upgrade $upgrade): ReflectionClass { - return $upgrade instanceof Structure - ? $upgrade->reflection() + return $upgrade instanceof Structure || $upgrade instanceof Migrations + ? new ReflectionClass($upgrade->class()) : new ReflectionClass($upgrade); } diff --git a/src/Services/Structure.php b/src/Services/Structure.php index 6a4c69e..67b712e 100644 --- a/src/Services/Structure.php +++ b/src/Services/Structure.php @@ -8,26 +8,11 @@ use Illuminate\Support\Facades\Config; use LaravelEnso\Permissions\Models\Permission; use LaravelEnso\Roles\Models\Role; -use LaravelEnso\Upgrade\Contracts\Applicable; -use LaravelEnso\Upgrade\Contracts\MigratesData; -use LaravelEnso\Upgrade\Contracts\MigratesPostDataMigration; -use LaravelEnso\Upgrade\Contracts\MigratesStructure; -use LaravelEnso\Upgrade\Contracts\Prioritization; -use LaravelEnso\Upgrade\Contracts\Upgrade; -use ReflectionClass; -class Structure implements Upgrade, MigratesData, Prioritization, MigratesPostDataMigration, Applicable +class Structure extends CustomUpgrade { - private MigratesStructure $upgrade; private Collection $existing; private Collection $roles; - private string $defaultRole; - - public function __construct(MigratesStructure $upgrade) - { - $this->upgrade = $upgrade; - $this->defaultRole = Config::get('enso.config.defaultRole'); - } public function isMigrated(): bool { @@ -50,36 +35,11 @@ public function migrateData(): void if (App::isLocal()) { $this->roles() - ->reject(fn ($role) => $role->name === $this->defaultRole) + ->reject(fn ($role) => $role->name === Config::get('enso.config.defaultRole')) ->each->writeConfig(); } } - public function reflection() - { - return new ReflectionClass($this->upgrade); - } - - public function priority(): int - { - return $this->upgrade instanceof Prioritization - ? $this->upgrade->priority() - : Prioritization::Default; - } - - public function migratePostDataMigration(): void - { - if ($this->upgrade instanceof MigratesPostDataMigration) { - $this->upgrade->migratePostDataMigration(); - } - } - - public function applicable(): bool - { - return ! $this->upgrade instanceof Applicable - || $this->upgrade->applicable(); - } - private function storeWithRoles(array $permission): void { $permission = Permission::create($permission); @@ -90,9 +50,8 @@ private function storeWithRoles(array $permission): void private function syncRoles(Permission $permission): Collection { - return $this->roles()->when(! $permission->is_default, fn ($roles) => $roles - ->filter(fn ($role) => in_array($role->name, $this->upgrade->roles()) - || $role->name === $this->defaultRole)); + return $this->roles()->when(! $permission->is_default, fn ($roles) => $roles->filter(fn ($role) => in_array($role->name, $this->upgrade->roles()) + || $role->name === Config::get('enso.config.defaultRole'))); } private function roles(): Collection diff --git a/tests/units/MigrationsUpgradeTest.php b/tests/units/MigrationsUpgradeTest.php new file mode 100644 index 0000000..d347db3 --- /dev/null +++ b/tests/units/MigrationsUpgradeTest.php @@ -0,0 +1,86 @@ +mock = $this->createMock(TestRenamesMigrations::class); + } + + /** @test */ + public function can_rename_migrations() + { + $this->createMigration('bar'); + + $this->mock->method('from')->willReturn(['bar']); + $this->mock->method('to')->willReturn(['foo']); + + $this->migrateStructure(); + + $this->assertTrue(DB::table('migrations')->whereMigration('foo')->exists()); + } + + /** @test */ + public function will_throw_exception_on_argument_count_mismatch() + { + $this->expectException(EnsoException::class); + + $this->mock->method('to')->willReturn(['foo']); + $this->mock->method('from')->willReturn(['foo', 'bar']); + + $this->migrateStructure(); + } + + /** @test */ + public function will_not_migrate_data_if_all_to_migrations_exist() + { + $this->createMigration('foo'); + $this->createMigration('qux'); + + $this->mock->method('from')->willReturn(['bar', 'baz']); + $this->mock->method('to')->willReturn(['foo', 'qux']); + + $service = Mockery::mock(Migrations::class, [$this->mock]); + $service->expects()->class()->andReturn($this->mock)->twice(); + $service->expects()->isMigrated()->andReturn(true); + + (new Database($service))->handle(); + } + + private function migrateStructure() + { + (new Database(new Migrations($this->mock)))->handle(); + } + + private function createMigration(string $name): void + { + DB::table('migrations')->insert([ + 'migration' => $name, + 'batch' => 1, + ]); + } +} + +class TestRenamesMigrations implements RenamesMigrations +{ + public function from(): array + { + return []; + } + + public function to(): array + { + return []; + } +} diff --git a/tests/units/StructureUpgradeTest.php b/tests/units/StructureUpgradeTest.php index 3339d2a..c966599 100644 --- a/tests/units/StructureUpgradeTest.php +++ b/tests/units/StructureUpgradeTest.php @@ -22,7 +22,7 @@ protected function setUp(): void { parent::setUp(); - $this->upgrade = new TestStructureMigration(); + $this->mock = $this->createMock(TestStructureMigration::class); $this->defaultRole = $this->role(Config::get('enso.config.defaultRole')); @@ -32,9 +32,9 @@ protected function setUp(): void /** @test */ public function can_migrate() { - $this->upgrade->permissions = [ + $this->mock->method('permissions')->willReturn([ ['name' => 'test', 'description' => 'test', 'is_default' => true], - ]; + ]); $this->migrateStructure(); @@ -44,9 +44,9 @@ public function can_migrate() /** @test */ public function can_migrate_default_permission() { - $this->upgrade->permissions = [ + $this->mock->method('permissions')->willReturn([ ['name' => 'test', 'description' => 'test', 'is_default' => true], - ]; + ]); $this->migrateStructure(); @@ -57,9 +57,9 @@ public function can_migrate_default_permission() /** @test */ public function can_migrate_non_default_permission() { - $this->upgrade->permissions = [ + $this->mock->method('permissions')->willReturn([ ['name' => 'test', 'description' => 'test', 'is_default' => false], - ]; + ]); $this->migrateStructure(); @@ -70,9 +70,9 @@ public function can_migrate_non_default_permission() /** @test */ public function skips_existing_permissions() { - $this->upgrade->permissions = [ + $this->mock->method('permissions')->willReturn([ ['name' => 'test', 'description' => 'test', 'is_default' => true], - ]; + ]); $this->migrateStructure(); $this->migrateStructure(); @@ -89,13 +89,11 @@ protected function role($name) private function migrateStructure() { - (new Database(new Structure($this->upgrade)))->handle(); + (new Database(new Structure($this->mock)))->handle(); } } class TestStructureMigration implements MigratesStructure { use StructureMigration; - - public $permissions = []; }