【问题标题】:Symfony 5 : setting a property based on a parent property valueSymfony 5:根据父属性值设置属性
【发布时间】:2020-10-11 07:23:45
【问题描述】:

在 Symfony 5 中,假设我们有 3 个这样链接的实体:

  • Foo 是一个以 Bar 为子实体的实体。 Foo 作为一个名为 fooProperty 的属性。
  • BarFoo 作为父级,Baz 作为子级
  • Baz 当然有 Bar 作为父母。 Baz 拥有一个名为 bazProperty 的属性。

假设bazProperty 的值取决于fooProperty 的值。我的第一个想法是在baz 实体类中引用foo 实体:

function setBazProperty($value) {
    if ($this->getBar()->getFoo()->getFooProperty > 0) {
        $this->bazProperty = $value;
    } else {
        $this->bazProperty = 0;
    }
}

但这会发生很多 sql 查询,因为 Doctrine 会首先要求获取 Bar 实体,然后是 Foo 实体。

所以我想通过存储库类中管理的唯一查询来访问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();

这是我的问题:

  1. 我无法在控制器中初始化 bazService。构造函数要求 2 个参数(实体和存储库),我只想提供实体并自动注入存储库类。 我试图在 serices.yaml 中添加它但没有成功(可能是因为我没有在控制器的构造函数中实例化 bazService):

    App\Service\BazService:
        arguments:
            $bazRepository: App\Repository\BazRepository
    

还有其他解决方案吗?如何在服务类中以不同方式注入实体类?

  1. 在设置过于复杂的属性时使用服务是推荐的解决方案吗?一些文章(hereherehere)建议在实体类内部的方法变得更复杂并且需要外部实体或存储库时使用服务。但也许有更轻松的解决方案...

【问题讨论】:

  • 非常自以为是的问题;o/
  • 我同意这个问题有点具体,但我面临一个真正的问题是更新依赖于父属性的属性。我想用最优雅的方案写出干净全面的代码。
  • 附带说明:如果需要创建实体,我发现“服务”有点臭。恕我直言,语法应该是$bazService->getFoo($baz)。然后,在您的控制器中,您可以通过控制器方法签名请求 BazService 并通过依赖注入获取它。
  • 我同意你的看法。在使用服务时将实体作为参数传递似乎不太方便。我还尝试创建一个扩展实体baz 的类bazService。但这在持久化实体时会导致问题,并且不能解决关注点分离问题。

标签: symfony service repository entity


【解决方案1】:

恕我直言,关注点分离是正确的论据。有一些方法可以采用,这在很大程度上取决于您如何检索实体。但是,在我看来,实体的关注点不是从数据库中获取一些其他实体数据,而是存储库或可能控制器的关注点。那么让我们看看如何做到这一点......

一种方法是自动检索父实体/实体。根据您的用例,您通常可以这样做(通过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 实体上的属性。

// 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();
    }
}

【讨论】:

  • 感谢您的回答@Jakumi。所以这意味着我需要先在我的控制器中调用getWithFooProperty,然后再设置bazProperty$baz = $bazRepository->getWithFooProperty($baz->getId()); $baz->setBazProperty($value);
  • @Mulot 我假设您的意思是在控制器方法中?您可以指定用于获取 Baz 的存储库方法。看看symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/…
  • 是的,当然,在控制器中!谢谢您的回答。所以我每次更新bazProperty时都必须先使用getWithFooProperty
  • 不是必须的,但应该,如果你只想有一个选择。您应该始终使用软后备,以防万一您在某个时候忘记它,不希望它的行为有所不同
猜你喜欢
  • 2022-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多