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.
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. 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...
<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 %}
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.
But no way to get the properties from there. {{ salarie.lastContrat.someProperty }}
does not work.
Has to see with the fact that {{ salarie.lastContrat }}
prints what Contrat __toString method returns.
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 ?
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.
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();
}