Skip to content

Commit

Permalink
Added support for disallowing class morphs (#7110)
Browse files Browse the repository at this point in the history
  • Loading branch information
People-Sea authored Oct 11, 2024
1 parent 9625e73 commit c0a9eb3
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 0 deletions.
35 changes: 35 additions & 0 deletions src/Exception/ClassMorphViolationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/

namespace Hyperf\Database\Exception;

use RuntimeException;

class ClassMorphViolationException extends RuntimeException
{
/**
* The name of the affected Eloquent model.
*/
public string $model;

/**
* Create a new exception instance.
*/
public function __construct(object $model)
{
$class = get_class($model);

parent::__construct("No morph map defined for model [{$class}].");

$this->model = $class;
}
}
10 changes: 10 additions & 0 deletions src/Model/Concerns/HasRelationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use Closure;
use Hyperf\Collection\Arr;
use Hyperf\Database\Exception\ClassMorphViolationException;
use Hyperf\Database\Model\Builder;
use Hyperf\Database\Model\Collection;
use Hyperf\Database\Model\Model;
Expand All @@ -27,6 +28,7 @@
use Hyperf\Database\Model\Relations\MorphOne;
use Hyperf\Database\Model\Relations\MorphTo;
use Hyperf\Database\Model\Relations\MorphToMany;
use Hyperf\Database\Model\Relations\Pivot;
use Hyperf\Database\Model\Relations\Relation;
use Hyperf\Stringable\Str;
use Hyperf\Stringable\StrCache;
Expand Down Expand Up @@ -564,6 +566,14 @@ public function getMorphClass()
return array_search(static::class, $morphMap, true);
}

if (static::class === Pivot::class) {
return static::class;
}

if (Relation::requiresMorphMap()) {
throw new ClassMorphViolationException($this);
}

return static::class;
}

Expand Down
31 changes: 31 additions & 0 deletions src/Model/Relations/Relation.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ abstract class Relation
*/
public static $morphMap = [];

/**
* Prevents morph relationships without a morph map.
*/
protected static bool $requireMorphMap = false;

/**
* The Model query builder instance.
*
Expand Down Expand Up @@ -357,6 +362,32 @@ public static function getMorphAlias(string $className): string
return array_search($className, static::$morphMap, strict: true) ?: $className;
}

/**
* Prevent polymorphic relationships from being used without model mappings.
*/
public static function requireMorphMap(bool $requireMorphMap = true): void
{
static::$requireMorphMap = $requireMorphMap;
}

/**
* Determine if polymorphic relationships require explicit model mapping.
*/
public static function requiresMorphMap(): bool
{
return static::$requireMorphMap;
}

/**
* Define the morph map for polymorphic relations and require all morphed models to be explicitly mapped.
*/
public static function enforceMorphMap(?array $map, bool $merge = true): array
{
static::requireMorphMap();

return static::morphMap($map, $merge);
}

/**
* Get all of the primary keys for an array of models.
*
Expand Down
101 changes: 101 additions & 0 deletions tests/DatabaseStrictMorphsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/

namespace HyperfTest\Database;

use Hyperf\Database\Exception\ClassMorphViolationException;
use Hyperf\Database\Model\Model;
use Hyperf\Database\Model\Relations\Pivot;
use Hyperf\Database\Model\Relations\Relation;
use PHPUnit\Framework\TestCase;

/**
* @internal
* @coversNothing
*/
class DatabaseStrictMorphsTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

Relation::requireMorphMap();
}

protected function tearDown(): void
{
parent::tearDown();

Relation::morphMap([], false);
Relation::requireMorphMap(false);
}

public function testStrictModeThrowsAnExceptionOnClassMap()
{
$this->expectException(ClassMorphViolationException::class);

$model = new TestModel();

$model->getMorphClass();
}

public function testStrictModeDoesNotThrowExceptionWhenMorphMap()
{
$model = new TestModel();

Relation::morphMap([
'foo' => TestModel::class,
]);

$morphName = $model->getMorphClass();
$this->assertSame('foo', $morphName);
}

public function testMapsCanBeEnforcedInOneMethod()
{
$model = new TestModel();

Relation::requireMorphMap(false);

Relation::enforceMorphMap([
'test' => TestModel::class,
]);

$morphName = $model->getMorphClass();
$this->assertSame('test', $morphName);
}

public function testMapIgnoreGenericPivotClass()
{
$this->expectNotToPerformAssertions();
$pivotModel = new Pivot();

$pivotModel->getMorphClass();
}

public function testMapCanBeEnforcedToCustomPivotClass()
{
$this->expectException(ClassMorphViolationException::class);

$pivotModel = new TestPivotModel();

$pivotModel->getMorphClass();
}
}

class TestModel extends Model
{
}

class TestPivotModel extends Pivot
{
}

0 comments on commit c0a9eb3

Please sign in to comment.