Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Entities, Twig and the Magic dot Syntax

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

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!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}
userVoice