Symfony 4:在同一个树枝模板中呈现 OneToMany 关系实体的 DRY 和高性能方式
Symfony 4 : DRY and performant way to render OneToMany relation entities in same twig template
我需要在同一个树枝模板中呈现两个关系实体 Salarie 和 Contrat 的一些属性,基本上来自所有 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。因此,这种方法很好,因为您不需要在您的实体上使用任何标准逻辑,而且它非常高效,因为您只获取您需要的内容。
如果你没有更多可能重复使用代码的用例,那么不要过度优化,等待情况发生,然后你可以寻找一种方式来分享你的代码,基于实际需要,而不是相信 :)
干杯!
我需要在同一个树枝模板中呈现两个关系实体 Salarie 和 Contrat 的一些属性,基本上来自所有 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。因此,这种方法很好,因为您不需要在您的实体上使用任何标准逻辑,而且它非常高效,因为您只获取您需要的内容。
如果你没有更多可能重复使用代码的用例,那么不要过度优化,等待情况发生,然后你可以寻找一种方式来分享你的代码,基于实际需要,而不是相信 :)
干杯!