It's time to talk about the most fundamental part of Symfony: services!

Honestly, Symfony is nothing more than a bunch of useful objects that work together. For example, there's a router object that matches routes and generates URLs. There's a Twig object that renders templates. And there's a Logger object that Symfony is already using internally to store things in a var/log/dev.log file.

Actually, everything in Symfony - I mean everything - is done by one of these useful objects. And these useful objects have a special name: services.

What's a Service?

But don't get too excited about that word - service. It's a special word for a really simple idea: a service is any object that does work, like generating URLs, sending emails or saving things to a database.

Symfony comes with a huge number of services, and I want you to think of services as your tools.

Like, if I gave you the logger service, or object, then you could use it to log messages. If I gave you a mailer service, you could send some emails! Tools!

The entire second half of Symfony is all about learning where to find these services and how to use them. Every time you learn about a new service, you get a new tool, and become just a little bit more dangerous!

Using the Logger Service

Let's check out the logging system. Find your terminal and run:

tail -f var/log/dev.log

I'll clear the screen. Now, refresh the page, and move back. Awesome! This proves that Symfony has some sort of logging system. And since everything is done by a service, there must be a logger object. So here's the question: how can we get the logger service so that we can log our own messages?

Here's the answer: inside the controller, on the method, add an additional argument. Give it a LoggerInterface type hint - hit tab to auto-complete that and call it whatever you want, how about $logger:

51 lines src/Controller/ArticleController.php
... lines 1 - 4
use Psr\Log\LoggerInterface;
... lines 6 - 10
class ArticleController extends AbstractController
{
... lines 13 - 41
public function toggleArticleHeart($slug, LoggerInterface $logger)
{
... lines 44 - 48
}
}

Remember: when you autocomplete, PhpStorm adds the use statement to the top for you.

Now, we can use one of its methods: $logger->info('Article is being hearted'):

51 lines src/Controller/ArticleController.php
... lines 1 - 10
class ArticleController extends AbstractController
{
... lines 13 - 41
public function toggleArticleHeart($slug, LoggerInterface $logger)
{
// TODO - actually heart/unheart the article!
$logger->info('Article is being hearted!');
... lines 47 - 48
}
}

Before we talk about this, let's try it! Find your browser and click the heart. That hit the AJAX endpoint. Go back to the terminal. Yes! There it is at the bottom. Hit Ctrl+C to exit tail.

Service Autowiring

Ok cool! But... how the heck did that work? Here's the deal: before Symfony executes our controller, it looks at each argument. For simple arguments like $slug, it passes us the wildcard value from the router:

51 lines src/Controller/ArticleController.php
... lines 1 - 10
class ArticleController extends AbstractController
{
... lines 13 - 38
/**
* @Route("/news/{slug}/heart", name="article_toggle_heart", methods={"POST"})
*/
public function toggleArticleHeart($slug, LoggerInterface $logger)
{
... lines 44 - 48
}
}

But for $logger, it looks at the type-hint and realizes that we want Symfony to pass us the logger object. Oh, and the order of the arguments does not matter.

This is a very powerful idea called autowiring: if you need a service object, you just need to know the correct type-hint to use! So... how the heck did I know to use LoggerInterface? Well, of course, if you look at the official Symfony docs about the logger, it'll tell you. But, there's a cooler way.

Go to your terminal and run:

./bin/console debug:autowiring

Boom! This is a full list of all of the type-hints that you can use to get a service. Notice that most of them say that they are an alias to something. Don't worry about that too much: like routes, each service has an internal name you can use to reference it. We'll learn more about that later. Oh, and whenever you install a new package, you'll get more and more services in this list. More tools!

Using Twig Directly

And check this out! If you want to get the Twig service, you can use either of these two type-hints.

And remember how I said that everything in Symfony is done by a service? Well, when we call $this->render() in a controller, that's just a shortcut to fetch the Twig service and call a method on it:

51 lines src/Controller/ArticleController.php
... lines 1 - 10
class ArticleController extends AbstractController
{
... lines 13 - 23
public function show($slug)
{
... lines 26 - 31
return $this->render('article/show.html.twig', [
... lines 33 - 35
]);
}
... lines 38 - 49
}

In fact, let's pretend that the $this->render() shortcut does not exist. How could we render a template? No problem: we just need the Twig service. Add a second argument with an Environment type-hint, because that's the class name we saw in debug:autowiring. Call the arg $twigEnvironment:

54 lines src/Controller/ArticleController.php
... lines 1 - 9
use Twig\Environment;
class ArticleController extends AbstractController
{
... lines 14 - 24
public function show($slug, Environment $twigEnvironment)
{
... lines 27 - 39
}
... lines 41 - 52
}

Next, change the return statement to be $html = $twigEnvironment->render():

54 lines src/Controller/ArticleController.php
... lines 1 - 9
use Twig\Environment;
class ArticleController extends AbstractController
{
... lines 14 - 24
public function show($slug, Environment $twigEnvironment)
{
... lines 27 - 32
$html = $twigEnvironment->render('article/show.html.twig', [
'title' => ucwords(str_replace('-', ' ', $slug)),
'slug' => $slug,
'comments' => $comments,
]);
... lines 38 - 39
}
... lines 41 - 52
}

The method we want to call on the Twig object is coincidentally the same as the controller shortcut.

Then at the bottom, return new Response() and pass $html:

54 lines src/Controller/ArticleController.php
... lines 1 - 8
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;
class ArticleController extends AbstractController
{
... lines 14 - 24
public function show($slug, Environment $twigEnvironment)
{
... lines 27 - 32
$html = $twigEnvironment->render('article/show.html.twig', [
'title' => ucwords(str_replace('-', ' ', $slug)),
'slug' => $slug,
'comments' => $comments,
]);
return new Response($html);
}
... lines 41 - 52
}

Ok, this is way more work than before... and I would not do this in a real project. But, I wanted to prove a point: when you use the $this->render() shortcut method on the controller, all it really does is call render() on the Twig service and then wrap it inside a Response object for you.

Try it! Go back and refresh the page. It works exactly like before! Of course we will use shortcut methods, because they make our life way more awesome. I'll change my code back to look like it did before. But the point is this: everything is done by a service. If you learn to master services, you can do anything from anywhere in Symfony.

There's a lot more to say about the topic of services, and so many other parts of Symfony: configuration, Doctrine & the database, forms, Security and APIs, to just name a few. The Space Bar is far from being the galactic information source that we know it will be!

But, congrats! You just spent an hour getting an awesome foundation in Symfony. You will not regret your hard work: you're on your way to building great things and, as always, becoming a better and better developer.

Alright guys, seeya next time!

Leave a comment!

  • 2018-08-07 Mike

    The new debug:autowiring over at Github looks so cool! Hopefully we'll see it soon! Thanks, you're awesome!

  • 2018-08-06 weaverryan

    Hey Mike!

    GREAT question :). And sorry for my slow reply - I was away last week. Here's the PR where I added debug:autowiring: https://github.com/symfony/...

    Basically, in Symfony 3.4, the autowiring types become much more important. And, the debug:container --types is a long name, and not easy to discover (you wouldn't easily be able to figure out that this command has a --types option). I also wanted something with a bit less information - hopefully something that looks MORE clean, not less clean ;).

    But, this was also a "rushed" feature - I introduced this after the Symfony 3.4 feature freeze, and so it needed to be done quickly and not touch any other parts of the system. We're actively talking about how to not have these 2 separate commands anymore, and, instead, to make one command that really shows the BEST things possible. Here is some info: https://github.com/symfony/...

    Overall, they how the same info, just displayed differently.

    Cheers!

  • 2018-07-31 Mike

    Whats the difference between:

    ./bin/console debug:autowiring
    and
    ./bin/console debug:container --types

    I had the other command from a SF3 Tutorial of you. It looks more clean in the terminal.

  • 2018-07-09 Victor Bocharsky

    Hey Sarah,

    Well, Symfony suggests using Twig as a template engine, and the question is: why don't you enough Twig that is secure and powerful? But if you need a few engines like Twig and PHP - I think that's OK to use both, it depends on your business logic.

    Cheers!

  • 2018-07-08 Sarah Vrielinck

    Is it also considered good practice(or does it make any sense?) if you constructor inject 'EngineInterface' and declare 'templating engines' in the 'framework.yml' file?

  • 2018-06-15 Victor Bocharsky

    Hey Hansi,

    If you want to unit-testing it - so yeah, it makes sense to inject Twig service. And that's totally fine I think. But mostly controllers are tested with functional tests, so it's ok to use shortcuts.

    Cheers!

  • 2018-06-15 Hansi Hanson

    Actually, i think it is good practice to inject the Environment service into your action and to not use the render shortcut.
    This way you can mock the twig rendering for unit testing.

  • 2018-05-07 Victor Bocharsky

    Hey Sandeep,

    This file is created when you load application in "dev" environment. But this path is different for Symfony 3 applications where logs are written in "app/logs/dev.log" or "var/logs/dev.log". So you need to figure out the path to logs for your *project*. But really, if you don't see logs file when load Symfony application and you even can't load the application at all, i.e. you see some server error (not Symfony error page) - then probably you need to check logs of your web server like Nginx / Apache.

    Cheers!

  • 2018-05-05 sandeep sangole

    Hi there , even I am not seeing dev.log inside log folder. How did you get it ?

  • 2018-03-05 agentolivia

    Thanks for the extra set of eyes and the help!

  • 2018-03-05 Victor Bocharsky

    Hey agentolivia ,

    Easy fix ;)

    Just remove slash from the beginning of the path, i.e.

    tail -f var/log/dev.log

    When you start with "/" - you look for the file relative to the root of your file system, but we need to look for it relative to the Symfony project directory. And as you can see, we don't have leading slash in paths we have in this screencast.

    Cheers!

  • 2018-03-04 agentolivia

    I'm getting this error when I run `tail -f /var/log/dev.log`:

    tail: /var/log/dev.log: No such file or directory

    There is indeed no such file in /var/log. Is this a permissions problem maybe? Here's what I have:

    /var/log
    drwxr-xr-x
    owner: root
    group: wheel

    I haven't changed any of the default configuration that was installed with monolog. Here is my config/packages/dev/monolog.yaml:

    ```yaml

    monolog:
    handlers:
    main:
    type: stream
    path: "%kernel.logs_dir%/%kernel.environment%.log"
    level: debug
    channels: ["!event"]
    # uncomment to get logging in your browser
    # you may have to allow bigger header sizes in your Web server configuration
    #firephp:
    # type: firephp
    # level: info
    #chromephp:
    # type: chromephp
    # level: info
    console:
    type: console
    process_psr_3_messages: false
    channels: ["!event", "!doctrine", "!console"]
    ```

  • 2018-02-19 Victor Bocharsky

    Hey Jay,

    Oh, unfortunately there's no tail command on Windows out of the box. Probably, you can install it somehow, but you can just open the file in your favorite text editor (or even in PhpStorm) and look over it. Maybe PhpStorm's console has an integration of tail command, I'm not sure but you can check it. Also, IIRC, I had "tail" command in the Git Bash - a terminal installed with Git on Windows OS. Try to find "git bash" in your system if you already have Git and run the command there.

    Cheers!

  • 2018-02-16 Jay Kay

    How should command "tail -f var/log/dev.log" look in windows?

  • 2018-02-07 Junaid Farooq

    Thanks Victor

  • 2018-02-06 Victor Bocharsky

    Hey Junaid,

    Yes! See Symfony 4 track for more courses: https://knpuniversity.com/t... - there're only . courses yet, there will be more soon.

    You can also check Symfony 3 track: https://knpuniversity.com/t... - there much more topics covered, but you will need to do it in Symfony 4 way using Flex like we show here.

    Cheers!

  • 2018-02-06 Junaid Farooq

    Is there going to be any addition to this course. This is my first ever introduction to Symfony framework and I have already started to like it.

  • 2018-01-31 Victor Bocharsky

    Haha, great! And you're welcome!

    Happy learning ;)

  • 2018-01-31 shing

    Fwah!. What a mental workout! I get it now! Awesome. Thanks for taking the time.
    👍👍👍👍👍👍👍👍

  • 2018-01-30 Victor Bocharsky

    Hey @shing,

    I just leave my comments, but in general you almost got it, probably even better than I think you did ;)

    > By default all services are lazy loaded.
    If you mean here that services are created not on the definition step but on its actual call, when you fetching them from DIC, then yes :)

    > But for constructor service injection, all services in the constructor will be instantiated and maybe not all services are going to be called.
    This sounds a bit weird, because if you don't use service in the class where you pass it - then question: why do you pass it?? :) So, you should always pass as dependencies only those services you actually use, otherwise just remove it if you're not going to use it at all.

    But if you mean here that not all dependencies are *often* use inside the service, then it makes sense. For example, you have a custom mailer service with 20 public methods, i.e. you send emails in 20 different use cases. But all those emails are just simple text without HTML markup, and only in *one* method you need to use HTML markup to make it looks really pretty. And due to this you want to inject Twig service (of course because it's much fun to render HTML templates with Twig). So this Twig dependency is used really rare. And that's the case when you probably don't want to instantiate this Twig service in constructor, because you really need it for only one method. And that's where you probably want to make this Twig service a "Lazy Service". This is a simple example, but I think is enough to understand this concept. And in this case:

    > This strict definition of (lazy:true) will make constructor service injection wait till the method is called to instantiate the service. Which gives this the title of "Lazy Services".

    Yes, if you mean that DIC will pass to the constructor proxy objects instead of real dependencies, and real dependencies will be instantiated when you actually call any method on them

    P.S. But really, forget about those "Lazy Services", because you probably don't really need them at initial stage, well, if you're not developing some enterprise project ;) What you really need to understand is that all services in DIC are instantiated not during its definition in YAML files but when you actually call them. For example, if you declare MyAwesomeService service in YAML, and you will never use it - it'll be *never* instantiated. It will be instantiated only when you get from the container ( ->get(MyAwesomeService::class) ) or when you inject it in a service which you use.

    Cheers!

  • 2018-01-30 shing

    Hey Victor. I think I'm almost there.

    By default all services are lazy loaded. There are 3 types of service injection. Constructor, setter and property. But for constructor service injection, all services in the constructor will be instantiated and maybe not all services are going to be called.

    For that case of using a constructor service injection, I add an extra definition to my services.yaml


    services:
    App\Twig\AppExtension:
    lazy: true


    This strict definition of (lazy:true) will make constructor service injection wait till the method is called to instantiate the service. Which gives this the title of "Lazy Services".

    Sorry for pestering on this concept. Many, many, many thanks for the help.

  • 2018-01-29 Victor Bocharsky

    Hey @shing,

    Well, "Lazy Services" allows you to postpone instantiating objects, i.e. with lazy services DIC instantiates services on its actual use in the code, not during injecting them in constructors. Actually, DIC injects a lightweight version of the "lazy service" which called a "proxy", so in constructor the proxy is passed, not a real object of lazy service. But if you start using the lazy service, proxy instantiates a real object of the lazy service. So, as you can see it's useful when you have many methods in your service, but actually use the injected services only a few of them and when your injected service is a really huge and consume many resources during instantiating. But if your services are lightweight, probably you don't need lazy services at all to avoid complecating things. IIRC, we don't have lazy services at all in KnpU.

    It's called Setter Injection, and yes, its idea is simpler than "lazy services" but also a bit different. Setter injection gives you optional injection, when you have optional dependencies. So you may or may not inject the service via setter method and so change behavior of the service, fo example do some measurement during development but do nothing in production.

    Cheers!

  • 2018-01-27 shing

    Ah ok! I think I get it. "Lazy Services" are when a class calls upon many services in the constructor. When the class is instantiated it loads up all the services but maybe the the called method doesnt need the service so it doesnt load up.

    While by default DIC, if just injecting the service through a method parameter, it'll just do that when the method is called. The service is added in the constructor of the calling class so its not loaded up anyways.

  • 2018-01-26 Victor Bocharsky

    Hey @shing,

    Ah, forget about Lazy Services. Actually, I meant a bit different when said they are "lazy loaded", sorry! :)

    What I was talking about is that DIC do not create real objects at all when Symfony starts, i.e. services are not created until they are actually called, so until then DIC only *knows* how to create them but does not have its object created. For example, if you declare "App\Service\Mailer" service in your "services.yml", the real object of "App\Service\Mailer" class won't be created by DIC until you explicitly call it e.g. when you call "$mailer = $this->get(App\Service\Mailer::class);", or, for example, when you call another service, which has this "App\Service\Mailer" service as a dependency in __construct().

    Hope this clarify things much more for you.

    But "Lazy Services" is a bit different term, as you can see from the docs: https://symfony.com/doc/cur... ;)

    Cheers!

  • 2018-01-25 shing

    Cool. That clears things up a bit.

    However the documentation https://symfony.com/doc/cur... says to require ocramius/proxy-manager for lazy services instantiation and to declare it explicitly in the configuration with lazy:true. Why is ocramius/proxy-manager needed with services are already lazy loaded?

  • 2018-01-25 Victor Bocharsky

    Hey @shing,

    Nope, services are lazy loaded thanks to the Symfony's Dependency Injection Container (DIC). DIC instantiates a service when you actually call it for the first time, and then while it was already instantiated, DIC returns the same object on every next service call instead of creating new objects. So if you have some services which are not called during the request - DIC does not instantiate them at all.

    Cheers!

  • 2018-01-25 shing

    Do all services get instantiated when symfony starts? Even services that you do not need. Thxs in advance.