diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index de2233249055..c819f8c90506 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -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. * diff --git a/src/Illuminate/Database/Schema/Grammars/Grammar.php b/src/Illuminate/Database/Schema/Grammars/Grammar.php index 391324b9c6d2..dc7308387b66 100755 --- a/src/Illuminate/Database/Schema/Grammars/Grammar.php +++ b/src/Illuminate/Database/Schema/Grammars/Grammar.php @@ -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) + ); + } } diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 16e8634d3e6b..13758c501acd 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -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) + ); + } } diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php index 76c734baa369..b617af213be5 100755 --- a/tests/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Database/DatabaseSchemaBlueprintTest.php @@ -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) {