Services: The Backbone of Everything
Let's talk about services. These are the most important concept in Symfony. And once you understand them, honestly, you'll be able to do anything.
What is a Service?
First, a service is an object that does work. That's it. For example, if you instantiated a Logger object that has a log() method, that's a service! It does work: it logs things! Or if you created a database connection object that makes queries to the database then... yup! That's a service too.
So then... if a service is just an object that does work... what lazy objects aren't services? Our Starship class is a perfect example of a non service. It's main job is not to do work: it's to hold data. Sure, it has a few public methods... and you could even put some logic inside of these methods to do something. But ultimately, it's not a worker, it's a data holder.
What about controller classes? Yeah, they're services too. Their work is to create response objects.
Anyway, every bit of work that's done in Symfony is actually done by a service. Writing log messages to this file? Yeah, there's a service for that. Figuring out which route matches the current URL? That's the router service! What about rendering a twig template? Yep, it turns out that the render() method is a shortcut to find the correct service object and call a method on it.
The Container & debug:container
You may sometimes also hear that these services are organized into a big object called the "service container". You can think of the container like a giant associative array of service objects, each with a unique id. Want to see a list of every service in our app right now? Me too!
Find your terminal and run:
bin/console debug:container
That's a lot of services! Let me make this smaller so each fits on its own line... better.
On the left side, we see the ID of each service. And on the right, the class of the object that the ID corresponds to. Cool, right?
Go back to our controller and hold control or command to open up the json() method again. Now this makes more sense! It's checking to see if the container has a service whose ID is serializer. If it does, it grabs that service from the container and calls the serialize() method on it.
When we work with services, it won't look exactly like this. But the super important thing is that we now understand what's going on.
Bundles Provide Services
My next question is: where do these services come from? Like, who says there's a service whose ID is twig... and that when we ask the container for it, it should return a twig Environment object? The answer is: entirely from bundles. In fact, that's the main point of installing a new bundle. Bundles give us services.
Remember when we installed twig? It added a bundle to our app. And guess what that bundle did? Yup: it gave us new services, including the twig service. Bundles give us services... and services are tools.
Autowiring
And though there are many services in this list, the vast majority of these are low-level service objects that we won't ever use or care about. We also won't care about the ID of the services most of the time.
Instead, run a related command called:
php bin/console debug:autowiring
This shows us all the services that are autowireable, which is the technique that we'll use to fetch services. It's basically a curated list of the services that we're most likely to need.
Autowiring the Logger Service
So let's do a challenge: let's log something from our controller. Here's a sneak peek into how I approach this problem in my brain:
Ok, I need to log something! And... logging is work. And... services do work! Thus, there must be a logger service that I can use! Quod erat demonstrandum!
Forgive me latin nerds. The point is: if we want to log something, we just need to find the service that does that work. Okay! Rerun the command but search for log:
php bin/console debug:autowiring log
Boom! It found about 10 services, all starting with Psr\Log\LoggerInterface. We're going to talk about what these other services are in the next tutorial. For now, focus on the main one. This tells me is that there is a service in the container for a logger. And to get it, we can autowire it using this interface.
What does that mean? In the controller method where we want the logger, add an argument type-hinted with LoggerInterface - hit tab - then say $logger.
| // ... lines 1 - 5 | |
| use Psr\Log\LoggerInterface; | |
| // ... lines 7 - 10 | |
| class StarshipApiController extends AbstractController | |
| { | |
| // ... line 13 | |
| public function getCollection(LoggerInterface $logger): Response | |
| { | |
| // ... lines 16 - 41 | |
| } | |
| } |
In this case, the name of the argument isn't important: it could be anything. What matters is that the LoggerInterface - that corresponds to this use statement - matches the Psr\Log\LoggerInterface from debug:autowiring.
It's that simple! Symfony will see this type-hint and say:
Oh! Since that type-hint matches the autowiring type for this service, they must want me to pass them that service object.
I don't know why Symfony sounds like a frog in my head. Anyway, let's see if this works. Add dd($logger): dd() stands for "dump and die" and comes from Symfony.
| // ... lines 1 - 5 | |
| use Psr\Log\LoggerInterface; | |
| // ... lines 7 - 10 | |
| class StarshipApiController extends AbstractController | |
| { | |
| // ... line 13 | |
| public function getCollection(LoggerInterface $logger): Response | |
| { | |
| dd($logger); | |
| // ... lines 17 - 41 | |
| } | |
| } |
Refresh! Yes! It printed the object beautifully then stopped execution. It's working! Symfony passes us a Monolog\Logger object, which implements that LoggerInterface.
The trick we just did - called autowiring - works in exactly two places: our controller methods and the __construct() method of any service. We'll see that second situation in the next chapter.
Controlling how Services Behave
And if you're wondering where this Logger service came from in the first place... we already know the answer! From a bundle. In this case, MonologBundle. And... how could we configure that service... to, I don't know, log to a different file? The answer is: config/packages/monolog.yaml.
This config - including this line - configures MonologBundle... which really means that it configures how the services work that MonologBundle give us. We'll learn about this percent syntax in the next tutorial, but this tells the Logger service to log to this dev.log file.
Using the Logger
Ok, now that we have the Logger service, let's use it! How? Well, of course, you can read the docs. But thanks to the type-hint, our editor will help us! LoggerInterface has a bunch of methods. Let's use ->info() and say:
| // ... lines 1 - 5 | |
| use Psr\Log\LoggerInterface; | |
| // ... lines 7 - 10 | |
| class StarshipApiController extends AbstractController | |
| { | |
| // ... line 13 | |
| public function getCollection(LoggerInterface $logger): Response | |
| { | |
| $logger->info('Starship collection retrieved'); | |
| // ... lines 17 - 41 | |
| } | |
| } |
Starship collection retrieved.
Try it out: refresh. The page worked... but did it log anything? We could go check the dev.log file. Or, we can use the Log section of the profiler for this request.
Seeing the Profiler for an API Request
But... wait! This is an API request... so we don't have that cool web debug toolbar on the bottom! That's true... but Symfony did still collect all that info! To get to the profiler for this request, change the URL to /_profiler. This lists the most recent requests to our app, with the newest on top. See this one? That's our API request from a minute ago! If you click this token... boom! We're looking at the profiler for that API call in all its glory... including a Log section... with our message.
Ok, now that we've seen how to use a service, let's create our own service next! We're unstoppable!
21 Comments
Why is $logger used as an object but it's named as LoggerInterface ?I think quite a few other objects are autowired via "Xinterface $xobject" dependence injection.
Hey John,
Yeah, most of core objects you type hint with an interface that the real object extends. Mostly, that's done for flexibility, as you can pass any object that extends that interface (Symfony has many objects which are wrapped with another object). And finally, that's just a good OOP concept when you typhint with interfaces instead of end objects if you have them. For your own objects it's redundant to add interfaces if you don't have benefits from it, but from some third-party services it has more sense as most of them already implement some interfaces, extend some base cases, etc.
But for you, to know how to typehint - you can leverage Symfony's
bin/console debug:autowiringcommand that will show you what exactly type-hint and variable name you need to use to inject a proper service/object.I hope it helps!
Cheers!
Hey @John-S,
Great question!
In practice, I typically always use controller method injection but it really comes down to personal/project/team preference. I find controllers easier to read when all their requirements are injected into the method.
sorry. deleted.(not sure if needed because you mention this in next chapter. pls delete if not needed). Deleted post:
Two different ways of injecting logger service.
1.
class TestController
{
}
2.
class TestController
{
}
I intend to use logger in several methods but not all of them. Should i use constructor injection or inject it in every method ?
Hello,
In api controller, there is no black bar at bottom for debug. Is it possible to enable for this page also? What is actual reason that this bar is seen on MainController pages, but not ApiController pages?
thank you
Hey @Mahmut-A!
Great question! There's no web debug toolbar because this is a json response. The toolbar can only be shown on html responses.
There's still a way you can see the profile for this request though! All requests, regardless of the response type, are saved. After making a request to
/api/starshipsvisit/_profiler. You should see a list of recent requests - you can even search them! Find the one to/api/starshipsand click the token. That will be the full profile forthat request.
Hope that help!
--Kevin
Hi,
I was following the tutorial but when i enter the log section of _profiler I have a deprecation with the message "Please install the "intl" PHP extension for best performance." I wish if someone could help me with a possible solution.
I tried to require symfony/intl but it couldn't fix the error.
Thanks
Hey @Jaime-Garcia
It depends on your local PHP installation and the system you use for development. It asks you to install a
php-intlextension to PHPIf you provide more details on you system and PHP installataion then I can provide you more detail help.
Cheers
Hi, sorry for the time spent on answering, I have windows 11 and php on 8.3.12 (cli) (NTS Visual C++ 2019 x64)
as I see, it's a windows php version, not a WSL one, so you have to find php.ini and enable php-intl module there
How can we change the logger config to log into the datadog log server for example or another external log server?
Hey Ahmad,
Not sure about datadog but for example you can log into Loggly logs server. You can see the required config in the bundle's configuration file: https://github.com/symfony/monolog-bundle/blob/06ddd76fd24dcf325a61e826b5c799e4b929422e/DependencyInjection/Configuration.php#L318-L322
There you can search for other log platforms there like rollbar, etc.
Cheers!
Hello,
I wonder what exactly means autowiring? because, we need to log service. but lets say X service. how can we know, we will search it by ./php/console debug:autowiring X.. Why do we need autowiring to search some services to find?
could you please help me to make it clear?
thank you
Hey Mahmut,
Autowiring helps you to inject services with type-hints, i.e. in your action you can literally write the typehint of the class or interface from the
debug:autowiringcommand output and the system will inject that service into your action, e.g.:That's so simple. As long as a service is listed in the
debug:autowiringoutput - it will be able to typehint this way in your controller actions or__construct()methods of our services.How can you know the service name? That's a good question! Well, first of all, you probably should base on the documentation, either official Symfony docs or bundle's docs in case you're going to use a service from a bundle. Or, you can just "guess" it, like if you need a cache search, just search something like "cache" in the output and see if you have any matches. But mostly, I would recommend you to base on docs in the beginning, unless you get used to already known services and will know exactly what to look for.
I hope it's clearer for you now!
Cheers!
thank you Victor. I will add something:
for example serializer is a service. I am doing. ./bin/console debug:autowiring serialize and there are aliases in output.
I also saw this service id in debug:container. However, I could not find related class information in bundless.php.
for example, below is outpu of autowiring serialize. what is class to be seen in bundles.php? how can ı detect it?
thank you
The following classes & interfaces can be used as type-hints when autowiring:
(only showing classes/interfaces matching serialize)
Symfony\Component\Messenger\Transport\Serialization\SerializerInterface - alias:messenger.transport.native_php_serializer
Symfony\Component\Serializer\Encoder\DecoderInterface - alias:serializer
Symfony\Component\Serializer\Encoder\EncoderInterface - alias:serializer
Hey Mahmut,
Don't forget that extra services may come not only from bundles but only from pure PHP packages called Symfony components. Components are not bundles, they do not contain any configuration. For example that Serializer component's configuration is come from the core FrameworkBundle that is installed but default in our Symfony app, but the actual classes are available only after you install the component with Composer.
But as I already said, if you're not sure - please, better check the docs instead of guessing, that's the easiest and fastest way probably :)
Cheers!
How are you getting the nice icons on your project folders in PHP Storm? I assume it's from a particular theme?
Thanks for the great videos. I'm a long-time fan! Even when I already know most of what you are talking about, I always pick up helpful tips to make things just that much better.
Hey @Ernest-H
These nice icons are from https://plugins.jetbrains.com/plugin/10044-atom-material-icons IIRC
Thanks for the feedback. Cheers and happy coding!
Hi. Thank you so much for you helpful videos. A i added a point. The bundles file have the environment activation for the bundle. For example: dev => true, the bundle will be active only on dev environment.
Hi @Inforob,
Yeah that's a good point. This file was mentioned in the chapter 5 of this course, and as it was said previously in most cases you will not edit it manually, every installed bundle will be registered there with proper env activation param depending on how you installed it and recipes system configuration. Of course on custom bundles you will need to choose how to activate them.
Cheers!
"Houston: no signs of life"
Start the conversation!