"5 Minutes Ago" Strings

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

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 Twig date Filter

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.

KnpTimeBundle

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.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.4.1 || ^8.0.0",
        "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.1.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.9.10
        "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
    }
}