如何解决Doctrine 2 自定义实体加载和持久化
是否可以基于每个实体在 Doctrine 2 中实现自定义的水化和持久化?
Doctrine 2 在值对象(例如集合和 ID)方面有一些主要限制。我想知道是否可以使用自定义机制(或实现)来将对象属性映射到数据库(加载和持久性)。
我知道有一些可能性可以“解决”这个问题,但我都不喜欢:
- 虚假实体需要在实体中进行适当处理,这会将持久层泄漏到域对象中
- 真正的实体需要在持久性方面做更多工作(更多存储库和更复杂的处理)
- Embaddables 具有上述限制
- 具有序列化功能的自定义 DBAL 类型使查询某些值变得不可能或至少非常慢
我知道学说中有生命周期事件可能有用。我不知道 postLoad 事件是否携带一个已经构造的实体对象(带有所有 VO)?因为那样的话对我来说就没用了。
最好的问候, 棘状体
解决方法
是的,您可以像这样在您的 config/packages/doctrine.yaml
中注册新的 hydrator:
doctrine:
dbal: ...
orm:
hydrators:
CustomEntityHydrator: 'App\ORM\Hydrator\CustomEntityHydrator'
...
mapping: ...
...
然后您可以在查询中使用它,如下所示:
public function findCustomEntities(): array
{
return $this->createQueryBuilder('c')
...your query logic...
->getResult('CustomEntityHydrator');
}
请注意,您只能指定要用于根实体的水合器。如果您获取关联实体,您最终可能会遇到更复杂且难以调试的设置。
相反,您可以考虑仅在实体的接口中处理值对象 (VO)。换句话说,字段是标量值,但您的方法参数和返回值是 VO。
这是一个示例,该实体具有 Uuid 类型的 id、位置(某些数字标识符)、状态(例如三元真/假/空)。这些只是为了展示如何处理不同类型的值对象:
/**
* @ORM\Entity()
*/
class CustomEntity
{
/**
* @ORM\Id()
* @ORM\Column(type="string",length=64)
*/
private string $id;
/**
* @ORM\Column(type="int")
*/
private int $location;
/**
* @ORM\Column(type="bool,nullable=true)
*/
private bool $status;
private function __construct(Uuid $id,Location $location,Status $status)
{
$this->id = (string) $id;
$this->location = $location->getValue();
$this->status = $status->get();
}
public static function new(Location $location,Status $status): self
{
return new self(Uuid::v4(),$location,$status);
}
public function getId(): Uuid
{
return Uuid::fromString($this->id);
}
public function getLocation(): Location
{
return new Location($this->location);
}
public function activate(): void
{
$this->status = true;
}
public function deactivate(): void
{
$this->status = false;
}
public function isActive(): bool
{
$this->status === true;
}
public function isInactive(): bool
{
$this->status === false;
}
public function isUninitialized(): bool
{
$this->status === null;
}
public function getStatus(): Status
{
if ($this->status === null) {
return new NullStatus();
}
if ($this->status === true) {
return new ActiveStatus();
}
return new InactiveStatus();
}
}
如您所见,您可以将 new()
替换为公共构造函数。它与二传手类似。我有时甚至在构造函数中为此使用(私有)setter。如果您使用多种方法在内部设置值,则在状态的情况下,您甚至不需要设置器。同样,在某些情况下,您可能希望返回标量值而不是 VO(或者相反,如状态 getter 和 issers 所示)。
关键是,您的实体从外部看起来好像会使用您的 VO,但在内部它已经切换到更适合 Doctrine ORM 的表示。您甚至可以将其与使用 VO 和自定义类型混合使用,例如对于 UUID。你只需要小心,当你的 VO 需要更多的信息来构建而不是你想存储在数据库中时,例如如果我们示例中的数字位置在创建过程中也使用区域设置,那么我们需要存储它(这很有意义,因为它似乎与数字 id 相关)或者我们必须在实体中对其进行硬编码或添加一个抽象上面,可以访问区域设置,在这种情况下,您的实体可能不会返回位置或至少不会返回 LocalizedLocation。
您可能还想考虑不要为实体中的每个财产都设置 VO。虽然它肯定会有所帮助,例如将电子邮件包装到自定义 VO 中以确保有效性,而不仅仅是字符串的类型提示,对于像(用户的)名称这样的通用内容可能不太有用,这对于它接受的字符串应该非常宽松,因为有一个广泛的各种名称。使用上面的方法,您可以在稍后轻松引入 VO,通过为 VO 添加新的 getter、更改 new()
或任何其他改变您的属性的方法,然后不必更改下面数据模型中的任何内容(除非有是对值的表示方式的更剧烈的变化)。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。