"5 Minutes Ago" Strings

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

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

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.2.5",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.8", // 1.8.2
        "doctrine/doctrine-bundle": "^2.1", // 2.1.0
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.1
        "doctrine/orm": "^2.7", // v2.7.3
        "knplabs/knp-markdown-bundle": "^1.8", // 1.8.1
        "knplabs/knp-time-bundle": "^1.11", // v1.12.0
        "sensio/framework-extra-bundle": "^5.5", // v5.6.1
        "sentry/sentry-symfony": "^3.4", // 3.5.2
        "stof/doctrine-extensions-bundle": "^1.4", // v1.4.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.0
        "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.7.3
        "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.3.1
        "symfony/debug-bundle": "5.1.*", // v5.1.2
        "symfony/maker-bundle": "^1.15", // v1.20.0
        "symfony/var-dumper": "5.1.*", // v5.1.2
        "symfony/web-profiler-bundle": "5.1.*", // v5.1.2
        "zenstruck/foundry": "^1.1" // v1.1.0
    }
}