Skip to content

Commit

Permalink
[1.x] Adds morph default index name.
Browse files Browse the repository at this point in the history
  • Loading branch information
DarkGhostHunter committed Mar 15, 2024
1 parent 2b54f8c commit 9ae2692
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 63 deletions.
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ return Car::migration(function (Blueprint $table) {
### Morphs

> [!CAUTION]
>
> Morphs are only supported for a single relation. It's highly discouraged multiple morphs relations on one table.
If your migration requires morph relationships, you will find that end-developers won't always have the same key type in their application. This problem can be fixed by using the `createMorph()` or `createNullableMorph()` method with the `Blueprint` instance and the name of the morph type.

```php
Expand All @@ -227,11 +231,33 @@ This will let the end-developer to change the morph type through the `morph()` m

```php
use MyVendor\MyPackage\Models\Car;
use Illuminate\Database\Schema\Blueprint;

return Car::migration()->morph('ulid', 'custom_index_name');
```

#### Default index name

You may also set a custom index name for the morph. It will be used as a default, unless the user overrides it manually.

```php
protected function create(Blueprint $table)
{
$this->createMorphRelation($table, 'ownable', 'ownable_table_index');

// ...
}
```

```php
use MyVendor\MyPackage\Models\Car;

// Uses "custom_index_name" as index name
return Car::migration()->morph('ulid', 'custom_index_name');

// Uses "ownable_table_index" as index name
return Car::migration()->morph('ulid');
```

### 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.
Expand Down
71 changes: 44 additions & 27 deletions src/CustomizableMigration.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Laragear\MetaModel;

use BadMethodCallException;
use Closure;
use Error;
use Illuminate\Database\Migrations\Migration;
Expand Down Expand Up @@ -32,7 +33,6 @@ abstract class CustomizableMigration extends Migration
* @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void)|null $afterUp
* @param (\Closure(\Illuminate\Database\Schema\Blueprint $table):void)|null $beforeDown
* @param "numeric"|"uuid"|"ulid"|"" $morphType
* @param string|null $morphIndexName
*/
public function __construct(
string $model,
Expand All @@ -41,6 +41,7 @@ public function __construct(
protected ?Closure $beforeDown = null,
protected string $morphType = '',
protected ?string $morphIndexName = null,
protected bool $morphCalled = false,
)
{
$this->table = (new $model)->getTable();
Expand All @@ -62,6 +63,48 @@ protected function addColumns(Blueprint $table): void
with($table, $this->with);
}

/**
* Create a new morph relation.
*/
protected function createMorph(Blueprint $table, string $name, string $indexName = null): void
{
if ($this->morphCalled) {
throw new BadMethodCallException('Using multiple customizable morph calls is unsupported.');
}

$indexName = $this->morphIndexName ?? $indexName;

match (strtolower($this->morphType)) {
'numeric' => $table->numericMorphs($name, $indexName),
'uuid' => $table->uuidMorphs($name, $indexName),
'ulid' => $table->ulidMorphs($name, $indexName),
default => $table->morphs($name, $indexName)
};

$this->morphCalled = true;
}

/**
* Create a new nullable morph relation.
*/
protected function createNullableMorph(Blueprint $table, string $name, string $indexName = null): void
{
if ($this->morphCalled) {
throw new BadMethodCallException('Using multiple customizable morph calls is unsupported.');

Check warning on line 93 in src/CustomizableMigration.php

View check run for this annotation

Codecov / codecov/patch

src/CustomizableMigration.php#L93

Added line #L93 was not covered by tests
}

$indexName = $this->morphIndexName ?? $indexName;

match (strtolower($this->morphType)) {
'numeric' => $table->nullableNumericMorphs($name, $indexName),
'uuid' => $table->nullableUuidMorphs($name, $indexName),
'ulid' => $table->nullableUlidMorphs($name, $indexName),
default => $table->nullableMorphs($name, $indexName)
};

$this->morphCalled = true;
}

/**
* Sets the morph type of the migration.
*
Expand Down Expand Up @@ -116,32 +159,6 @@ public function beforeDown(Closure $callback): static
return $this;
}

/**
* Create a new morph relation.
*/
protected function createMorph(Blueprint $table, string $name): void
{
match (strtolower($this->morphType)) {
'numeric' => $table->numericMorphs($name, $this->morphIndexName),
'uuid' => $table->uuidMorphs($name, $this->morphIndexName),
'ulid' => $table->ulidMorphs($name, $this->morphIndexName),
default => $table->morphs($name, $this->morphIndexName)
};
}

/**
* Create a new nullable morph relation.
*/
protected function createNullableMorph(Blueprint $table, string $name): void
{
match (strtolower($this->morphType)) {
'numeric' => $table->nullableNumericMorphs($name, $this->morphIndexName),
'uuid' => $table->nullableUuidMorphs($name, $this->morphIndexName),
'ulid' => $table->nullableUlidMorphs($name, $this->morphIndexName),
default => $table->nullableMorphs($name, $this->morphIndexName)
};
}

/**
* Dynamically handle property access to the object.
*
Expand Down
150 changes: 126 additions & 24 deletions tests/CustomizableMigrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@

namespace Tests;

use BadMethodCallException;
use Closure;
use Illuminate\Container\Container;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder as SchemaBuilder;
use Illuminate\Support\Facades\Schema as SchemaFacade;
use Laragear\MetaModel\CustomizableMigration;
use Mockery as m;
use Mockery\MockInterface;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Tests\Fixtures\TestMigration;
use Tests\Fixtures\TestMigrationWithMorph;
use Tests\Fixtures\TestMigrationWithMorphDefaulted;
use Tests\Fixtures\TestModel;
use Throwable;

class CustomizableMigrationTest extends TestCase
{
Expand Down Expand Up @@ -57,8 +62,6 @@ public function creates_columns(): void
return true;
});



TestModel::migration()->up();
}

Expand Down Expand Up @@ -116,14 +119,101 @@ public function creates_column_with_callback(): void
}

#[Test]
public function morphs_default_from_builder(): void
public function morphs_throws_if_called_twice(): void
{
TestModel::$migration = TestMigrationWithMorph::class;
$blueprint = m::mock(Blueprint::class);
$blueprint->expects('morphs')->with('foo', null)->once();

$this->container->instance('db.schema', $schema = m::mock(SchemaBuilder::class));

$exception = null;

$schema->expects('create')->once()->withArgs(
function (string $table, Closure $closure) use ($blueprint, &$exception): bool {
try {
$closure($blueprint);
} catch (Throwable $e) {
$exception = $e;
}

return true;
}
);

$migration = new class(TestModel::class) extends CustomizableMigration
{
public function create(Blueprint $table): void
{
$this->createMorph($table, 'foo');
$this->createMorph($table, 'foo');
}
};

$this->expectException(BadMethodCallException::class);
$this->expectExceptionMessage('Using multiple customizable morph calls is unsupported.');

$migration->up();

throw $exception;
}

#[Test]
public function morph_nullable_throws_if_called_twice(): void
{
$blueprint = m::mock(Blueprint::class);
$blueprint->expects('createCall')->once();
$blueprint->expects('morphs')->with('foo', null)->once();
$blueprint->expects('nullableMorphs')->with('bar', null)->once();

$this->container->instance('db.schema', $schema = m::mock(SchemaBuilder::class));

$exception = null;

$schema->expects('create')->once()->withArgs(
function (string $table, Closure $closure) use ($blueprint, &$exception): bool {
try {
$closure($blueprint);
} catch (Throwable $e) {
$exception = $e;
}

return true;
}
);

$migration = new class(TestModel::class) extends CustomizableMigration
{
public function create(Blueprint $table): void
{
$this->createMorph($table, 'foo');
$this->createMorph($table, 'foo');
}
};

$this->expectException(BadMethodCallException::class);
$this->expectExceptionMessage('Using multiple customizable morph calls is unsupported.');

$migration->up();

throw $exception;
}

public static function useMigrations(): array
{
return [
['migration' => TestMigrationWithMorph::class, 'index' => null],
['migration' => TestMigrationWithMorphDefaulted::class, 'index' => 'custom_index'],
];
}

#[Test]
#[DataProvider('useMigrations')]
public function morphs_default_from_builder(string $migration, ?string $index): void
{
TestModel::$migration = $migration;

$blueprint = m::mock(Blueprint::class);
$blueprint->expects('createCall')->once();
$blueprint->expects('morphs')->with('foo', $index)->once();
$blueprint->expects('nullableMorphs')->with('bar', $index)->once();

$this->container->instance('db.schema', $schema = m::mock(SchemaBuilder::class));

Expand All @@ -138,17 +228,20 @@ public function morphs_default_from_builder(): void
}

#[Test]
public function morphs_to_numeric(): void
#[DataProvider('useMigrations')]
public function morphs_to_numeric(string $migration, ?string $index): void
{
TestModel::$migration = TestMigrationWithMorph::class;
TestModel::$migration = $migration;

$blueprint = m::mock(Blueprint::class);
$blueprint->expects('createCall')->twice();
$blueprint->expects('numericMorphs')->with('foo', null)->twice();
$blueprint->expects('nullableNumericMorphs')->with('bar', null)->twice();
$blueprint->expects('createCall')->times(3);
$blueprint->expects('numericMorphs')->with('foo', $index)->twice();
$blueprint->expects('nullableNumericMorphs')->with('bar', $index)->twice();
$blueprint->expects('numericMorphs')->with('foo', 'test_index')->once();
$blueprint->expects('nullableNumericMorphs')->with('bar', 'test_index')->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')->times(3)->withArgs(function (string $table, Closure $closure) use ($blueprint): bool {
static::assertSame('test_models', $table);

$closure($blueprint);
Expand All @@ -158,20 +251,24 @@ public function morphs_to_numeric(): void

TestModel::migration()->morphNumeric->up();
TestModel::migration()->morph('numeric')->up();
TestModel::migration()->morph('numeric', 'test_index')->up();
}

#[Test]
public function morphs_to_uuid(): void
#[DataProvider('useMigrations')]
public function morphs_to_uuid(string $migration, ?string $index): void
{
TestModel::$migration = TestMigrationWithMorph::class;
TestModel::$migration = $migration;

$blueprint = m::mock(Blueprint::class);
$blueprint->expects('createCall')->twice();
$blueprint->expects('uuidMorphs')->with('foo', null)->twice();
$blueprint->expects('nullableUuidMorphs')->with('bar', null)->twice();
$blueprint->expects('createCall')->times(3);
$blueprint->expects('uuidMorphs')->with('foo', $index)->twice();
$blueprint->expects('nullableUuidMorphs')->with('bar', $index)->twice();
$blueprint->expects('uuidMorphs')->with('foo', 'test_index')->once();
$blueprint->expects('nullableUuidMorphs')->with('bar', 'test_index')->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')->times(3)->withArgs(function (string $table, Closure $closure) use ($blueprint): bool {
static::assertSame('test_models', $table);
$closure($blueprint);

Expand All @@ -180,20 +277,24 @@ public function morphs_to_uuid(): void

TestModel::migration()->morphUuid->up();
TestModel::migration()->morph('uuid')->up();
TestModel::migration()->morph('uuid', 'test_index')->up();
}

#[Test]
public function morphs_to_ulid(): void
#[DataProvider('useMigrations')]
public function morphs_to_ulid(string $migration, ?string $index): void
{
TestModel::$migration = TestMigrationWithMorph::class;
TestModel::$migration = $migration;

$blueprint = m::mock(Blueprint::class);
$blueprint->expects('createCall')->twice();
$blueprint->expects('ulidMorphs')->with('foo', null)->twice();
$blueprint->expects('nullableUlidMorphs')->with('bar', null)->twice();
$blueprint->expects('createCall')->times(3);
$blueprint->expects('ulidMorphs')->with('foo', $index)->twice();
$blueprint->expects('nullableUlidMorphs')->with('bar', $index)->twice();
$blueprint->expects('ulidMorphs')->with('foo', 'test_index')->once();
$blueprint->expects('nullableUlidMorphs')->with('bar', 'test_index')->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')->times(3)->withArgs(function (string $table, Closure $closure) use ($blueprint): bool {
static::assertSame('test_models', $table);
$closure($blueprint);

Expand All @@ -202,6 +303,7 @@ public function morphs_to_ulid(): void

TestModel::migration()->morphUlid->up();
TestModel::migration()->morph('ulid')->up();
TestModel::migration()->morph('ulid', 'test_index')->up();
}

#[Test]
Expand Down
Loading

0 comments on commit 9ae2692

Please sign in to comment.