Symfony 5:根据 parent 属性 值设置 属性
Symfony 5 : setting a property based on a parent property value
在 Symfony 5 中,假设我们有 3 个这样链接的实体:
Foo
是具有 Bar
和 child 的实体。 Foo
作为一个 属性 称为 fooProperty
。
Bar
将 Foo
作为 parent,将 Baz
作为 child
当然,Baz
的 Bar 为 parent。 Baz
有一个 属性 叫做 bazProperty
。
假设 bazProperty
的值取决于 fooProperty
的值。我的第一个想法是在 baz
实体 class 中引用 foo
实体:
function setBazProperty($value) {
if ($this->getBar()->getFoo()->getFooProperty > 0) {
$this->bazProperty = $value;
} else {
$this->bazProperty = 0;
}
}
但这会发生很多 sql 查询,因为 Doctrine 会首先要求获得 Bar
实体,然后是 Foo
实体。
所以我想象通过存储库 class 中管理的唯一查询访问 Foo
实体。
但是,由于 the separation of concern,我不会在 Baz
实体中注入存储库,而是使用服务。
所以我在构造函数中创建了一个带有两个参数的 BazService
:
public function __construct(Baz $baz, BazRepository $bazRepository)
{
$this->baz = $baz;
$this->bazRepository= $bazRepository;
}
在此服务中,我还添加了一个获取 Foo
实体的方法:
public function getFoo()
{
return $this->bazRepository->getFoo($this->baz);
}
最后,在控制器中,现在我想获得 Foo
实体:
$bazService = new BazService($baz);
$foo = $bazService->getFoo();
这是我的问题:
我无法在控制器中初始化 bazService
。构造函数要求 2 个参数(实体和存储库),我只想提供实体并自动注入存储库 class。
我试图将它添加到 serices.yaml 但没有成功(可能是因为我没有在我的控制器的构造函数中实例化 bazService
):
App\Service\BazService:
arguments:
$bazRepository: App\Repository\BazRepository
还有其他解决办法吗?如何在服务 class 中以不同方式注入实体 class ?
- 在设置 属性 时使用服务太复杂是推荐的解决方案吗?一些文章 (here, here and here) 建议在实体 class 内的方法变得更加复杂并且需要外部实体或存储库时使用服务。但也许有更简单的解决方案...
关注点分离是恕我直言,值得关注的正确论点。有一些方法可供选择,这在很大程度上取决于您检索实体的方式。然而,在我看来,一个实体的关注点不是从数据库中获取一些其他实体数据,而是存储库或 可能 控制器的关注点。那么让我们看看如何做到这一点...
一种方法是自动检索父实体。根据您的用例,您通常可以这样做(通过 fetch="EAGER"
- 请参阅:@ManyToOne / @OneToOne),否则您可以实现一个特殊的存储库函数,以获取其他实体。如果您的实体始终每个最多只有一个父实体,这绝对可以将查询数量从 3 减少到 1,因为可以同时检索父实体和父实体的父实体。
// in BazRepository
public function getWithParents($id) {
$qb = $this->createQueryBuilder('baz');
$qb->leftJoin('baz.bar', 'bar')
->addSelect('bar')
->leftJoin('bar.foo', 'foo')
->addSelect('foo')
->where('baz.id = :id')
->setParameter('id', $id);
return $qb->getQuery()->getOneOrNullResult();
}
如果子实体访问父实体,它应该只使用缓存中的实体并避免第二次查询(来源:https://symfonycasts.com/screencast/doctrine-relations/join-n-plus-one)
如果拥有实体已经“太多”,您可以(再次)创建一个自定义存储库方法来稍微作弊,该方法不仅获取 Baz 实体,还获取 Foo.fooProperty 值并将其设置为Baz 实体上的 virtual/temporary 属性。
// in BazRepository
public function getWithFooProperty(int $id) {
$qb = $this->createQueryBuilder('baz');
$qb->leftJoin('baz.bar', 'bar')
->lefTJoin('bar.foo', 'foo')
->select('foo.fooProperty as fooProperty')
->where('baz.id = :id')
->setParameter('id', $id);
$result = $qb->getQuery()->getResult(); // should be an array with an array with two keys, but I might be wrong
if(count($result) == 0) {
return null;
}
$baz = $row[0][0];
$baz->fooProperty = $row[0][1];
return $baz;
}
(免责声明:请在此处检查 $result,以查看访问是否正确)
您现在可以在 Baz:
中访问它
function getFooProperty() {
if(isset($this->fooProperty)) {
return $this->fooProperty;
} else {
// fallback, in case entity was fetched by another repository method
return $this->getBar()->getFoo()->getFooProperty();
}
}
在 Symfony 5 中,假设我们有 3 个这样链接的实体:
Foo
是具有Bar
和 child 的实体。Foo
作为一个 属性 称为fooProperty
。Bar
将Foo
作为 parent,将Baz
作为 child
当然,Baz
的 Bar 为 parent。Baz
有一个 属性 叫做bazProperty
。
假设 bazProperty
的值取决于 fooProperty
的值。我的第一个想法是在 baz
实体 class 中引用 foo
实体:
function setBazProperty($value) {
if ($this->getBar()->getFoo()->getFooProperty > 0) {
$this->bazProperty = $value;
} else {
$this->bazProperty = 0;
}
}
但这会发生很多 sql 查询,因为 Doctrine 会首先要求获得 Bar
实体,然后是 Foo
实体。
所以我想象通过存储库 class 中管理的唯一查询访问 Foo
实体。
但是,由于 the separation of concern,我不会在 Baz
实体中注入存储库,而是使用服务。
所以我在构造函数中创建了一个带有两个参数的 BazService
:
public function __construct(Baz $baz, BazRepository $bazRepository)
{
$this->baz = $baz;
$this->bazRepository= $bazRepository;
}
在此服务中,我还添加了一个获取 Foo
实体的方法:
public function getFoo()
{
return $this->bazRepository->getFoo($this->baz);
}
最后,在控制器中,现在我想获得 Foo
实体:
$bazService = new BazService($baz);
$foo = $bazService->getFoo();
这是我的问题:
我无法在控制器中初始化
bazService
。构造函数要求 2 个参数(实体和存储库),我只想提供实体并自动注入存储库 class。 我试图将它添加到 serices.yaml 但没有成功(可能是因为我没有在我的控制器的构造函数中实例化bazService
):App\Service\BazService: arguments: $bazRepository: App\Repository\BazRepository
还有其他解决办法吗?如何在服务 class 中以不同方式注入实体 class ?
- 在设置 属性 时使用服务太复杂是推荐的解决方案吗?一些文章 (here, here and here) 建议在实体 class 内的方法变得更加复杂并且需要外部实体或存储库时使用服务。但也许有更简单的解决方案...
关注点分离是恕我直言,值得关注的正确论点。有一些方法可供选择,这在很大程度上取决于您检索实体的方式。然而,在我看来,一个实体的关注点不是从数据库中获取一些其他实体数据,而是存储库或 可能 控制器的关注点。那么让我们看看如何做到这一点...
一种方法是自动检索父实体。根据您的用例,您通常可以这样做(通过 fetch="EAGER"
- 请参阅:@ManyToOne / @OneToOne),否则您可以实现一个特殊的存储库函数,以获取其他实体。如果您的实体始终每个最多只有一个父实体,这绝对可以将查询数量从 3 减少到 1,因为可以同时检索父实体和父实体的父实体。
// in BazRepository
public function getWithParents($id) {
$qb = $this->createQueryBuilder('baz');
$qb->leftJoin('baz.bar', 'bar')
->addSelect('bar')
->leftJoin('bar.foo', 'foo')
->addSelect('foo')
->where('baz.id = :id')
->setParameter('id', $id);
return $qb->getQuery()->getOneOrNullResult();
}
如果子实体访问父实体,它应该只使用缓存中的实体并避免第二次查询(来源:https://symfonycasts.com/screencast/doctrine-relations/join-n-plus-one)
如果拥有实体已经“太多”,您可以(再次)创建一个自定义存储库方法来稍微作弊,该方法不仅获取 Baz 实体,还获取 Foo.fooProperty 值并将其设置为Baz 实体上的 virtual/temporary 属性。
// in BazRepository
public function getWithFooProperty(int $id) {
$qb = $this->createQueryBuilder('baz');
$qb->leftJoin('baz.bar', 'bar')
->lefTJoin('bar.foo', 'foo')
->select('foo.fooProperty as fooProperty')
->where('baz.id = :id')
->setParameter('id', $id);
$result = $qb->getQuery()->getResult(); // should be an array with an array with two keys, but I might be wrong
if(count($result) == 0) {
return null;
}
$baz = $row[0][0];
$baz->fooProperty = $row[0][1];
return $baz;
}
(免责声明:请在此处检查 $result,以查看访问是否正确)
您现在可以在 Baz:
中访问它function getFooProperty() {
if(isset($this->fooProperty)) {
return $this->fooProperty;
} else {
// fallback, in case entity was fetched by another repository method
return $this->getBar()->getFoo()->getFooProperty();
}
}