Buy

ago Filter with KnpTimeBundle

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

Login Subscribe

Ok, I just need to show you something fun - it deals with Twig filters. See this 4 hours ago? That's still hard coded! Find the show template and scroll up a bit to find it:

83 lines templates/article/show.html.twig
... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="show-article-container p-3 mt-4">
<div class="row">
<div class="col-sm-12">
... line 13
<div class="show-article-title-container d-inline-block pl-3 align-middle">
... lines 15 - 17
<span class="pl-2 article-details"> 4 hours ago</span>
... lines 19 - 22
</div>
</div>
</div>
... lines 26 - 71
</div>
</div>
</div>
</div>
{% endblock %}
... lines 78 - 83

There!

Printing a DateTime Object in Twig

The Article entity has a $publishedAt property, so let's get our act together and starting using that to print out the real date. Oh, but remember: the $publishedAt field might be null if the article has not been published yet. So let's use the fancy ternary syntax to say: {{ article.publishedAt }}, then, if it is published, print article.publishedAt. But, publishedAt is a DateTime object... and you can't just run around printing DateTime objects, and expect PHP to not get angry.

To fix that, pipe this through a date filter, and then say Y-m-d:

85 lines templates/article/show.html.twig
... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="show-article-container p-3 mt-4">
<div class="row">
<div class="col-sm-12">
... line 13
<div class="show-article-title-container d-inline-block pl-3 align-middle">
... lines 15 - 17
<span class="pl-2 article-details">
{{ article.publishedAt ? article.publishedAt|date('Y-m-d') : 'unpublished' }}
</span>
... lines 21 - 24
</div>
</div>
</div>
... lines 28 - 73
</div>
</div>
</div>
</div>
{% endblock %}
... lines 80 - 85

Most filters do not have any arguments - most are like cached_markdown. But filters are allowed to have arguments. If the article is not published, just say that: unpublished:

85 lines templates/article/show.html.twig
... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="show-article-container p-3 mt-4">
<div class="row">
<div class="col-sm-12">
... line 13
<div class="show-article-title-container d-inline-block pl-3 align-middle">
... lines 15 - 17
<span class="pl-2 article-details">
{{ article.publishedAt ? article.publishedAt|date('Y-m-d') : 'unpublished' }}
</span>
... lines 21 - 24
</div>
</div>
</div>
... lines 28 - 73
</div>
</div>
</div>
</div>
{% endblock %}
... lines 80 - 85

Love it! When we go back and refresh, published on March 20th.

Installing KnpTimeBundle

Cool... but it looked better when it said something like "five minutes ago" or "two weeks ago" - that was way more hipster. The date... it's ugly!

Fortunately, there's a really simple bundle that can convert your dates into this cute "ago" format. Search for KnpTimeBundle. Despite seeing my little face there, I did not create this bundle, so I take no credit for it. I just think it's great.

Scroll down to the "composer require" line, copy that, find your terminal and, paste!

composer require knplabs/knp-time-bundle

This installs the bundle and... interesting! It also installs symfony/translation. Behind the scenes, KnpTimeBundle uses the translator to translate the "ago" wording into other languages.

But what's really cool is that symfony/translation has a Flex recipe. Before I recorded this chapter, I committed our changes so far. So now I can run:

git status

to see what that sneaky translation recipe did. Interesting: we have a new config/packages/translation.yaml file and a new translations/ directory where any translation files should live... if we need any.

At a high level, the recipe system, like always, is making sure that everything is setup for us, automatically.

Using the ago Filter

Ok, let's use that filter! Back in the template, replace the date filter with |ago:

85 lines templates/article/show.html.twig
... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="show-article-container p-3 mt-4">
<div class="row">
<div class="col-sm-12">
... line 13
<div class="show-article-title-container d-inline-block pl-3 align-middle">
... lines 15 - 17
<span class="pl-2 article-details">
{{ article.publishedAt ? article.publishedAt|ago : 'unpublished' }}
</span>
... lines 21 - 24
</div>
</div>
</div>
... lines 28 - 73
</div>
</div>
</div>
</div>
{% endblock %}
... lines 80 - 85

That's it. Find the page, refresh and... perfect! 27 days ago. So much nicer!

Next, I want to talk a little bit more about the AppExtension Twig extension because, for a very subtle but important reason, it has a performance problem.

Leave a comment!

  • 2018-06-21 Diego Aguiar

    Hey @Mehdi Marchouk

    How's the structure of your Phone entity?
    Does it contains a relationship to Person and another one to Business?
    If that's the case you can set up an "UniqueConstraint" at the table level. Check this example: https://www.doctrine-projec...

    Cheers!

  • 2018-06-21 toporovvv

    Can we cache the result of "ago" function, like we did with "parse" in the previous tutorial? If so, what is the logic of invalidation of this value (because date and time flow constantly and it seems that we could cache it for long periods like "a year ago", but can not for "2 hours ago")? If we can not cache this value, will it be more convenient (for a real world project) to make this job on a client side with javascript and to print a full datetime string in a template?