Added support for disallowing class morphs (#7110)

This commit is contained in:
WenRenHai 2024-10-11 18:25:57 +08:00 committed by GitHub
parent e42bc9676f
commit a0be0cd6aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 178 additions and 0 deletions

View File

@ -5,6 +5,7 @@
- [#7063](https://github.com/hyperf/hyperf/pull/7063) Added methods `nullableUuidMorphs` `uuidMorphs` and `nullableNumericMorphs` to `Hyperf\Database\Schema\Blueprint`.
- [#7070](https://github.com/hyperf/hyperf/pull/7070) Added `Blueprint::charset()` and `Blueprint::collation()`.
- [#7071](https://github.com/hyperf/hyperf/pull/7071) Added `Hyperf\Database\Schema\Blueprint::tinyText()`.
- [#7110](https://github.com/hyperf/hyperf/pull/7110) Added support for disallowing class morphs.
# v3.1.43 - 2024-10-10

View File

@ -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;
}
}

View File

@ -14,6 +14,7 @@ namespace Hyperf\Database\Model\Concerns;
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;
@ -27,6 +28,7 @@ use Hyperf\Database\Model\Relations\MorphMany;
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;
@ -564,6 +566,14 @@ trait HasRelationships
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;
}

View File

@ -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.
*
@ -357,6 +362,32 @@ abstract class Relation
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.
*

View File

@ -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
{
}