Symfony 4:在同一个树枝模板中呈现 OneToMany 关系实体的 DRY 和高性能方式

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

我需要在同一个树枝模板中呈现两个关系实体 SalarieContrat 的一些属性,基本上来自所有 Salarie 记录但仅来自一个特定的 Contrat 附加到每个 Salarie.

受薪实体

namespace App\Entity;

class Salarie
{
// ...

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

合同实体

namespace App\Entity;

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

工资控制器

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

乍一看,我认为使用 Salarie 存储库中的 自定义查询 会很简单,但我一直在与连接、子查询和其他东西作斗争。这是一个 pure Twig working 解决方案,但它 根本不是 DRY,因为我必须为每个 属性 重复它,并且我敢打赌它也有一个 性能命中 因为当我只需要一些时我正在查询所有合同...

<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 %}

根据@msg 建议编辑

我还尝试了 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();
}

然后在 Twig 中我可以 {{ dump(salarie.lastContrat) }} returns 预期的对象。

但是无法从那里获取属性。 {{ salarie.lastContrat.someProperty }} 无效。

不得不看 {{ salarie.lastContrat }} 打印什么 Contrat __toString 方法 returns.

我不会公开更多的尝试,所以请问我的问题是:如何从上面渲染属性值 getLastContrat() 以及什么应该是最干燥和最高效的方法来实现这个?

无需循环,您只需提取第一个元素即可:

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

Criteria returns a Collection 即使只有一个元素,所以你可以应用与上述相同的原则。

尽管您也可以在将结果传递给 twig 之前在控制器中提取结果,并将它们作为实体而不是集合传递。在上面的存储库中:

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

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

您可以做的另一件事是将另一个变量传递给模板,其中包含您要呈现的相关对象(在您的情况下是最后一个合同)。因此,在您的控制器中,您首先获取 Salarie 对象,然后获取所需的合同。它不是最 DRY 的解决方案,但如果你没有更多的用例 need/want 来重用一段代码,你就不能真正应用 DRY。因此,这种方法很好,因为您不需要在您的实体上使用任何标准逻辑,而且它非常高效,因为您只获取您需要的内容。

如果你没有更多可能重复使用代码的用例,那么不要过度优化,等待情况发生,然后你可以寻找一种方式来分享你的代码,基于实际需要,而不是相信 :)

干杯!