Doctrine 2 自定义实体加载和持久化
Doctrine 2 custom entity loading and persisting
是否有可能在每个实体的基础上实施 Doctrine 2 中的自定义水合作用和持久性?
Doctrine 2 在值对象(例如集合和 ID)方面有一些主要限制。我想知道是否可以使用自定义机制(或实现)来从对象属性映射到数据库(加载和持久化)。
我知道有一些可能性可以“解决”这个问题,但我喜欢 none 个:
- 假实体需要在将持久层泄漏到域对象中的实体中进行适当处理
- 真实的实体需要更多的持久性工作(更多的存储库和更复杂的处理)
- Embaddables 具有上述限制
- 带有序列化的自定义 DBAL 类型使得查询某些值变得不可能或至少非常慢
我知道学说中有生命周期事件可能有用。我无法确定 postLoad 事件是否携带一个已经构建的实体对象(包含所有 VO)?因为那样的话对我来说就没用了。
此致,
水母
是的,您可以像这样在 config/packages/doctrine.yaml
中注册新的保湿器:
doctrine:
dbal: ...
orm:
hydrators:
CustomEntityHydrator: 'App\ORM\Hydrator\CustomEntityHydrator'
...
mapping: ...
...
然后您可以像这样在查询中使用它:
public function findCustomEntities(): array
{
return $this->createQueryBuilder('c')
...your query logic...
->getResult('CustomEntityHydrator');
}
请注意,您只能指定要用于根实体的水化器。如果您获取关联的实体,您最终可能会得到一个更复杂且难以调试的设置。
相反,您可以考虑仅在实体的界面中处理值对象 (VO)。换句话说,字段是标量值,但您的方法参数和 return 值是 VO。
这是一个实体示例,该实体具有 Uuid 类型的 ID、位置(一些数字标识符)、状态(例如三进制 true/false/null)。这些只是为了展示如何处理不同类型的值对象:
/**
* @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()
替换为 public 构造函数。它与 setter 的工作方式类似。我有时甚至在构造函数中为此使用(私有)设置器。在状态的情况下,如果您使用多种方法在内部设置值,则您甚至不需要设置器。同样,在某些情况下,您可能希望 return 标量值而不是 VO(或者相反,如状态 getter 和 issers 所示)。
重点是,您的实体从外部看起来好像会使用您的 VO,但在内部它已经切换到更适合 Doctrine ORM 的表示。您甚至可以将其与使用 VO 和自定义类型混合使用,例如对于 UUID。你只需要小心,当你的 VO 需要更多的信息来构建而不是你想存储在数据库中时,例如如果我们示例中的数字位置在创建期间也将使用区域设置,那么我们将需要存储它(这很有意义,因为它似乎与数字 ID 相关)或者我们必须在实体中对其进行硬编码或添加抽象上面,它可以访问语言环境,在这种情况下,您的实体可能不会 return Location 或至少不是 LocalizedLocation。
您可能还想考虑不为实体中的每个 属性 设置 VO。虽然它肯定会有帮助,例如将电子邮件包装到自定义 VO 中以确保有效性,而不仅仅是字符串的类型提示,对于像(用户的)名称这样通用的东西,它可能不太有用,它应该对它接受的字符串非常宽容,因为有很多各种各样的名字。使用上面的方法,您可以稍后轻松地引入一个 VO,方法是为 VO 添加一个新的 getter,更改 new()
或任何其他改变您的 属性 的方法,然后不必更改任何内容在下面的数据模型中(除非值的表示方式发生更剧烈的变化)。
是否有可能在每个实体的基础上实施 Doctrine 2 中的自定义水合作用和持久性?
Doctrine 2 在值对象(例如集合和 ID)方面有一些主要限制。我想知道是否可以使用自定义机制(或实现)来从对象属性映射到数据库(加载和持久化)。
我知道有一些可能性可以“解决”这个问题,但我喜欢 none 个:
- 假实体需要在将持久层泄漏到域对象中的实体中进行适当处理
- 真实的实体需要更多的持久性工作(更多的存储库和更复杂的处理)
- Embaddables 具有上述限制
- 带有序列化的自定义 DBAL 类型使得查询某些值变得不可能或至少非常慢
我知道学说中有生命周期事件可能有用。我无法确定 postLoad 事件是否携带一个已经构建的实体对象(包含所有 VO)?因为那样的话对我来说就没用了。
此致, 水母
是的,您可以像这样在 config/packages/doctrine.yaml
中注册新的保湿器:
doctrine:
dbal: ...
orm:
hydrators:
CustomEntityHydrator: 'App\ORM\Hydrator\CustomEntityHydrator'
...
mapping: ...
...
然后您可以像这样在查询中使用它:
public function findCustomEntities(): array
{
return $this->createQueryBuilder('c')
...your query logic...
->getResult('CustomEntityHydrator');
}
请注意,您只能指定要用于根实体的水化器。如果您获取关联的实体,您最终可能会得到一个更复杂且难以调试的设置。
相反,您可以考虑仅在实体的界面中处理值对象 (VO)。换句话说,字段是标量值,但您的方法参数和 return 值是 VO。
这是一个实体示例,该实体具有 Uuid 类型的 ID、位置(一些数字标识符)、状态(例如三进制 true/false/null)。这些只是为了展示如何处理不同类型的值对象:
/**
* @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()
替换为 public 构造函数。它与 setter 的工作方式类似。我有时甚至在构造函数中为此使用(私有)设置器。在状态的情况下,如果您使用多种方法在内部设置值,则您甚至不需要设置器。同样,在某些情况下,您可能希望 return 标量值而不是 VO(或者相反,如状态 getter 和 issers 所示)。
重点是,您的实体从外部看起来好像会使用您的 VO,但在内部它已经切换到更适合 Doctrine ORM 的表示。您甚至可以将其与使用 VO 和自定义类型混合使用,例如对于 UUID。你只需要小心,当你的 VO 需要更多的信息来构建而不是你想存储在数据库中时,例如如果我们示例中的数字位置在创建期间也将使用区域设置,那么我们将需要存储它(这很有意义,因为它似乎与数字 ID 相关)或者我们必须在实体中对其进行硬编码或添加抽象上面,它可以访问语言环境,在这种情况下,您的实体可能不会 return Location 或至少不是 LocalizedLocation。
您可能还想考虑不为实体中的每个 属性 设置 VO。虽然它肯定会有帮助,例如将电子邮件包装到自定义 VO 中以确保有效性,而不仅仅是字符串的类型提示,对于像(用户的)名称这样通用的东西,它可能不太有用,它应该对它接受的字符串非常宽容,因为有很多各种各样的名字。使用上面的方法,您可以稍后轻松地引入一个 VO,方法是为 VO 添加一个新的 getter,更改 new()
或任何其他改变您的 属性 的方法,然后不必更改任何内容在下面的数据模型中(除非值的表示方式发生更剧烈的变化)。