diff --git a/src/Illuminate/Database/ClassMorphViolationException.php b/src/Illuminate/Database/ClassMorphViolationException.php new file mode 100644 index 000000000000..6594d2d90238 --- /dev/null +++ b/src/Illuminate/Database/ClassMorphViolationException.php @@ -0,0 +1,29 @@ +model = $class; + } +} diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php index 5262d4305273..a4612b462ecb 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Eloquent\Concerns; use Closure; +use Illuminate\Database\ClassMorphViolationException; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; @@ -731,6 +732,10 @@ public function getMorphClass() return array_search(static::class, $morphMap, true); } + if (Relation::requiresMorphMap()) { + throw new ClassMorphViolationException($this); + } + return static::class; } diff --git a/src/Illuminate/Database/Eloquent/Relations/Relation.php b/src/Illuminate/Database/Eloquent/Relations/Relation.php index 7fe9f3e9fa82..afb5ba8aea08 100755 --- a/src/Illuminate/Database/Eloquent/Relations/Relation.php +++ b/src/Illuminate/Database/Eloquent/Relations/Relation.php @@ -57,6 +57,13 @@ abstract class Relation */ public static $morphMap = []; + /** + * Prevents morph relationships without a morph map. + * + * @var bool + */ + protected static $requireMorphMap = false; + /** * The count of self joins. * @@ -376,6 +383,41 @@ protected function whereInMethod(Model $model, $key) : 'whereIn'; } + /** + * Prevent polymorphic relationships from being used without model mappings. + * + * @param bool $requireMorphMap + * @return void + */ + public static function requireMorphMap($requireMorphMap = true) + { + static::$requireMorphMap = $requireMorphMap; + } + + /** + * Determine if polymorphic relationships require explicit model mapping. + * + * @return bool + */ + public static function requiresMorphMap() + { + return static::$requireMorphMap; + } + + /** + * Define the morph map for polymorphic relations and require all morphed models to be explicitly mapped. + * + * @param array|null $map + * @param bool $merge + * @return array + */ + public static function enforceMorphMap(array $map, $merge = true) + { + static::requireMorphMap(); + + return static::morphMap($map, $merge); + } + /** * Set or get the morph map for polymorphic relations. * diff --git a/tests/Integration/Database/EloquentStrictMorphsTest.php b/tests/Integration/Database/EloquentStrictMorphsTest.php new file mode 100644 index 000000000000..ec9f038e520c --- /dev/null +++ b/tests/Integration/Database/EloquentStrictMorphsTest.php @@ -0,0 +1,65 @@ +expectException(ClassMorphViolationException::class); + + $model = TestModel::make(); + + $model->getMorphClass(); + } + + public function testStrictModeDoesNotThrowExceptionWhenMorphMap() + { + $model = TestModel::make(); + + Relation::morphMap([ + 'test' => TestModel::class, + ]); + + $morphName = $model->getMorphClass(); + $this->assertEquals('test', $morphName); + } + + public function testMapsCanBeEnforcedInOneMethod() + { + $model = TestModel::make(); + + Relation::requireMorphMap(false); + + Relation::enforceMorphMap([ + 'test' => TestModel::class, + ]); + + $morphName = $model->getMorphClass(); + $this->assertEquals('test', $morphName); + } + + protected function tearDown(): void + { + parent::tearDown(); + + Relation::morphMap([], false); + Relation::requireMorphMap(false); + } +} + +class TestModel extends Model +{ +}