Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions src/Illuminate/Database/Schema/Blueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,80 @@ public function foreignIdFor($model, $column = null)
->referencesModelColumn($model->getKeyName());
}

/**
* Convert a foreign key column to a polymorphic relationship (integer IDs).
*
* @param string $column
* @param string $morphName
* @param string $defaultOwnerType
* @return void
*/
public function foreignIdToMorph($column, $morphName, $defaultOwnerType)
{
$this->convertToMorph($column, $morphName, $defaultOwnerType, 'bigInteger', ['unsigned' => true]);
}

/**
* Convert a foreign key column to a polymorphic relationship (UUID IDs).
*
* @param string $column
* @param string $morphName
* @param string $defaultOwnerType
* @return void
*/
public function foreignIdToUuidMorph($column, $morphName, $defaultOwnerType)
{
$this->convertToMorph($column, $morphName, $defaultOwnerType, 'uuid');
}

/**
* Convert a foreign key column to a polymorphic relationship (ULID IDs).
*
* @param string $column
* @param string $morphName
* @param string $defaultOwnerType
* @return void
*/
public function foreignIdToUlidMorph($column, $morphName, $defaultOwnerType)
{
$this->convertToMorph($column, $morphName, $defaultOwnerType, 'char', ['length' => 26]);
}

/**
* Convert a foreign key column to a polymorphic relationship.
*
* @param string $column
* @param string $morphName
* @param string $defaultOwnerType
* @param string $columnType
* @param array $columnOptions
* @return void
*/
protected function convertToMorph($column, $morphName, $defaultOwnerType, $columnType, $columnOptions = [])
{
// Drop the foreign key constraint if it exists
$this->dropForeign([$column]);

// Rename the column first
$this->renameColumn($column, "{$morphName}_id");

// Change the column type
$this->addColumn($columnType, "{$morphName}_id", $columnOptions)->change();

// Add the morph type column
$this->string("{$morphName}_type")->after("{$morphName}_id")->nullable();

// Add index for the morph columns
$this->index(["{$morphName}_type", "{$morphName}_id"]);

// Add command to update existing records with the default owner type
$this->addCommand('updateMorphType', [
'morphIdColumn' => "{$morphName}_id",
'morphTypeColumn' => "{$morphName}_type",
'defaultOwnerType' => $defaultOwnerType,
]);
}

/**
* Create a new float column on the table.
*
Expand Down
19 changes: 19 additions & 0 deletions src/Illuminate/Database/Schema/Grammars/Grammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -507,4 +507,23 @@ public function supportsSchemaTransactions()
{
return $this->transactions;
}

/**
* Compile an update morph type command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string
*/
public function compileUpdateMorphType(Blueprint $blueprint, Fluent $command)
{
return sprintf(
'update %s set %s = %s where %s is not null and %s is null',
$this->wrapTable($blueprint->getTable()),
$this->wrap($command->morphTypeColumn),
$this->getDefaultValue($command->defaultOwnerType),
$this->wrap($command->morphIdColumn),
$this->wrap($command->morphTypeColumn)
);
}
}
19 changes: 19 additions & 0 deletions src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -1390,4 +1390,23 @@ protected function wrapJsonSelector($value)

return 'json_unquote(json_extract('.$field.$path.'))';
}

/**
* Compile an update morph type command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string
*/
public function compileUpdateMorphType(Blueprint $blueprint, Fluent $command)
{
return sprintf(
'update %s set %s = %s where %s is not null and %s is null',
$this->wrapTable($blueprint->getTable()),
$this->wrap($command->morphTypeColumn),
$this->getDefaultValue(addslashes($command->defaultOwnerType)),
$this->wrap($command->morphIdColumn),
$this->wrap($command->morphTypeColumn)
);
}
}
84 changes: 84 additions & 0 deletions tests/Database/DatabaseSchemaBlueprintTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,90 @@ public function testDefaultUsingNullableUlidMorph()
], $getSql('MySql'));
}

public function testForeignIdToMorph()
{
$getSql = function ($grammar) {
if ($grammar == 'MySql') {
$connection = $this->getConnection($grammar);
$connection->shouldReceive('getServerVersion')->andReturn('8.0.4');
$connection->shouldReceive('isMaria')->andReturn(false);

return (new Blueprint($connection, 'posts', function ($table) {
$table->foreignIdToMorph('user_id', 'author', 'App\Models\Post');
}))->toSql();
} else {
return $this->getBlueprint($grammar, 'posts', function ($table) {
$table->foreignIdToMorph('user_id', 'author', 'App\Models\Post');
})->toSql();
}
};

$this->assertEquals([
'alter table `posts` drop foreign key `posts_user_id_foreign`',
'alter table `posts` rename column `user_id` to `author_id`',
'alter table `posts` modify `author_id` bigint unsigned not null',
'alter table `posts` add `author_type` varchar(255) null after `author_id`',
'alter table `posts` add index `posts_author_type_author_id_index`(`author_type`, `author_id`)',
'update `posts` set `author_type` = \'App\\\\Models\\\\Post\' where `author_id` is not null and `author_type` is null',
], $getSql('MySql'));
}

public function testForeignIdToUuidMorph()
{
$getSql = function ($grammar) {
if ($grammar == 'MySql') {
$connection = $this->getConnection($grammar);
$connection->shouldReceive('getServerVersion')->andReturn('8.0.4');
$connection->shouldReceive('isMaria')->andReturn(false);

return (new Blueprint($connection, 'posts', function ($table) {
$table->foreignIdToUuidMorph('user_id', 'author', 'App\Models\Post');
}))->toSql();
} else {
return $this->getBlueprint($grammar, 'posts', function ($table) {
$table->foreignIdToUuidMorph('user_id', 'author', 'App\Models\Post');
})->toSql();
}
};

$this->assertEquals([
'alter table `posts` drop foreign key `posts_user_id_foreign`',
'alter table `posts` rename column `user_id` to `author_id`',
'alter table `posts` modify `author_id` char(36) not null',
'alter table `posts` add `author_type` varchar(255) null after `author_id`',
'alter table `posts` add index `posts_author_type_author_id_index`(`author_type`, `author_id`)',
'update `posts` set `author_type` = \'App\\\\Models\\\\Post\' where `author_id` is not null and `author_type` is null',
], $getSql('MySql'));
}

public function testForeignIdToUlidMorph()
{
$getSql = function ($grammar) {
if ($grammar == 'MySql') {
$connection = $this->getConnection($grammar);
$connection->shouldReceive('getServerVersion')->andReturn('8.0.4');
$connection->shouldReceive('isMaria')->andReturn(false);

return (new Blueprint($connection, 'posts', function ($table) {
$table->foreignIdToUlidMorph('user_id', 'author', 'App\Models\Post');
}))->toSql();
} else {
return $this->getBlueprint($grammar, 'posts', function ($table) {
$table->foreignIdToUlidMorph('user_id', 'author', 'App\Models\Post');
})->toSql();
}
};

$this->assertEquals([
'alter table `posts` drop foreign key `posts_user_id_foreign`',
'alter table `posts` rename column `user_id` to `author_id`',
'alter table `posts` modify `author_id` char(26) not null',
'alter table `posts` add `author_type` varchar(255) null after `author_id`',
'alter table `posts` add index `posts_author_type_author_id_index`(`author_type`, `author_id`)',
'update `posts` set `author_type` = \'App\\\\Models\\\\Post\' where `author_id` is not null and `author_type` is null',
], $getSql('MySql'));
}

public function testGenerateRelationshipColumnWithIncrementalModel()
{
$getSql = function ($grammar) {
Expand Down