Buy
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:

... 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:

... 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:

... 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:

... 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!

  • 2019-01-29 Victor Bocharsky

    Perfect! :) Yes, translations is probably the only place where you have to clear the cache for new translation files. So, it's always a good idea to start with clearing cache before going further.

    Cheers!

  • 2019-01-29 Дмитрий Ченгаев

    Yes, clearing the cache helped. Everything turned out to be easier than I thought. Thank.

  • 2019-01-29 Victor Bocharsky

    Hey Dmitriy,

    Wait, do you mean that "diff.ago.month" string wasn't translated before? If so, then I misunderstood you :) Well, coping the translation file into your project might help, but it really should work out of the box. I think you needed just clear the cache before instead of copying it. So, you can try to clear cache first.

    Cheers!

  • 2019-01-29 Дмитрий Ченгаев

    Thank you, Victor. I just copied the "EN" language file from / vendor / knplabs / knp-time-bundle / Knp / Bundle / TimeBundle / Resources / translations into the root translations folder and it all worked.

  • 2019-01-29 Victor Bocharsky

    Hey Dmitriy,

    As far as I remember this bundle uses smart format. If you did something a few minutes ago, it would use "diff.ago.minute", but if you did something a few years ago, it would use "diff.ago.year". So, it automatically detects how far is the given date from now, and depends on it uses different formats. Is it clear for you now? Is it OK for you? If not, you'd probably need to override the main logic of this bundle somehow, because I see this bundle does not have any configuration options.

    I hope this helps.

    Cheers!

  • 2019-01-27 Дмитрий Ченгаев

    Filter {{page.dateCreate | ago}} returns the date in the format "diff.ago.month". How to change the date format, as shown in the video?

  • 2019-01-03 OutspOaken

    Thank you very much for your fast reply :)

  • 2019-01-03 Vladimir Sadicov

    Hi OutspOaken

    No worries it will be merged

  • 2019-01-03 OutspOaken

    Hello,

    knp-markdown-bundle causes a "A tree builder without a root node is deprecated since Symfony 4.2" error in Symfony 4.2.
    Someone already made a fix which is awaiting being merged:
    https://github.com/KnpLabs/...

    The issue and associated MR has been opened a few weeks ago, hopefully the maintainer or someone with the rights to merge is still aware of this project and merges the fix.

  • 2018-11-26 weaverryan

    Hey again Kristof Kreimeyer !

    Oh geez - this is a nasty error! The problem is not in your code at all - which is very confusing. The error is being very accurate: it basically says that it's looking for this MarkdownHelper file - which is part of a *vendor* library - and it found the file where that class *should* live, but it's not inside. That... makes no sense :). If you, for example, handled installed KnpMarkdownBundle, then the file wouldn't exist at all. The fact that the file - D:\dev\the-spacebar\vendor\composer/../knplabs/knp-markdown-bundle\Helper\MarkdownHelper.php - exists but has the wrong contents is a total mystery.

    So, my only explanation is that something either went strange with Composer or, perhaps, your IDE (PhpStorm can do this) got over-excited when you were (for example) renaming something and renamed things deep inside of this core library. Try this to fix it:


    rm -rf vendor
    composer install

    Between this question and your previous one, something definitely went very wrong (e.g. PhpStorm renaming too much code) - I think these errors are caused by something outside of what you're doing.

    Let me know what you find out!

    Cheers!

  • 2018-11-23 Kristof Kreimeyer

    Hi Guys,

    following error shows up while installing the bundle :

    The autoloader expected class "Knp\Bundle\MarkdownBundle\Helper\MarkdownHelper" to be defined in file "D:\dev\the-spacebar\vendor\composer/../knplabs/knp-markdown-bundle\Helper\MarkdownHelper.php". The file was found but the class was not in it, the class name or namespace probably has a typo.

    This is the MarkdownHelper.php file:


    namespace App\Service;
    use Michelf\MarkdownInterface;
    use Psr\Log\LoggerInterface;
    use Symfony\Component\Cache\Adapter\AdapterInterface;

    class MarkdownHelper

    {
    private $cache;
    private $markdown;
    private $logger;
    private $isDebug;
    public function __construct(AdapterInterface $cache, MarkdownInterface $markdown, LoggerInterface $markdownLogger, bool $isDebug)

    {
    $this->cache = $cache;
    $this->markdown = $markdown;
    $this->logger = $markdownLogger;
    $this->isDebug = $isDebug;
    }
    public function parse(string $source): string

    {
    if (stripos($source, 'bacon') !== false) {
    $this->logger->info('They are talking about bacon again!');
    }
    // skip caching entirely in debug
    if ($this->isDebug) {
    return $this->markdown->transform($source);
    }
    $item = $this->cache->getItem('markdown_'.md5($source));
    if (!$item->isHit()) {
    $item->set($this->markdown->transform($source));
    $this->cache->save($item);
    }
    return $item->get();
    }
    }
  • 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?