ago Filter with KnpTimeBundle

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

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!

  • 2020-06-16 Victor Bocharsky

    Hey Raed,

    Glad it helped, thanks for confirming!

    Cheers!

  • 2020-06-15 Zool

    Hey Victor Bocharsky ,

    Thank you so much for your reply ! This helped solve the problem and understand the concept !

  • 2020-06-12 Victor Bocharsky

    Hey Raed,

    If you look into your composer.json - you will find that it has "config.platform.php" section in it and PHP is locked at 7.1.3 by us. That was made because we don't want to lock project dependencies on the latest PHP version as users with lower PHP version won't be able to install it. So, technically, you can bump that PHP version to 7.2.5 as required by Symfony and then Composer should be able to install it. Actually, you can even remove that config.platform.php section at all and then Composer will base on your current PHP version only, but keep that config.platform.php is a good idea, especially when you have different PHP versions on production on locally. Ideally you should set that config.platform.php version to the one you use on production to make sure all the dependencies you're installing locally will work on production's PHP version :)

    But I suppose you got that error not on installing KnpTimeBundle but on executing "composer update", as it clearly wants to upgrade symfony versions. But if it's something you intended to do - then follow the solution I mentioned above. If not - try to just install locked dependencies with "composer install" execute only "composer require %bundle-name%" to install the KnpTimeBundle without upgrading existent packages.

    I hope it's clear for you!

    Cheers!

  • 2020-06-11 Zool

    HI team !
    i had a problem when installing KnpTimeBundle showed as:


    Your requirements could not be resolved to an installable set of packages.
    Problem 1
    - symfony/config v5.1.0 requires php >=7.2.5 -> your PHP version (7.4.5) overridden by "config.platform.php" version (7.1.3) does not satisfy that requirement.
    - symfony/config v5.0.9 requires php >=7.2.5 -> your PHP version (7.4.5) overridden by "config.platform.php" version (7.1.3) does not satisfy that requirement.
    - Can only install one of: symfony/config[v4.4.7, v4.0.14].
    - Can only install one of: symfony/config[v4.4.9, v4.0.14].
    - Installation request for symfony/config (locked at v4.0.14) -> satisfiable by symfony/config[v4.0.14].
    Installation failed, reverting ./composer.json to its original content.

    = symfony version was 4.0 then i changed requires in composer.json to
    "symfony/asset": "^4.4",
    "symfony/console": "^4.4",

    = i run composer update, then i had another error that Victor Bocharsky helped me out with it in another tutorial,

    Cannot autowire service "App\Repository\ArticleRepository": argument "$registry" of method "__construct()" references interface "Symfony\Bridge\
    !! Doctrine\RegistryInterface" but no such service exists. Try changing the type-hint to "Doctrine\Persistence\ManagerRegistry" instead.

    I did followed the messag type-hint, afterwards i could finally run composer require knplabs/knp-time-bundle and it worked.
    The problem is that, there is an error in the constructor of ArticleRepository, precisely under the parent:: property , i when i hover, the IDE shows :
    Cannot use 'parent' in a class with no parent.intelephense(1035)
    Peek Problem (Alt+F8)
    No quick fixes available

    public function __construct(ManagerRegistry $registry)
    {
    parent::__construct($registry, Article::class);
    }

    My question is, Does this error come from my IDE or did i change something that i should not while configuring the project ? Thanks

  • 2020-01-06 Victor Bocharsky

    Hey LE Xuan Hung,

    Glad to hear you got it working! Looks like the bundle required some dependencies you had to be updated as well and composer wasn't able to solve this issue itself.

    Cheers!

  • 2020-01-06 LE Xuan Hung

    Hello Victor,

    After running composer update, i can install knptime bundle now.

    Otherwise,

    I have downloaded the source code and used start directory. I have Symfony version 4.4.2

    bin/console --version
    Symfony 4.4.2 (env:dev, debug: true)

    Thanks you for help.

  • 2020-01-06 Victor Bocharsky

    Hey LE Xuan Hung,

    First of all, "symfony/lts" is deprecated package in favor of Symfony Flex... if you have it in your composer.json, you can remove it. But that's minor. About the problem installing KnpTimeBundle - what version of Symfony do you have? Are you trying to install it on your own project? Or did you download the course code and work in start/ directory of it? Are you able to install dependencies successfully without KnpTimeBundle executing "composer install"? Do you see any errors?

    Cheers!

  • 2020-01-05 LE Xuan Hung

    Hello, i have this error when user composer to install knpTimeBundle

    ~Dev/the_spacebartest$ composer require knplabs/knp-time-bundle
    Using version ^1.11 for knplabs/knp-time-bundle
    ./composer.json has been updated
    Loading composer repositories with package information
    Updating dependencies (including require-dev)

    Your requirements could not be resolved to an installable set of packages.
    Problem 1
    - Conclusion: remove symfony/lts 4.x-dev
    - Conclusion: don't install symfony/lts 4.x-dev
    - symfony/config v5.0.0 conflicts with symfony/lts[4.x-dev].
    - symfony/config v5.0.1 conflicts with symfony/lts[4.x-dev].
    - symfony/config v5.0.2 conflicts with symfony/lts[4.x-dev].
    - Installation request for symfony/lts ^4@dev -> satisfiable by symfony/lts[4.x-dev].
    - Installation request for knplabs/knp-time-bundle ^1.11 -> satisfiable by knplabs/knp-time-bundle[v1.11.0].
    - Conclusion: don't install symfony/config v4.0.14|install
    symfony/config v5.0.0|install symfony/config v5.0.1|install
    symfony/config v5.0.2
    - Conclusion: remove symfony/config
    v4.0.14|install symfony/config v5.0.0|install symfony/config
    v5.0.1|install symfony/config v5.0.2

    - knplabs/knp-time-bundle
    v1.11.0 requires symfony/config ~3.4|^4.3|^5.0 -> satisfiable by
    symfony/config[v3.4.0, v3.4.1, v3.4.10, v3.4.11, v3.4.12, v3.4.13,
    v3.4.14, v3.4.15, v3.4.16, v3.4.17, v3.4.18, v3.4.19, v3.4.2, v3.4.20,
    v3.4.21, v3.4.22, v3.4.23, v3.4.24, v3.4.25, v3.4.26, v3.4.27, v3.4.28,
    v3.4.29, v3.4.3, v3.4.30, v3.4.31, v3.4.32, v3.4.33, v3.4.34, v3.4.35,
    v3.4.36, v3.4.4, v3.4.5, v3.4.6, v3.4.7, v3.4.8, v3.4.9, v4.3.0, v4.3.1,
    v4.3.2, v4.3.3, v4.3.4, v4.3.5, v4.3.6, v4.3.7, v4.3.8, v4.3.9, v4.4.0,
    v4.4.1, v4.4.2, v5.0.0, v5.0.1, v5.0.2].

    - Can only install one of: symfony/config[v3.4.0, v4.0.14].
    - Can only install one of: symfony/config[v3.4.1, v4.0.14].
    - Can only install one of: symfony/config[v3.4.10, v4.0.14].
    - Can only install one of: symfony/config[v3.4.11, v4.0.14].
    - Can only install one of: symfony/config[v3.4.12, v4.0.14].
    - Can only install one of: symfony/config[v3.4.13, v4.0.14].
    - Can only install one of: symfony/config[v3.4.14, v4.0.14].
    - Can only install one of: symfony/config[v3.4.15, v4.0.14].
    - Can only install one of: symfony/config[v3.4.16, v4.0.14].
    - Can only install one of: symfony/config[v3.4.17, v4.0.14].
    - Can only install one of: symfony/config[v3.4.18, v4.0.14].
    ...
    Thanks

  • 2019-12-30 weaverryan

    Hey Manoj Kumar!

    It should be working now - I had a bug in a release of KnpTimeBundle, which has now been fixed.

    Cheers!

  • 2019-12-30 weaverryan

    Hey Mike!

    Thanks for the poke - I've got that PR merged and a new release tagged :).

    Cheers!

  • 2019-12-30 Mike

    There seems to be a solution:
    https://github.com/KnpLabs/...

    But weaverryan needs to merge this commit to make the bundle compatible with twig 3 & sf 5.

  • 2019-12-29 Mike

    Like Manoj said in the comment below, its not working.

    You are right, Ryan said that this bundle is now compatible with SF5 (In the release notes), but its not.

    When I install knpTimeBundle via composer I get this error:

    !! 17:12:38 CRITICAL [php] Uncaught Error: Class 'Twig_Extension' not found ["exception" => Error { …}]
    ...
    !! In TimeExtension.php line 20:
    !!
    !! Attempted to load class "Twig_Extension" from the global namespace.
    !! Did you forget a "use" statement?

    Thats because it relies on the twig/extensions bundle.
    And when I try to install it via:

    composer require twig/extensions

    I get this error:

    Restricting packages listed in "symfony/symfony" to "5.0.*"
    Your requirements could not be resolved to an installable set of packages.

    Because the twig/extensions bundle got abandoned by Fabian.

    WARNING: This repository is abandoned in favor of Twig Core Extra extensions.
    ...
    DateExtension: time_diff filter -> no equivalent

    So the knpTimeBundle by itself maybe is compatible with SF5, but the required twig/extensions bundle to use it is incompatible with Twig3 & SF 5, that's why we can't use it.

    Any advice weaverryan how to make it work again?

  • 2019-12-29 Manoj Kumar

    I tried to install on SF 5. But it's not working out.

  • 2019-12-05 Diego Aguiar

    Hey Mike

    I think that bundle just got support for Syfmony5 and 4.4, you should give it a try

    Cheers!

  • 2019-12-05 Mike

    This bundle relies on the Twig/Extensions Bundle (Which is abandoned) and isn't compatible with SF 5 and Twig 3 (Twig 3 ships normally with SF 4.4, so its even incompatible out of the box with SF 4.4).
    https://github.com/KnpLabs/...

    Is there any alternative? Will this bundle get fixed?

  • 2019-08-21 Victor Bocharsky

    Hey John,

    Are you sue you had exactly the same missing file, i.e. "Symfony\Component\Form\FormTypeGuesserInterface" after did "composer require form"? Because that file IS from the symfony/form repo, you check see the file there: https://github.com/symfony/... . Most probably after installing it there was a similar error but with another file name, that you would need to require as well. Or probably it was cache, try to clear it first.

    Anyway, I'm glad you found a solution that works for you!

    Cheers!

  • 2019-08-19 John Christensen

    Ok thx. Fyi.. I did try installing the form package, but get the same error. I had to downgrade php to 7.1.30 just to get the package to install. But if I then run php 7.2.20 or 7.3.7, I get the 'uncaught ReflectionException' error. I am using MAMP PRO which does not have php 7.3.8 yet, so I guess I will just run with 7.1 for now.

  • 2019-08-19 Victor Bocharsky

    Hey Ozornick,

    Yeah, upgrading to the newest PHP version 7.3.8 should be the best option I think. Thanks for the tip!

    Cheers!

  • 2019-08-19 Ozornick

    PHP 7.3.8 normally

  • 2019-08-19 Victor Bocharsky

    Hey John,

    Yeah, it's a known issue on PHP 7.2.20 / 7.3.7. Upgrading to the latest PHP version should solve the issue - there's a new release 7.2.21 / 7.3.8. But in case you can't upgrade to the latest for some reasons - you can temporarily workaround it by installing missing packages - symfony/form in your case.

    Cheers!

  • 2019-08-18 John Christensen

    I figured it out..

    Apparently this is a PHP compatibility issue. The error occurs with PHP 7.2.20 and 7.3.7.

    Downgrading to PHP 7.1.30 solves the problem.

  • 2019-08-18 John Christensen

    After installing the time bundle with `composer require knplabs/knp-time-bundle` I get this error:

    Script cache:clear returned with error code 255

    Error: During class fetch: Uncaught ReflectionException: Class Symfony\Comp
    !! onent\Form\FormTypeGuesserInterface not found in /Users/john_christensen/Do
    !! wnloads/code-symfony-doctrine/start/vendor/symfony/doctrine-bridge/Form/Doc
    !! trineOrmTypeGuesser.php:25
    !! Stack trace:
    !! #0 /Users/john_christensen/Downloads/code-symfony-doctrine/start/vendor/sym
    !! fony/debug/DebugClassLoader.php(145): require('/Users/john_chr...')
    !! #1 [internal function]: Symfony\Component\Debug\DebugClassLoader->loadClass
    !! ('Symfony\\Bridge\\...')
    !! #2 [internal function]: spl_autoload_call('Symfony\\Bridge\\...')
    !! #3 /Users/john_christensen/Downloads/code-symfony-doctrine/start/vendor/sym
    !! fony/config/Resource/ClassExistenceResource.php(76): class_exists('Symfony\
    !! \Bridge\\...')
    !! #4 /Users/john_christensen/Downloads/code-symfony-doctrine/start/vendor/sym
    !! fony/dependency-injection/ContainerBuilder.php(350): Symfony\Component\Conf
    !! ig\Resource\ClassExistenceResource->isFresh(0)
    !! #5 /Users/john_christensen/Downloads/code-symfony-doctrine/start/vendor/sym
    !! fony/dependency-inje

    which appears to be this flex issue:

    https://github.com/symfony/...

    Also.. if I start with the tutorial 'finish' folder and do a `composer install` I still get this error.

    So has something changed with symfony/flex? Any idea how to resolve this?

    Thx!

  • 2019-04-08 Diego Aguiar

    Hey AbelardoLG

    That is true, frontend should load quick but in this case, we are rendering HTML on the server, so it won't make any difference. If you ask me, I would do that filtering at DB level.

    Cheers!

  • 2019-04-08 AbelardoLG

    The less process at frontend, the less consuming resources in the client side is.

  • 2019-04-08 AbelardoLG

    I would only publish those blogs that have publishedAt field equal not null. The "if" statement should be at PHP side.

  • 2019-03-11 Victor Bocharsky

    Hey Mike,

    Hm, it's probably too minor to worry about caching it. Actually, it's almost the same as use "|date" Twig filter, i.e. it's just a date formatter. Yes, you can use JavaScript to do things like this, but you would need to render the date object anyway to put the date into HTML, so you would need to use |date filter anyway. And so, why not use |ago filter instead? There's no any time-consuming calculations behind the scene, you would probably need to worry about how to cache the entity itself to avoid hitting MySQL - would have more sense :)

    Btw you can use Blackfire.io to profile the performance and see some bottle necks in your project. But if you do think that "|ago" hit your performance, probably better to cache the whole page. And yes, in *that* case you would need to render dates with JavaScript.

    Cheers!

  • 2019-03-10 Mike

    Is it possible to cache the 'ago' time or does this Bundle do it automatically for us? My question is, wouldn't it be better, performance wise, to calculate the time client side via Javascript? Of course I see the point of this example, being super simple.

  • 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?