diff --git a/README.md b/README.md index 876ecaf..e36606d 100644 --- a/README.md +++ b/README.md @@ -288,9 +288,22 @@ return Car::migration(function (Blueprint $table) { }) ``` +An end-developer can also add multiple callbacks programmatically if needed, which are great to separate concerns. + +```php +use MyVendor\MyPackage\Models\Car; +use Illuminate\Database\Schema\Blueprint; + +return Car::migration( + fn ($table) => /* ... */, + fn ($table) => /* ... */, + fn ($table) => /* ... */, +); +``` + > [!TIP] > -> If you don't want to support additional columns, it's fine. If the end-developer adds a callback, it won't be executed regardless. +> You can omit the `addColumns()` call if you don't want to support additional columns, as any added callback won't be executed. ### Morphs @@ -322,7 +335,7 @@ return Car::migration()->morph('ulid', 'custom_index_name'); ### After Up & Before Down -The `CustomizableMigration` contains two methods, `afterUp()` and `beforeDown()`. The first is executed after the table is created, while the latter is executed before the table is dropped. This allows the developer to run custom logic to enhance its migrations, or avoid failing migrations. +An end-developer can execute logic after the table is created, and before the table is dropped, using the `afterUp()` and `beforeDown()` methods, respectively. This allows the developer to run enhance the table, or avoid failing migrations. For example, the end-developer can use these methods to create foreign column references, and remove them before dropping the table. @@ -339,6 +352,10 @@ return Car::migration() }); ``` +> [!IMPORTANT] +> +> The `afterUp()` and `beforeDown()` adds callbacks to the migration, it doesn't replace them. + ## Package documentation If you plan to add this to your package, you may also want to copy-and-paste the [MIGRATIONS.md](MIGRATIONS.md) file in your package. This way developers will know how to use your model and migrations. Alternatively, you may also just copy its contents, or link back to this repository. diff --git a/src/CustomizableMigration.php b/src/CustomizableMigration.php index 84bc9d0..b91e12a 100644 --- a/src/CustomizableMigration.php +++ b/src/CustomizableMigration.php @@ -7,6 +7,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +use function array_push; use function sprintf; use function strtolower; @@ -28,17 +29,17 @@ abstract class CustomizableMigration extends Migration * Create a new Customizable Migration instance. * * @param class-string<\Illuminate\Database\Eloquent\Model> $model - * @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void)|null $with - * @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void)|null $afterUp - * @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void)|null $beforeDown + * @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void)[] $with + * @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void)[] $afterUp + * @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void)[] $beforeDown * @param "numeric"|"uuid"|"ulid"|"" $morphType * @param string|null $morphIndexName */ public function __construct( string $model, - protected ?Closure $with = null, - protected ?Closure $afterUp = null, - protected ?Closure $beforeDown = null, + protected array $with = [], + protected array $afterUp = [], + protected array $beforeDown = [], protected string $morphType = '', protected ?string $morphIndexName = null, ) @@ -64,14 +65,16 @@ protected function boot(): void abstract public function create(Blueprint $table): void; /** - * Execute a callback from the developer to add more columns in the table, if any. + * Execute stored callbacks using the table Blueprint instance. * * @param \Illuminate\Database\Schema\Blueprint $table * @return void */ protected function addColumns(Blueprint $table): void { - with($table, $this->with); + foreach ($this->with as $callback) { + $callback($table); + } } /** @@ -92,12 +95,12 @@ public function morph(string $type, string $indexName = null): static /** * Add additional columns to the table. * - * @param \Closure(\Illuminate\Database\Schema\Blueprint $table):void $callback + * @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void) ...$callbacks * @return $this */ - public function with(Closure $callback): static + public function with(Closure ...$callbacks): static { - $this->with = $callback; + array_push($this->with, ...$callbacks); return $this; } @@ -105,12 +108,12 @@ public function with(Closure $callback): static /** * Execute the callback after the "up" method. * - * @param \Closure(\Illuminate\Database\Schema\Blueprint $table):void $callback + * @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void) ...$callbacks * @return $this */ - public function afterUp(Closure $callback): static + public function afterUp(Closure ...$callbacks): static { - $this->afterUp = $callback; + array_push($this->afterUp, ...$callbacks); return $this; } @@ -118,12 +121,12 @@ public function afterUp(Closure $callback): static /** * Execute the callback before the "down" method. * - * @param \Closure(\Illuminate\Database\Schema\Blueprint $table):void $callback + * @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void) ...$callbacks * @return $this */ - public function beforeDown(Closure $callback): static + public function beforeDown(Closure ...$callbacks): static { - $this->beforeDown = $callback; + array_push($this->beforeDown, ...$callbacks); return $this; } @@ -180,8 +183,8 @@ public function up(): void { Schema::create($this->table, $this->create(...)); - if ($this->afterUp) { - Schema::table($this->table, $this->afterUp); + foreach ($this->afterUp as $callback) { + Schema::table($this->table, $callback); } } @@ -192,8 +195,8 @@ public function up(): void */ public function down(): void { - if ($this->beforeDown) { - Schema::table($this->table, $this->beforeDown); + foreach ($this->beforeDown as $callback) { + Schema::table($this->table, $callback); } Schema::dropIfExists($this->table); diff --git a/src/CustomizableModel.php b/src/CustomizableModel.php index 4d019f2..152465b 100644 --- a/src/CustomizableModel.php +++ b/src/CustomizableModel.php @@ -121,10 +121,10 @@ abstract protected static function migrationClass(): string; /** * Return a new customizable migration instance. * - * @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void)|null $with + * @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void) ...$with */ - public static function migration(Closure $with = null): CustomizableMigration + public static function migration(Closure ...$with): CustomizableMigration { - return new (static::migrationClass())(static::class, $with); + return (new (static::migrationClass())(static::class, $with)); } } diff --git a/tests/CustomizableMigrationTest.php b/tests/CustomizableMigrationTest.php index 8528a30..22660b0 100644 --- a/tests/CustomizableMigrationTest.php +++ b/tests/CustomizableMigrationTest.php @@ -98,19 +98,24 @@ public function creates_column_with_callback(): void TestMigration::$callMethod = true; $blueprint = m::mock(Blueprint::class); - $blueprint->expects('createCall')->twice(); - $blueprint->expects('addCall')->twice(); + $blueprint->expects('createCall')->once(); + $blueprint->expects('firstCall')->once(); + $blueprint->expects('secondCall')->once(); + $blueprint->expects('thirdCall')->once(); + $blueprint->expects('fourthCall')->once(); $this->container->instance('db.schema', $schema = m::mock(SchemaBuilder::class)); - $schema->expects('create')->twice()->withArgs(function (string $table, Closure $closure) use ($blueprint): bool { + $schema->expects('create')->once()->withArgs(function (string $table, Closure $closure) use ($blueprint): bool { static::assertSame('test_models', $table); $closure($blueprint); return true; }); - TestModel::migration()->with(fn($table) => $table->addCall())->up(); - TestModel::migration(fn($table) => $table->addCall())->up(); + TestModel::migration(fn($table) => $table->firstCall()) + ->with(fn($table) => $table->secondCall()) + ->with(fn($table) => $table->thirdCall(), fn($table) => $table->fourthCall()) + ->up(); } #[Test] @@ -146,7 +151,8 @@ public function morphs_to_numeric(): void $blueprint->expects('nullableNumericMorphs')->with('bar', null)->twice(); $this->container->instance('db.schema', $schema = m::mock(SchemaBuilder::class)); - $schema->expects('create')->twice()->withArgs(function (string $table, Closure $closure) use ($blueprint): bool { + $schema->expects('create')->twice()->withArgs(function (string $table, Closure $closure) use ($blueprint + ): bool { static::assertSame('test_models', $table); $closure($blueprint); @@ -169,7 +175,8 @@ public function morphs_to_uuid(): void $blueprint->expects('nullableUuidMorphs')->with('bar', null)->twice(); $this->container->instance('db.schema', $schema = m::mock(SchemaBuilder::class)); - $schema->expects('create')->twice()->withArgs(function (string $table, Closure $closure) use ($blueprint): bool { + $schema->expects('create')->twice()->withArgs(function (string $table, Closure $closure) use ($blueprint + ): bool { static::assertSame('test_models', $table); $closure($blueprint); @@ -191,7 +198,8 @@ public function morphs_to_ulid(): void $blueprint->expects('nullableUlidMorphs')->with('bar', null)->twice(); $this->container->instance('db.schema', $schema = m::mock(SchemaBuilder::class)); - $schema->expects('create')->twice()->withArgs(function (string $table, Closure $closure) use ($blueprint): bool { + $schema->expects('create')->twice()->withArgs(function (string $table, Closure $closure) use ($blueprint + ): bool { static::assertSame('test_models', $table); $closure($blueprint); @@ -206,18 +214,24 @@ public function morphs_to_ulid(): void public function calls_after_up(): void { $blueprint = m::mock(Blueprint::class); - $blueprint->expects('createCall')->once(); + $blueprint->expects('firstCall')->once(); + $blueprint->expects('secondCall')->once(); + $blueprint->expects('thirdCall')->once(); $this->container->instance('db.schema', $schema = m::mock(SchemaBuilder::class)); $schema->expects('create')->once(); - $schema->expects('table')->once()->withArgs(function (string $table, Closure $closure) use ($blueprint): bool { + $schema->expects('table')->times(3)->withArgs(function (string $table, Closure $closure) use ($blueprint + ): bool { static::assertSame('test_models', $table); $closure($blueprint); return true; }); - TestModel::migration()->afterUp(fn($table) => $table->createCall())->up(); + TestModel::migration() + ->afterUp(fn($table) => $table->firstCall()) + ->afterUp(fn($table) => $table->secondCall(), fn($table) => $table->thirdCall()) + ->up(); } #[Test] @@ -235,10 +249,13 @@ public function drops_table(): void public function calls_before_down(): void { $blueprint = m::mock(Blueprint::class); - $blueprint->expects('afterDownCall')->once(); + $blueprint->expects('firstCall')->once(); + $blueprint->expects('secondCall')->once(); + $blueprint->expects('thirdCall')->once(); $this->container->instance('db.schema', $schema = m::mock(SchemaBuilder::class)); - $schema->expects('table')->once()->withArgs(function (string $table, Closure $closure) use ($blueprint): bool { + $schema->expects('table')->times(3)->withArgs(function (string $table, Closure $closure) use ($blueprint + ): bool { static::assertSame('test_models', $table); $closure($blueprint); @@ -247,7 +264,10 @@ public function calls_before_down(): void $schema->expects('dropIfExists')->with('test_models')->once(); - TestModel::migration()->beforeDown(fn($table) => $table->afterDownCall())->down(); + TestModel::migration() + ->beforeDown(fn($table) => $table->firstCall()) + ->beforeDown(fn($table) => $table->secondCall(), fn($table) => $table->thirdCall()) + ->down(); } #[Test]