Buy

Making all Services Private

There is one more key that lives under _defaults in a new Symfony 3.3 project: public: false:

42 lines app/config/services.yml
... lines 1 - 8
services:
_defaults:
... lines 11 - 12
public: false
... lines 14 - 42

The idea of public versus private services is not new in Symfony 3.3... but defaulting all services to private is new, and it's a critical change. Thanks to this, every service in this file is now private. What does that mean? One simple thing: when a service is private, you cannot fetch it directly from the container via $container->get(). So, for example, $container->get('AppBundle\Service\MarkdownTransformer') will not work.

Tip

In Symfony 3.3, sometimes you can fetch a private service directly from the container. But doing this has been deprecated and will be removed in Symfony 4.

But, everything else works the same: I can pass this service as an argument and it can be autowired into an argument. Only the $container->get() usage changed.

In our app, making this change is safe! We just created all of these service ids a minute ago and they're not being used anywhere yet, definitely not with $container->get(). The exception is the last two services: we might be fetching these services directly from the container. And in fact, I know we are: in src/AppBundle/Controller/Admin/GenusAdminController.php. Down in editAction(), we're fetching both services via $this->get(), which is a shortcut for $this->container->get():

96 lines src/AppBundle/Controller/Admin/GenusAdminController.php
... lines 1 - 15
class GenusAdminController extends Controller
{
... lines 18 - 63
public function editAction(Request $request, Genus $genus)
{
... lines 66 - 69
if ($form->isSubmitted() && $form->isValid()) {
... lines 71 - 76
$this->addFlash(
'success',
$this->get('app.encouraging_message_generator')->getMessage()
);
... lines 81 - 84
} elseif ($form->isSubmitted()) {
$this->addFlash(
'error',
$this->get('app.discouraging_message_generator')->getMessage()
);
}
... lines 91 - 94
}
}

That means, for now, to keep our app working, add public: true under each service:

42 lines app/config/services.yml
... lines 1 - 8
services:
... lines 10 - 30
app.encouraging_message_generator:
... lines 32 - 34
public: true
... line 36
app.discouraging_message_generator:
... lines 38 - 40
public: true

Aliases can also be private... but in legacy_aliases.yml, there is no _defaults key with public: false. So, these are all public aliases... which is exactly what we want, ya know, because we're trying not to break our app!

Like with every step so far, our app should still work fine. Woohoo! But... you may be wondering why we made the services private. Doesn't this just make our services harder to use? As we'll learn soon, when you use private services, it becomes impossible to make a mistake and accidentally reference a non-existent service. Private services are going to make our app even more dependable than before. And also, a little bit faster.

Next, let's talk about the biggest change to this file: auto-registration of all classes in src/AppBundle as services. Woh.

Leave a comment!

  • 2017-08-25 weaverryan

    Yo Mike!

    Actually, at this exact moment in the tutorial, we're still using $this->get('app.markdown_transformer'), which in legacy_aliases.yml (and those aliases are public). We *do* change to use the private, class id later - https://knpuniversity.com/s... - and we see an error and refactor to a controller argument.

    But yea, you're right - you can't fetch a private service from the container. But instead of making it public, in that later chapter, we use special controller argument dependency injection!

    Cheers!

  • 2017-08-25 Mike

    Well one more service is used via the container: $transformer = $this->get('AppBundle\Service\MarkdownTransformer');
    in the new genusController showAction(). So this has to be public as well.