Buy

Phew! Dependency injection, check! Registering new services, check! Delicious snack, check! Well, I hope you just had a delicious snack.

This tutorial is the start to our victory lap. We need to add caching to MarkdownTransformer: it should be pretty easy. Copy part of the old caching code and paste that into the parse() function. Remove the else part of the if and just return $cache->fetch():

32 lines src/AppBundle/Service/MarkdownTransformer.php
... lines 1 - 6
class MarkdownTransformer
{
... lines 9 - 15
public function parse($str)
{
$cache = $this->get('doctrine_cache.providers.my_markdown_cache');
$key = md5($str);
if ($cache->contains($key)) {
return $cache->fetch($key);
}
... lines 23 - 29
}
}

Below, assign the method call to the $str variable and go copy the old $cache->save() line. Return $str and re-add the sleep() call so that things are really slow - that keeps it interesting:

32 lines src/AppBundle/Service/MarkdownTransformer.php
... lines 1 - 6
class MarkdownTransformer
{
... lines 9 - 15
public function parse($str)
{
... lines 18 - 23
sleep(1);
$str = $this->markdownParser
->transformMarkdown($str);
$cache->save($key, $str);
return $str;
}
}

On top, change the $funFact variables to $str. Perfect!

We know this won't work: there is no get() function in this class. And more importantly, we don't have access to the doctrine_cache.provider.my_markdown_cache service. How can we get access? Dependency injection.

Dependency Inject!

This time, add a second argument to the constructor called $cache. And hmm, we should give this a type-hint. Copy the service name and run:

./bin/console debug:container doctrine_cache.providers.my_markdown_cache

This service is an instance of ArrayCache. But wait! Do not type-hint that. In our earlier course on environments, we setup a cool system that uses ArrayCache in the dev environment and FilesystemCache in prod:

72 lines app/config/config.yml
... lines 1 - 7
parameters:
locale: en
cache_type: file_system
... lines 11 - 65
doctrine_cache:
providers:
my_markdown_cache:
type: %cache_type%
file_system:
directory: %kernel.cache_dir%/markdown_cache

If we type-hint with ArrayCache, this will explode in prod because this service will be a different class.

Let's do some digging: open up ArrayCache:

95 lines vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php
... lines 1 - 32
class ArrayCache extends CacheProvider
{
... lines 35 - 93
}

This extends CacheProvider:

278 lines vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php
... lines 1 - 31
abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, MultiGetCache
{
... lines 34 - 276
}

That might work. But it implements several interface - one of them is just called Cache. Let's try that. If this isn't the right interface - meaning it doesn't contain the methods we're using - PhpStorm will keep highlighting those after we add the type-hint:

35 lines src/AppBundle/Service/MarkdownTransformer.php
... lines 1 - 7
class MarkdownTransformer
{
... lines 10 - 12
public function __construct(MarkdownParserInterface $markdownParser, Cache $cache)
{
... lines 15 - 16
}
... lines 18 - 33
}

I'll use a keyboard shortcut - option+enter on a Mac - and select initialize fields:

35 lines src/AppBundle/Service/MarkdownTransformer.php
... lines 1 - 7
class MarkdownTransformer
{
... line 10
private $cache;
public function __construct(MarkdownParserInterface $markdownParser, Cache $cache)
{
... line 15
$this->cache = $cache;
}
... lines 18 - 33
}

All this did was add the private $cache property and set it in __construct(). You can also do that by hand.

Cool! Update parse() with $cache = $this->cache:

35 lines src/AppBundle/Service/MarkdownTransformer.php
... lines 1 - 7
class MarkdownTransformer
{
... lines 10 - 18
public function parse($str)
{
$cache = $this->cache;
... lines 22 - 32
}
}

And look! All of the warnings went away. That was the right interface to use. Yay!

Because we added a new constructor argument, we need to update any code that instantiates the MarkdownTransformer. But now, that's not done by us: it's done by Symfony, and we help it in services.yml. Under arguments, add a comma and quotes. Copy the service name - @doctrine_cache.providers.my_markdown_cache and paste it here:

10 lines app/config/services.yml
... lines 1 - 5
services:
app.markdown_transformer:
class: AppBundle\Service\MarkdownTransformer
arguments: ['@markdown.parser', '@doctrine_cache.providers.my_markdown_cache']

That's it! That's the dependency injection pattern.

Go back to refresh. The sleep() should make it really slow. And it is slow. Refresh again: still slow because we setup caching to really only work in the prod environment.

Clear the prod cache:

./bin/console cache:clear --env=prod

And now add /app.php/ in front of the URI to use this environment. This should be slow the first time... but then fast after. Super fast! Caching is working. And dependency injection is behind us.

Leave a comment!

  • 2017-10-11 weaverryan

    Hey Eduardo Guglielmotti!

    Ah, that's great news that any errors have been minor. Missing the use statement is probably the #1 thing that people forget. You'll make this mistake a few hundred times... then stop making it ;). As long as you can locate the problem, it's no issue.

    About the strtoupper, it's ok! This should not be in your code anymore. We originally put this code in the MarkdownTransformer.parse() method, just so we could see things working. But then, we removed it and instead started using the MarkdownParser internally. You can see that at the very beginning of this video: https://knpuniversity.com/s.... So, you're ok! Your fun fact should not be upper cased, but it should be running through markdown transformation.

    Cheers!

  • 2017-10-11 Eduardo Guglielmotti

    Hello, I am doing the Level up with Services and the Container Tutorial course and for now all good, the errors that I usually found were by writing or lack of a use in any class.
    But I see that the functionality of strtoupper has been lost since it does not change lowercase to uppercase the fun: fact.
    Is it correct or do I have some error to correct

    regards

  • 2017-10-04 weaverryan

    Hey Rich!

    Haha, oh no! It's really not going to be bad. In fact, this is one of the best reasons to use Symfony: we don't break backwards compatibility and we practice something called the "continuous upgrade path": a system where you can migrate your code across major versions without breaking things. We'll have a tutorial out in about 2 months about upgrading to Symfony 4.

    In fact, usually, when big things happen in Symfony, it's not a change to the core code, but new recommendations we make about how you should write your code. That's more or less what's happening with Symfony 4. So, let me assure you, it's going to be ok! And here's my recommendation: continue with these tutorials, completing following them in the Symfony 3 way of doing things (so, commenting-out all the autowiring stuff as you did). When Symfony 4 comes out, we will have a tutorial about learning the changes. But, it's not as much as you think, and there are basically 2 big changes

    A) The directory structure of a project will change. No big deal, you will have basically the same files, but in new locations.

    B) We will use autowiring everywhere. The Symfony 3 way of doing things is to wire all of your services in services.yml manually. In Symfony 4, much of this will be automated, but everything under the hood is still the same (in fact, the manual wiring will still work, if you prefer that way). So, the change from Symfony 3 to Symfony 4 will be to do less work, and let the framework take care of more tasks for you. You're actually in great shape, because you're learning it the "hard" way. In Symfony 4, you'll need to worry about less. I think it's a bit like driving a car: you're learning to drive a manual transmission now. In Symfony 4, it's an automatic transmission. It will (hopefully) be so much simpler, you'll get bored ;).

    So, keep going! And we'll help you through the Symfony 4 changes when it's time. You're doing great! I know because of how well you debugged this issue.

    Cheers!

  • 2017-10-04 Rich Wilx

    Hey Ryan....

    Gotta say, I'm very fragile right now.... I'm just beginning to get the hang of Symfony 3.3 and now you speak of a major version coming out in just a few weeks.

    How bad is it going to be for me if I decide not to jump up to the new shiny goodness when it arrives?

    -Rich

  • 2017-10-02 weaverryan

    Yo Rich Wilx!

    Good debugging :). We're in a weird spot right now while we transition from the old, more manual way of defining services to the newer, autowiring way. In this tutorial, we use a version of Symfony that does not come with any of the new autowiring features - so you were correct to disable those (if you want to follow along with the tutorial exactly). Later this year, we'll start releasing tutorials that use the new autowiring way of doing things (once Symfony 4 comes out, in Nov). We have a tutorial about this new stuff (https://knpuniversity.com/s..., but mostly, it's not a huge change... it mostly makes life easier. So, keep on as you are, then watch for tutorials with the new stuff once Symfony 4 is here!

    Cheers!

  • 2017-09-30 Rich Wilx

    ok... so I'm getting there... I finally spotted your notes about Symfony 3.3 and stripped the new lines from services.yml

    Now, things seem to be working again.

    Fingers crossed for the next parts.

  • 2017-09-30 Rich Wilx

    Debug is ongoing here.... using PhP Storms search function, I searched for any instance of the text "my_markdown_cache" in my project. Only one.... in services.yml.... the very line we just typed.

    This is interesting as this means that I have probably inadvertently damaged/deleted wherever it was that I built my_markdown_cache.

    Now what tutorial step did we do that???? Time to go hunting....

  • 2017-09-30 Rich Wilx

    Unfortunately, I'm getting myself into a real mess with this particular part of the series. I keep getting error messages and when I correct one, I get another..... anyway, today's latest error reads:

    "(1/1) AutowiringFailedException
    Cannot autowire service "AppBundle\Service\MarkdownTransformer": argument "$cache" of method "__construct()" references interface "Doctrine\Common\Cache\Cache" but no such service exists. You should maybe alias this interface to one of these existing services: "2_ef8536ca925f81f3571c61ede2633674a188e643de54e41c1dc937224c39e54d", "2_1e0b21c4662523189de20fb2b1592823910640a5c2c8e5a769894fee7e2dd97b", "annotations.filesystem_cache", "annotations.cache", "doctrine_cache.providers.doctrine.orm.default_metadata_cache", "doctrine_cache.providers.doctrine.orm.default_result_cache", "doctrine_cache.providers.doctrine.orm.default_query_cache", "doctrine_cache.providers.my_markdown_cache"."

    Any thoughts?

  • 2017-08-25 weaverryan

    Wow, this is a mystery! The bin/console cache:clear --env=prod command actually creates a fresh, *new* var/cache/prod directory, then deletes the old one, and moves this new one into its place (it's temporarily called pro_ if you look quickly). In other words, the cache directory is *completely* replaced when you run the command.

    If you're curious, you could look for the cached Twig file and see what's inside. The Twig files cache down to PHP files, in var/cache/prod/twig There are a *bunch* of different directories and files, so you would need to grep in there for some content that appears on your 404 page. What would be interesting is to see - after you run cache:clear - is the correct, updated code inside the cached Twig template PHP file? Or does the cached Twig template PHP file still show the old code?

    This is a long way of saying... this is weird.! It certainly should not work like this, and I can't think why it is!

    Cheers!

  • 2017-08-24 Mike

    Ive tried it again, now even with admin right:

    pwd -> in correct directory (phpstorm)
    Visit 404 page and Homepage
    change /app/Resources/TwigBundle/views/Exception/error404.html.twig & views/main/homepage.html.twig (for start page)
    run:
    sudo ./bin/console cache:clear --env=prod && sudo ./bin/console cache:clear --env=dev
    Got: [OK] Cache for the "prod" environment (debug=false) was successfully cleared.
    [OK] Cache for the "dev" environment (debug=true) was successfully cleared.

    But nothing changed on the site. After manually deleting the cache it works. Very strange.

  • 2017-08-24 Diego Aguiar

    Hey Mike!

    That's kind of weird, "bin/console cache:clear --env=prod" should do the work, if you make more changes to it, can you see them after clearing cache, or you have to delete the cache folder manually?

    How did you customize the 404 page ?

    Cheers!

  • 2017-08-24 Mike

    I have one strange error regarding the cache System.
    The test:

    1.) Visit site.com/sitedoesntexist (env = prod)
    2.) Receive custom 404 page
    3.) Change source code of custom 404 page
    4.) Clear Cache via :./bin/console cache:clear --no-warmup --env=prod // ./bin/console cache:warmup --env=prod
    5.) Revisit site, *nothing* changed (expected to see the change from 3.))
    6.) Remove all the contents from /var/cache/* manually
    7.) Revisit the site, now I can see the source code change

    I don't understand why the twig template cache doesn't get deleted. I googled the command and it should be the right for SF 3.3.6 but I see the old cached pages. Only if I delete the cache direcotry manually I see the change. Do you know why this can happen // how can I find the reason out and how do I fix it?

  • 2017-07-14 Mehmet Eren Soylu

    Thanks Ryan, cheers!

  • 2017-07-13 weaverryan

    Hey Mehmet Eren Soylu!

    I think I know the problem: it's due to a big "philosophical" change that we made in Symfony 3.3, and we need to add some notes to our tutorials to help guide people through them. Basically, add public: true to your service. Like this:


    services:
    app.markdown_transformer:
    class: ...
    arguments: [...]
    public: true

    While you're going through the tutorial, add that to any services that you create. Then, when you're ready, here is an explanation (actually written by me, so hopefully it makes some sense!) of how we've started configuring services slightly differently in Symfony 3.3: https://symfony.com/doc/cur...

    Cheers!

  • 2017-07-13 Mehmet Eren Soylu

    Hi everyone,

    I have a ServiceNotFoundException on this line in GenusController

    >>> $this->get('app.markdown_transformer');

    "Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException:
    You have requested a non-existent service "app.markdown_transformer"

    - I'm using phpstorm on windows, symfony 3.3.4
    - I have checked services.yml, MarkdownTransformer.php and GenusController.php nothing seems wrong
    - php bin/console debug:container markdown command shows app.markdown_transformer in console
    - var\cache\dev\appDevDebugProjectContainer.php does not have getApp_MarkdownTransformerService function and app.markdown_transformer in methodMap object
    - I have manually deleted var/cache, nothing is changed

    Any help is appreciated, thanks.

  • 2017-06-13 Victor Bocharsky

    Hey Darkfaythe ,

    Glad you solved it! Was it a user error? Or do we have this error in code we provide to you?

    Cheers!

  • 2017-06-13 Darkfaythe

    I get this same error and I am using

    use Doctrine\Common\Cache\Cache;

    CRITICAL - Uncaught PHP Exception
    Symfony\Component\Debug\Exception\ContextErrorException: "Notice:
    Undefined property: AppBundle\Service\MarkdownTransformer::$cache" at
    /home/rob/aqua_note/src/AppBundle/Service/MarkdownTransformer.php line
    22

    Solved it now though.

  • 2017-05-05 Diego Aguiar

    That's something you can't miss when watching our tutorials ;)

    Have a nice day!

  • 2017-05-05 Islam Elshobokshy

    Nope, totally didn't had a delicious snake :( I'll go to the supermarket to buy some chips and then get back to the course. Thanks for the idea ! :P

  • 2017-02-13 weaverryan

    Great news! Cheers!

  • 2017-02-11 Sander de Wijs

    Thanks! I needed to perform all the steps from A to D to fix the problem. Not sure about the source of the issue though. Was coding along form lesson 1 and I haven't looked in production until this step so It could have existed for a while. Thanks again for the help and creating this awesome course!

  • 2017-02-08 weaverryan

    Yo Sander de Wijs!

    Wooooh. Indeed, something is NOT right here (good idea to re-run composer install and also try a fresh composer update). I *do* think we have some problem, somehow, with either our dependencies, or something else weird. What's interesting is that those 2 classes are new in Symfony 3.2. So, it's almost like your container was cached when those files DID exist... but then your vendor directory got re-set to an old version. Some things to try:

    A) Put your original composer.lock file back (if you still have it - otherwise download the code from this page and use ours)
    B) Run an rm -rf vendor/ to completely reset things
    C) Try a composer install
    ... if the problem persist
    D) Run an rm -rf var/cache/* and then try re-building the cache

    If (D) works, then - at least from a very high level - the issue is that your cached container got compiled using Symfony 3.2 (or later) code (somehow), but then your vendor directory (somehow) was changed to only include Symfony 3.1. Rebuilding the cache *uses* the existing cache. And in this situation, your existing cache would kind of be in a "poisoned state".

    Phew! Ok, let me know what (if anything) works!

    Cheers!

  • 2017-02-07 Sander de Wijs

    I just ran into an issue when clearing the production cache:
    Fatal error: Class 'Symfony\Component\HttpKernel\Debug\FileLinkFormatter' not found in /Users/sanderdewijs/Documents/Projects/Symfony3/var/cache/prod/appProdProjectContainer.php on line 3207

    I did a composer install (removed composer.lock first) and a composer update but the problem persists.

    When learing the dev cache there are no errors. Any ideas what is causing this?

    <edit> Perhaps related, if I go to the prod environment I get another error on the single Genus page:
    Fatal error: Class 'Symfony\Bundle\SecurityBundle\SecurityUserValueResolver' not found in [...]appProdProjectContainer.php on line 1501

  • 2016-11-04 Victor Bocharsky

    Hi Terry,

    Yes, you're right. This method returns a raw HTML, but Controller::render() method returns a Response object, check it in source code to understand how it works behind the scene: https://github.com/symfony/... . To get a raw HTML is useful for rendering a mail body which you will send to user, etc.

    Cheers

  • 2016-11-03 Terry Caliendo

    Figured it out... needed to use:
    $this->renderResponse
    instead of
    $this->render
    in my service.

    I remembered one of the videos where Ryan dove into the controller and "render" was just a pass through method.

  • 2016-11-03 Terry Caliendo

    I'm having issues... As you have above the method someMethod in MyService returns the $this->templating->render(...) to my controller which is called like this:


    /**
    * @Route("/myapp")
    */
    public function myappAction()
    {
    $MyService= $this->get('app.MyService');
    return $MyService->someMethod();
    }


    I was expecting the returns to bubble through back to the controller with the response object. But when I run the app, I'm getting the error message "The controller must return a response (<html><body>...".

    It looks like its returning the raw html?!

    Here is a screen shot:
    https://s16.postimg.org/4g3...

    ps
    FYI: I'd like to attache a screenshot of the error, but it appears as though the ability to add images is turned off.

  • 2016-11-03 Terry Caliendo

    That's exactly what I needed. I was struggling to find the EngineInterface connection. Thanks much!

  • 2016-11-03 Victor Bocharsky

    Hey Terry,

    Check my example how to inject templating service into your service:


    #in services.yml

    services:
    my_service:
    class: AppBundle\Service\MyService
    arguments: ['@templating']

    # and then in MyService.php

    namespace AppBundle\Service;

    use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;

    class MyService
    {
    private $templating;

    public function __construct(EngineInterface $templating)
    {
    $this->templating = $templating;
    }

    public function someMethod()
    {
    $someVar = 'some-val';

    return $this->templating->render('path/to/template.html.twig', [
    'someVar' => $someVar,
    ]);
    }
    }

    So as you see injecting a service is very simple. Also if you use PhpStorm with configured Symfony plugin or actually any other good IDE - you will have an autocompletion for methods, so will see all methods which the EngineInterface interface has.

    But if you mean outputting something in services with echo/print PHP statements - it's a bad practice. You probably need a custom Twig function or filter, check How to Write a custom Twig Extension. We also have a few screencasts about custom Twig extensions, check it out:
    https://knpuniversity.com/s...
    https://knpuniversity.com/s...

    Cheers!

  • 2016-11-02 Terry Caliendo

    If I wanted to output a twig template from my service, how would I do so? Or is that bad practice?

    I can't figure out how to inject and use what I inject. Do I inject "@templating" in my services.yml? Also how do I configure the constructor and then call the template in my method?



    private <variable name="">;
    public function __construct(<type hint=""> <variable name="">)
    {

    $this-><variable name=""> = <variable name="">

    }

    public function myServiceMethod()
    {
    // how do I render the template?

    // if I was in the controller I'd use:
    return $this->render('myServiceOutput.html.twig');

    }

  • 2016-04-25 Roy Hochstenbach

    Yeah I thought it had to be something similar to that. Got it working now, thanks :-)

  • 2016-04-24 weaverryan

    Hi Roy!

    Ah, this is SUCH a great error. It's really hard to spot the problem the first time (the error from PHP is not that clear). You DID remember to add the use statement for Cache in MarkdownTransformer (forgetting this is the MOST common mistake). But, you have the wrong class. There are multiple classes from different libraries called cache. You want:


    use Doctrine\Common\Cache\Cache;

    If you look closely, the error says: "Hey, I'm you this ArrayCache object when MarkdownTransformer is instantiated. But, the type-hint on the argument says that you're expecting this other Sensio\Bundle\FrameworkBundle\Configuration\Cache object. That's a problem!".

    Cheers!

  • 2016-04-21 Roy Hochstenbach

    I get the following error at the end: Catchable Fatal Error: Argument 2 passed to AppBundle\Service\MarkdownTransformer::__construct() must be an instance of Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache, instance of Doctrine\Common\Cache\ArrayCache given.

    MarkdownTransformer.php:

    namespace AppBundle\Service;

    use Knp\Bundle\MarkdownBundle\MarkdownParserInterface;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;

    class MarkdownTransformer
    {

    private $markdownParser;

    private $cache;

    public function __construct(MarkdownParserInterface $markdownParser, Cache $cache)
    {
    $this->markdownParser = $markdownParser;
    $this->cache = $cache;
    }

    public function parse($str)
    {
    $cache = $this->cache;
    $key = md5($str);
    if ($cache->contains($key)) {
    return $cache->fetch($key);
    }

    sleep(1);
    $str = $this->markdownParser
    ->transformMarkdown($str);
    $cache->save($key, $str);

    return $str;

    }
    }

    services.yml:

    # Learn more about services, parameters and containers at
    # http://symfony.com/doc/curr...
    parameters:
    # parameter_name: value

    services:
    app.markdown_transformer:
    class: AppBundle\Service\MarkdownTransformer
    arguments: ['@markdown.parser','@doctrine_cache.providers.my_markdown_cache']

    When I run /bin/console debug:container doctrine_cache.providers.my_markdown_cache It shows the following line:

    Class Doctrine\Common\Cache\ArrayCache