Entity objects in Twig
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWe now have a Question
object inside our controller. And at the bottom, we render a template. What we need to do is pass that Question
object into the template and use it on the page to print the name and other info.
Remove the dd()
, leave the $answers
- we'll keep those hardcoded for now because we don't have an Answer
entity yet - and get rid of the hardcoded $question
, and $questionText
.
// ... lines 1 - 12 | |
class QuestionController extends AbstractController | |
{ | |
// ... lines 15 - 70 | |
public function show($slug, MarkdownHelper $markdownHelper, EntityManagerInterface $entityManager) | |
{ | |
// ... lines 73 - 79 | |
if (!$question) { | |
throw $this->createNotFoundException(sprintf('no question found for slug "%s"', $slug)); | |
} | |
$answers = [ | |
'Make sure your cat is sitting `purrrfectly` still ?', | |
'Honestly, I like furry shoes better than MY cat', | |
'Maybe... try saying the spell backwards?', | |
]; | |
// ... lines 89 - 93 | |
} | |
} |
Instead pass a question
variable to Twig set to the Question
object.
// ... lines 1 - 12 | |
class QuestionController extends AbstractController | |
{ | |
// ... lines 15 - 70 | |
public function show($slug, MarkdownHelper $markdownHelper, EntityManagerInterface $entityManager) | |
{ | |
// ... lines 73 - 79 | |
if (!$question) { | |
throw $this->createNotFoundException(sprintf('no question found for slug "%s"', $slug)); | |
} | |
$answers = [ | |
'Make sure your cat is sitting `purrrfectly` still ?', | |
'Honestly, I like furry shoes better than MY cat', | |
'Maybe... try saying the spell backwards?', | |
]; | |
return $this->render('question/show.html.twig', [ | |
'question' => $question, | |
'answers' => $answers, | |
]); | |
} | |
} |
Twig's Smart . Syntax
Let's go find the template: templates/question/show.html.twig
. The question
variable is no longer a string: it's now an object. So... how do we render an object? Because the Question
class has a name
property, we can say question.name
. It even auto-completes it for me! That doesn't always work in Twig, but it's nice when it does.
// ... lines 1 - 2 | |
{% block title %}Question: {{ question.name }}{% endblock %} | |
{% block body %} | |
<div class="container"> | |
<div class="row"> | |
<div class="col-12"> | |
<h2 class="my-4">Question:</h2> | |
<div style="box-shadow: 2px 3px 9px 4px rgba(0,0,0,0.04);"> | |
<div class="q-container-show p-4"> | |
<div class="row"> | |
// ... lines 13 - 27 | |
<div class="col"> | |
<h1 class="q-title-show">{{ question.name }}</h1> | |
<div class="q-display p-3"> | |
<i class="fa fa-quote-left mr-3"></i> | |
<p class="d-inline">{{ question.question }}</p> | |
<p class="pt-4"><strong>--Tisha</strong></p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
// ... lines 41 - 68 | |
</div> | |
{% endblock %} |
Below... here's another one - question.name
and questionText
is now question.question
.
// ... lines 1 - 2 | |
{% block title %}Question: {{ question.name }}{% endblock %} | |
{% block body %} | |
<div class="container"> | |
<div class="row"> | |
<div class="col-12"> | |
<h2 class="my-4">Question:</h2> | |
<div style="box-shadow: 2px 3px 9px 4px rgba(0,0,0,0.04);"> | |
<div class="q-container-show p-4"> | |
<div class="row"> | |
// ... lines 13 - 27 | |
<div class="col"> | |
<h1 class="q-title-show">{{ question.name }}</h1> | |
<div class="q-display p-3"> | |
<i class="fa fa-quote-left mr-3"></i> | |
<p class="d-inline">{{ question.question }}</p> | |
<p class="pt-4"><strong>--Tisha</strong></p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
// ... lines 41 - 68 | |
</div> | |
{% endblock %} |
I think that's it! Testing time! Move over, go back to the real question slug and... there it is! We have a real name and real question text. This date is still hard coded, but we'll fix that soon.
Now, some of you might be thinking:
Um... how the heck did that work?
We said question.name
... which makes it look like it's reading the name property. But... if you look at the name
property inside of the Question
entity... it's private! That means we can't access the name
property directly. What's going on?
// ... lines 1 - 10 | |
class Question | |
{ | |
// ... lines 13 - 22 | |
private $name; | |
// ... lines 24 - 91 | |
} |
We're witnessing some Twig magic. In reality, when we say question.name
, Twig first does look to see if the name
property exists and is public. If it were public, Twig would use it. But since it's not, Twig then tries to call a getName()
method. Yep, we write question.name
, but, behind the scenes, Twig is smart enough to call getName()
.
// ... lines 1 - 10 | |
class Question | |
{ | |
// ... lines 13 - 22 | |
private $name; | |
// ... lines 24 - 44 | |
public function getName(): ?string | |
{ | |
return $this->name; | |
} | |
// ... lines 49 - 91 | |
} |
I love this: it means you can run around saying question.name
in your template and not really worry about whether there's a getter method or not. It's especially friendly to non-PHP frontend devs.
If you wanted to actually call a method - like getName()
- that is allowed, but it's usually not necessary.
The one thing that we did lose is that, originally, the question text was being parsed through markdown. We can fix that really easily by using the parse_markdown
filter that we created in the last tutorial.
// ... lines 1 - 4 | |
{% block body %} | |
<div class="container"> | |
<div class="row"> | |
<div class="col-12"> | |
<h2 class="my-4">Question:</h2> | |
<div style="box-shadow: 2px 3px 9px 4px rgba(0,0,0,0.04);"> | |
<div class="q-container-show p-4"> | |
<div class="row"> | |
// ... lines 13 - 27 | |
<div class="col"> | |
<h1 class="q-title-show">{{ question.name }}</h1> | |
<div class="q-display p-3"> | |
<i class="fa fa-quote-left mr-3"></i> | |
<p class="d-inline">{{ question.question|parse_markdown }}</p> | |
<p class="pt-4"><strong>--Tisha</strong></p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
// ... lines 41 - 68 | |
</div> | |
{% endblock %} |
Refresh and... it works.
The Doctrine Web Debug Toolbar
You may not have noticed, but near the middle of the web debug toolbar, there's a little database icon that says 1 database query. And we can click the icon to jump into the profiler and... see the exact query! If this page made multiple queries, you would see all of them here.
If you ever want to debug a query directly, click "View runnable query" to get a version that you can copy.
Seeing the Profiler for AJAX Requests
Now, here's a challenge: how could we see the INSERT
query that's made when we go to /questions/new
? This did just make that query... but because we're not rendering HTML, this doesn't have a web debug toolbar. The same problem happens whenever you make an AJAX call.
So... are we out of luck? Nah - we can use a trick. Go to /_profiler
to find a list of the most recent requests we've made. Here's the one we just made to /questions/new
. Click the little token string on the right to jump into the full profiler for that request! Go to the "Doctrine" tab and... bam! Cool! It even wraps the INSERT in a transaction.
Remember this trick the next time you want to see database queries, a rendered version of an error, or something else for an AJAX request.
Go back a few times to the question show page. The last piece of question data that's hardcoded is this "asked 10 minutes ago" text. Search for it in the template... there it is, line 18.
Let's make this dynamic... but, not just by printing some boring date like "July 10th at 10:30 EST". Yuck. Let's print a much-friendlier "10 minutes ago" type of message next.
noob question about docker. I set up the database service with mariadb, yadda yadda, turned off the machine, turned it back on today and was quite surprised that the
question
table we created in the migrations chapter was still there, even though mydocker-compose.yaml
doesn't say anything about volumes. so let's see -- the state of the database persists in the docker image after you shut down the container? so, you only lose your db data if you <i>destroy</i> the image? If so, that seems reasonable enough!