简体   繁体   English

Symfony 4:在同一树枝模板中渲染OneToMany关系实体的DRY和高性能方法

[英]Symfony 4 : DRY and performant way to render OneToMany relation entities in same twig template

I need to render some properties of two relational entities Salarie and Contrat , in a same twig template, basically from all the Salarie records but only from one specific Contrat attached to each Salarie . 我需要在同一树枝模板中渲染两个关系实体SalarieContrat的某些属性,基本上是从所有Salarie记录中渲染出来的,仅是从 附加到每个Salarie的 特定 Contrat中 渲染出来的

Salarie Entity 薪金实体

namespace App\Entity;

class Salarie
{
// ...

/**
     * @ORM\OneToMany(targetEntity="App\Entity\Contrat", mappedBy="salarie")
     * @ORM\OrderBy({"dateDebut" = "DESC"})
     */
    private $contrats;
//...

Contrat Entity 冲突实体

namespace App\Entity;

class Contrat
{
// ...
/**
     * @ORM\ManyToOne(targetEntity="App\Entity\Salarie", inversedBy="contrats")
     * @ORM\JoinColumn(nullable=false)
     */
    private $salarie;
// ...

Salarie controller 工资控制器

class SalarieController extends AbstractController
{
    /**
     * @Route("/", name="salarie_index", methods={"GET"})
     */
    public function index(SalarieRepository $salarieRepository): Response
    {
        return $this->render('salarie/index.html.twig', [
            'salaries' => $salarieRepository->findAll(), //findAllWithLastContrat(),
        ]);
    }

At first glance I thought this would be straightforward with a custom query in Salarie repository , but I've been fighting with joins, subqueries, and other stuff. 乍一看,我认为在Salarie存储库中使用自定义查询会很简单,但是我一直在与联接,子查询和其他东西作斗争。 Here's a pure Twig working solution , but it is not DRY at all, since I have to repeat it for each property , and I bet it has also a performance hit because I'm querying all the Contrat when I need only some... 这是一个纯Twig的工作解决方案,但它根本不是DRY ,因为我必须为每个属性重复它,并且我敢打赌它也会对性能产生影响,因为当我只需要一些Contrat时,我会查询所有...

<tbody class="list">
    {% for salarie in salaries %}
        <tr>
            <td>{% for contrat in salarie.contrats  %}
                  {% if loop.first %}
                    {{ contrat.departement }}
                  {% endif %}
                {% endfor %}
            </td>
            <td>{% for contrat in salarie.contrats  %}
                  {% if loop.first %}
                    {{ contrat.service }}
                  {% endif %}
                {% endfor %} 
            </td>
        </tr>
        <!-- AND SO ON ABOUT 12 TIMES ! -->
    {% endfor %}

EDITED with @msg suggestions 用@msg建议编辑

I also tried a cool Feature from Doctrine ( Criteria ) as explained in Criteria System: Champion Collection Filtering 标准系统:冠军收集过滤中所述,我还尝试了从教义( 标准 )获得的出色功能

public function getLastContrat()
{
    $criteria = Criteria::create()
      ->orderBy(['dateDebut' => 'DESC']);
      ->setMaxResults(1);

    return $this->contrats->matching($criteria)->current();
}

then in Twig I can {{ dump(salarie.lastContrat) }} returns the expected object. 然后在Twig中,我可以{{ dump(salarie.lastContrat) }}返回期望的对象。

在此处输入图片说明

But no way to get the properties from there. 但是没有办法从那里获取属性。 {{ salarie.lastContrat.someProperty }} does not work. {{ salarie.lastContrat.someProperty }}不起作用。

salarie.lastContrat.someProperty

Has to see with the fact that {{ salarie.lastContrat }} prints what Contrat __toString method returns. 必须看到{{ salarie.lastContrat }}打印出Contrat __toString方法返回的事实。

I won't expose more tries, so please my question is : How to render the properties values from above getLastContrat() and what should be the most DRY and performant way to achieve this ? 我不会公开更多尝试,所以请问我的问题是: 如何从getLastContrat()上方呈现属性值,什么是实现这一目标的最干和最有效的方法?

Instead of looping, you just can extract the first element: 除了循环,您还可以提取第一个元素:

{% if not empty salarie.contrats %}
    {% set contrat = salarie.contrats[0] %}
    {# you can also use salarie.contrats|first #}
    {{ contrat.departement }}
{% endif %}

Criteria returns a Collection even if there's just one element, so you can apply the same principle as above. 即使只有一个元素, Criteria也会返回一个Collection ,因此您可以应用与上述相同的原理。

Although you could also extract the results in your controller before passing them to twig and pass them as Entities instead of collections. 尽管您也可以在将结果传递给树枝之前将结果提取到控制器中,然后将它们作为实体而不是集合进行传递。 In your repository above: 在上面的存储库中:

/**
 * @returns Contrat|null
 */
public function getLastContrat()
{
    $criteria = Criteria::create()
      ->orderBy(['dateDebut' => 'DESC'])
      ->setMaxResults(1);

    return $this->contrats->matching($criteria)->first();
}

another thing you can do is to pass into the template another variable with the related object that you want to render (last contrat in your case). 您可以做的另一件事是将要呈现的相关对象的另一个变量传递到模板中(本例中的最后一项)。 So, in your controller, you first fetch the Salarie object and then fetch the desired contrat. 因此,在控制器中,您首先要获取Salarie对象,然后获取所需的约束。 It's not the most DRY solution but you can't really apply DRY if you don't have more use cases where you need/want to re-use a piece of code. 它不是最DRY的解决方案,但是如果您没有更多需要/想要重用一段代码的用例,则不能真正应用DRY。 So, this approach is good because you don't need any criteria logic on your entity and it's very performant because you only fetch what you need. 因此,这种方法很好,因为您的实体不需要任何标准逻辑,而且性能很好,因为您只获取所需的信息。

If you don't have more use cases that may re-use the code, then don't over-optimize, wait for the situation to occur, and then you can look for a way to share your code, based on real needs, not in believings :) 如果您没有更多可以重用代码的用例,则不要过度优化,等待情况发生,然后您可以根据实际需求寻找共享代码的方法,不相信:)

Cheers! 干杯!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM