Buy
Buy
This tutorial has a new version, check it out!

Entities, Twig and the Magic dot Syntax

Let's finally make this page real with a template. Return $this->render('genus/list.html.twig') and pass it a genuses variable:

... lines 1 - 11
class GenusController extends Controller
{
... lines 14 - 33
public function listAction()
{
$em = $this->getDoctrine()->getManager();
$genuses = $em->getRepository('AppBundle:Genus')
->findAll();
return $this->render('genus/list.html.twig', [
'genuses' => $genuses
]);
}
... lines 45 - 89
}

You know what to do from here: in app/Resources/views/genus, create the new list.html.twig template. Don't forget to extend base.html.twig and then override the body block. I'll paste a table below to get us started:

{% extends 'base.html.twig' %}
{% block body %}
<table class="table table-striped">
<thead>
<tr>
<th>Genus</th>
<th># of species</th>
</tr>
</thead>
<tbody>
... lines 12 - 14
</tbody>
</table>
{% endblock %}

Since genuses is an array, loop over it with {% for genus in genuses %} and add the {% endfor %}. Next, just dump out genus inside:

... lines 1 - 10
<tbody>
{% for genus in genuses %}
{{ dump(genus) }}
{% endfor %}
</tbody>
... lines 16 - 18

Looks like a good start - try it out!. Ok cool - this dumps out 4 Genus objects. Open up the tr. Bring this to life with a td that prints {{ genus.name }} and another that prints {{ genus.speciesCount }}. And hey, we're getting autocompletion, that's kind of nice:

... lines 1 - 10
<tbody>
{% for genus in genuses %}
<tr>
<td>{{ genus.name }}</td>
<td>{{ genus.speciesCount }}</td>
</tr>
{% endfor %}
</tbody>
... lines 19 - 21

The Magic Twig "." Notation

Refresh! Easy - it looks exactly how we want it. But wait a second... something cool just happened in the background. We printed genus.name... but name is a private property - so we should not be able to access it directly. How is this working?

... lines 1 - 10
class Genus
{
... lines 13 - 22
private $name;
... lines 24 - 39
public function getName()
{
return $this->name;
}
... lines 44 - 79
}

This is Twig to the rescue! Behind the scenes, Twig noticed that name was private and called getName() instead. And it does the same thing with genus.speciesCount:

... lines 1 - 10
class Genus
{
... lines 13 - 32
private $speciesCount;
... lines 34 - 59
public function getSpeciesCount()
{
return $this->speciesCount;
}
... lines 64 - 79
}

Twig is smart enough to figure out how to access the data - and this lets us keep the template simple.

With that in mind, I have a challenge! Add a third column to the table called "Last Updated":

... lines 1 - 4
<thead>
<tr>
<th>Genus</th>
<th># of species</th>
<th>Last updated</th>
</tr>
</thead>
... lines 12 - 23

This won't work yet, but what I I want to be able to say is {{ genus.updatedAt }}. If this existed and returned a DateTime object, we could pipe it through the built-in Twig date filter to format it:

... lines 1 - 11
<tbody>
{% for genus in genuses %}
<tr>
<td>{{ genus.name }}</td>
<td>{{ genus.speciesCount }}</td>
<td>{{ genus.updatedAt|date('Y-m-d') }}</td>
</tr>
{% endfor %}
</tbody>
... lines 21 - 23

But this won't work - there is not an updatedAt property. We'll add one later, but we're stuck right now.

Wait! We can fake it! Add a public function getUpdatedAt() and return a random DateTime object:

... lines 1 - 10
class Genus
{
... lines 13 - 79
public function getUpdatedAt()
{
return new \DateTime('-'.rand(0, 100).' days');
}
}

Try that out. It works! Twig doesn't care that there is no updatedAt property - it happily calls the getter function. Twig, you're awesome.

Leave a comment!

  • 2018-03-22 Diego Aguiar

    Ohh, ok, I get it. So in your case, you cannot trust in your proxies... Hmm, I can think of a couple of options:

    1) You could create a custom repository method for fetching your customers, so they come with their "location" field already hydrated, i.e. add a join to the query. Maybe this option is not the best choice because you could end up printing a location that doesn't exist anymore.

    2) Create a Twig's function that receives a customer and returns an array of its location objects, so whenever you need to print the locations of a given customer you can make use of that function.

  • 2018-03-21 Geoff Maddock

    It's a pretty simple and common case - I'm displaying a list of objects, in this case, "Customers". I'm using a twig template to display a table, and one column of the table tries to display the customer's location:


    {% set location = customer.location %}
    {{ attribute(customer, location) is defined ? location : '' }}

    The "Location" relation is set in the "Customer" table (customer.location, which is actually stored as customer.location_id), but in this case, the location_id value doesn't match an entry in the "Location" table. As I mentioned above, this is due to having to do a partial database restore, so that specific Location object is missing. However I'd like to be able to ignore that and still render the page for this or any other similar case.

  • 2018-03-21 Diego Aguiar

    Hey Geoff Maddock

    Interesting question, could you give me a bit more of context. How do you try to access to that child object? Is it come from a request (executing a controller's action)?

    Cheers!

  • 2018-03-21 Geoff Maddock

    Is there a best practice for catching EntityNotFoundExceptions gracefully? In our app, there are times when, due to a limited database restore, we have parent entities that have a related object, but that child object is not in the database. When accessing the parent entity, it throws a 500 error:

    Error: Method Proxies\__CG__\App\Entity\Location::__toString() must not throw an exception, caught Doctrine\ORM\EntityNotFoundException: Entity of type 'App\Entity\Location' for IDs id(265)

    Ideally we'd like to be able to generically handle these and instread of throwing at 500, just return a "Relation missing" string of something similar.

  • 2018-02-14 Victor Bocharsky

    Hey GuitarSessions!

    Good question! You can achieve it by installing https://github.com/beberlei... which has support of DAYNAME(), see example of how to enable it in your symfony config: https://github.com/beberlei...

    Cheers!

  • 2018-02-13 GuitarSessions!

    I want to use this sql function to get the current day: ('DAYNAME(CURRENT_DATE)'); It will return Monday, Tuesday etc. Is there a function like this in query builder?

  • 2017-11-13 Diego Aguiar

    Hey Suraj Sharma

    Thanks for sharing it!
    BTW, you can use any other syntax for your template path, i.e. '@AppBundle/path/to/template.html.twig'

    Cheers!

  • 2017-11-12 Suraj Sharma

    To get auto-completion in list.html.twig I did this on listAction in GenusController

    return $this->render(':genus:list.html.twig', [
    'genuses' => $genuses
    ]);

  • 2017-05-08 Victor Bocharsky

    Hey maxii123 ,

    I'm not sure Symfony Plugin reads the annotation in twig templates. Do you use the latest PhpStorm version? Check that Symfony Plugin is enabled and has necessary settings. Also, in your controller, you need to have PhpStorm autocompletion for that entity and you should pass this entity variable explicitly in render() function like:


    $this->render('path-to-your-template.html.twig', [
    'entity' => $entity, // Ensure you have autocompletion for $entity in this spot.
    ]);

    And the last but not least advise: try to restart PhpStorm, it helps in most cases ;)

    Cheers!

  • 2017-05-06 maxii123

    Even trying the annotation below

    {# @controller AppBundle:Genus:list #}

    I am not getting twig entity completion in my own project. Suggestions?

  • 2017-05-04 Nobuyuki Fujioka

    Ok, my bad. Thank you.

  • 2017-05-04 Victor Bocharsky

    Hey Fujioka,

    I see what the problem is :) You need to use concatenation instead of comma - all this should be as a first argument for DateTime constructor. So the correct example is: return new \DateTime('-'.rand(0,100).' days');

    Cheers!

  • 2017-05-04 Nobuyuki Fujioka

    Hi, when I followed the tutorial, I get the following error.
    Type error: DateTime::__construct() expects parameter 2 to be DateTimeZone, string given

    My symfony does not seem to like
    public function getUpdatedAt()
    {
    return new \DateTime('-'.rand(0,100),'days');
    }

    Any idea? Why this does not work on mine?

  • 2016-04-19 weaverryan

    Hey Hans!

    Ah, that's very interesting - you're hinting your template - very cool! In my experience, the variables work sometimes, but not all of the time - I'm not sure exactly what logic PhpStorm is using to "guess" the controller for the template. I assume it's based on naming conventions (GenusController::listAction -> genus/list.html.twig), but even when I follow that, I don't think I *always* get auto-completion.

    So, you're not missing something - but I am surprised it doesn't work at all for you without this!

  • 2016-04-18 Hans Nieuwenhuis

    The auto-completion of the variables in the twigfile in PhpStorm only works for me if I add the next line at the top of the twig file

    {# @controller AppBundle:Genus:list #}

    Or have I missed something ?