gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Let's make this date dynamic! The field on Question
that we're going to use is $askedAt
, which - remember - might be null. If a Question
hasn't been published yet, then it won't have an askedAt
.
Let's plan for this. In the template, add {% if question.askedAt %}
with an {% else %}
and {% endif %}
... lines 1 - 5 | |
<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"> | |
<div class="col-2 text-center"> | |
<img src="{{ asset('images/tisha.png') }}" width="100" height="100" alt="Tisha avatar"> | |
<div class="mt-3"> | |
<small> | |
{% if question.askedAt %} | |
... lines 18 - 19 | |
{% else %} | |
... line 21 | |
{% endif %} | |
</small> | |
... lines 24 - 29 | |
</div> | |
</div> | |
... lines 32 - 39 | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
... lines 45 - 72 | |
</div> | |
... lines 74 - 75 |
If the question is not published, say (unpublished)
.
... lines 1 - 5 | |
<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"> | |
<div class="col-2 text-center"> | |
<img src="{{ asset('images/tisha.png') }}" width="100" height="100" alt="Tisha avatar"> | |
<div class="mt-3"> | |
<small> | |
{% if question.askedAt %} | |
... lines 18 - 19 | |
{% else %} | |
(unpublished) | |
{% endif %} | |
</small> | |
... lines 24 - 29 | |
</div> | |
</div> | |
... lines 32 - 39 | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
... lines 45 - 72 | |
</div> | |
... lines 74 - 75 |
In a real app, we would probably not allow users to see unpublished questions... we could do that in our controller by checking for this field and saying throw $this->createNotFoundException()
if it's null. But... maybe a user will be able to preview their own unpublished questions. If they did, we'll show unpublished
.
The easiest way to try to print the date would be to say {{ question.askedAt }}
.
... lines 1 - 5 | |
<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"> | |
<div class="col-2 text-center"> | |
<img src="{{ asset('images/tisha.png') }}" width="100" height="100" alt="Tisha avatar"> | |
<div class="mt-3"> | |
<small> | |
{% if question.askedAt %} | |
... line 18 | |
{{ question.askedAt }} | |
{% else %} | |
(unpublished) | |
{% endif %} | |
</small> | |
... lines 24 - 29 | |
</div> | |
</div> | |
... lines 32 - 39 | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
... lines 45 - 72 | |
</div> | |
... lines 74 - 75 |
But... you might be shouting: "Hey Ryan! That's not going to work!".
And... you're right:
Object of class
DateTime
could not be converted to string
We know that when we have a datetime
type in Doctrine, it's stored in PHP as a DateTime
object. That's nice because DateTime
objects are easy to work with... but we can't simply print them.
To fix this, pass the DateTime
object through a |date()
filter. This takes a format argument - something like Y-m-d H:i:s
.
... lines 1 - 5 | |
<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"> | |
<div class="col-2 text-center"> | |
<img src="{{ asset('images/tisha.png') }}" width="100" height="100" alt="Tisha avatar"> | |
<div class="mt-3"> | |
<small> | |
{% if question.askedAt %} | |
... line 18 | |
{{ question.askedAt|date('Y-m-d H:i:s') }} | |
{% else %} | |
(unpublished) | |
{% endif %} | |
</small> | |
... lines 24 - 29 | |
</div> | |
</div> | |
... lines 32 - 39 | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
... lines 45 - 72 | |
</div> | |
... lines 74 - 75 |
When we try the page now... it's technically correct... but yikes! This... well... how can I put this politely: it looks like a backend developer designed this.
Whenever I render dates, I like to make them relative. Instead of printing an exact date, I prefer something like "10 minutes ago". It also avoids timezone problems... because 10 minutes ago makes sense to everyone! But this exact date would really need a timezone to make sense.
So let's do this. Start by adding the word "Asked" back before the date. Cool.
... lines 1 - 5 | |
<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"> | |
<div class="col-2 text-center"> | |
<img src="{{ asset('images/tisha.png') }}" width="100" height="100" alt="Tisha avatar"> | |
<div class="mt-3"> | |
<small> | |
{% if question.askedAt %} | |
Asked <br> | |
{{ question.askedAt|date('Y-m-d H:i:s') }} | |
{% else %} | |
(unpublished) | |
{% endif %} | |
</small> | |
... lines 24 - 29 | |
</div> | |
</div> | |
... lines 32 - 39 | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
... lines 45 - 72 | |
</div> | |
... lines 74 - 75 |
To convert the DateTime
into a friendly string, we can install a nice bundle. At your terminal, run:
composer require knplabs/knp-time-bundle
You could find this bundle if you googled for "Symfony ago". As we know, the main thing that a bundle gives us is more services. In this case, the bundle gives us one main service that provides a Twig filter called ago
.
It's pretty awesome. Back in the template, add |ago
.
... lines 1 - 5 | |
<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"> | |
<div class="col-2 text-center"> | |
<img src="{{ asset('images/tisha.png') }}" width="100" height="100" alt="Tisha avatar"> | |
<div class="mt-3"> | |
<small> | |
{% if question.askedAt %} | |
Asked <br> | |
{{ question.askedAt|ago }} | |
{% else %} | |
(unpublished) | |
{% endif %} | |
</small> | |
... lines 24 - 29 | |
</div> | |
</div> | |
... lines 32 - 39 | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
... lines 45 - 72 | |
</div> | |
... lines 74 - 75 |
We're done! When we refresh now... woohoo!
Asked 1 month ago
Next: let's make the homepage dynamic by querying for all of the questions in the database and rendering them. Along the way, we're going to learn a secret about the repository object.
// composer.json
{
"require": {
"php": "^7.4.1",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/doctrine-bundle": "^2.1", // 2.1.1
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
"doctrine/orm": "^2.7", // 2.8.2
"knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
"knplabs/knp-time-bundle": "^1.11", // v1.16.0
"sensio/framework-extra-bundle": "^6.0", // v6.2.1
"sentry/sentry-symfony": "^4.0", // 4.0.3
"stof/doctrine-extensions-bundle": "^1.4", // v1.5.0
"symfony/asset": "5.1.*", // v5.1.2
"symfony/console": "5.1.*", // v5.1.2
"symfony/dotenv": "5.1.*", // v5.1.2
"symfony/flex": "^1.3.1", // v1.17.5
"symfony/framework-bundle": "5.1.*", // v5.1.2
"symfony/monolog-bundle": "^3.0", // v3.5.0
"symfony/stopwatch": "5.1.*", // v5.1.2
"symfony/twig-bundle": "5.1.*", // v5.1.2
"symfony/webpack-encore-bundle": "^1.7", // v1.8.0
"symfony/yaml": "5.1.*", // v5.1.2
"twig/extra-bundle": "^2.12|^3.0", // v3.0.4
"twig/twig": "^2.12|^3.0" // v3.0.4
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
"symfony/debug-bundle": "5.1.*", // v5.1.2
"symfony/maker-bundle": "^1.15", // v1.23.0
"symfony/var-dumper": "5.1.*", // v5.1.2
"symfony/web-profiler-bundle": "5.1.*", // v5.1.2
"zenstruck/foundry": "^1.1" // v1.5.0
}
}