mirror of
https://github.com/symfony/symfony-docs.git
synced 2026-03-24 00:32:14 +01:00
867 lines
33 KiB
ReStructuredText
867 lines
33 KiB
ReStructuredText
The Symfony 3.3 DI Container Changes Explained (autowiring, _defaults, etc)
|
|
===========================================================================
|
|
|
|
If you look at the ``services.yaml`` file in a new Symfony 3.3 or newer project, you'll
|
|
notice some big changes: ``_defaults``, ``autowiring``, ``autoconfigure`` and more.
|
|
These features are designed to *automate* configuration and make development faster,
|
|
without sacrificing predictability, which is very important! Another goal is to make
|
|
controllers and services behave more consistently. In Symfony 3.3, controllers *are*
|
|
services by default.
|
|
|
|
The documentation has already been updated to assume you have these new features
|
|
enabled. If you're an existing Symfony user and want to understand the "what"
|
|
and "why" behind these changes, this article is for you!
|
|
|
|
All Changes are Optional
|
|
------------------------
|
|
|
|
Most importantly, **you can upgrade to Symfony 3.3 today without making any changes to your app**.
|
|
Symfony has a strict :doc:`backwards compatibility promise </contributing/code/bc>`,
|
|
which means it's always safe to upgrade across minor versions.
|
|
|
|
All of the new features are **optional**: they are not enabled by default, so you
|
|
need to actually change your configuration files to use them.
|
|
|
|
.. _`service-33-default_definition`:
|
|
|
|
The new Default services.yaml File
|
|
----------------------------------
|
|
|
|
To understand the changes, look at the new default ``services.yaml`` file (this is
|
|
what the file looks like in Symfony 4):
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/services.yaml
|
|
services:
|
|
# default configuration for services in *this* file
|
|
_defaults:
|
|
autowire: true # Automatically injects dependencies in your services.
|
|
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
|
public: false # Allows optimizing the container by removing unused services; this also means
|
|
# fetching services directly from the container via $container->get() won't work.
|
|
# The best practice is to be explicit about your dependencies anyway.
|
|
|
|
# makes classes in src/ available to be used as services
|
|
# this creates a service per class whose id is the fully-qualified class name
|
|
App\:
|
|
resource: '../src/*'
|
|
exclude: '../src/{Entity,Migrations,Tests}'
|
|
|
|
# controllers are imported separately to make sure services can be injected
|
|
# as action arguments even if you don't extend any base controller class
|
|
App\Controller\:
|
|
resource: '../src/Controller'
|
|
tags: ['controller.service_arguments']
|
|
|
|
# add more service definitions when explicit configuration is needed
|
|
# please note that last definitions always *replace* previous ones
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/services.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd">
|
|
|
|
<services>
|
|
<defaults autowire="true" autoconfigure="true" public="false"/>
|
|
|
|
<prototype namespace="App\" resource="../src/*" exclude="../src/{Entity,Migrations,Tests}"/>
|
|
|
|
<prototype namespace="App\Controller\" resource="../src/Controller">
|
|
<tag name="controller.service_arguments"/>
|
|
</prototype>
|
|
|
|
<!-- add more services, or override services that need manual wiring -->
|
|
</services>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/services.php
|
|
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
|
|
|
return function(ContainerConfigurator $configurator) {
|
|
// default configuration for services in *this* file
|
|
$services = $configurator->services()
|
|
->defaults()
|
|
->autowire() // Automatically injects dependencies in your services.
|
|
->autoconfigure() // Automatically registers your services as commands, event subscribers, etc.
|
|
;
|
|
|
|
// makes classes in src/ available to be used as services
|
|
// this creates a service per class whose id is the fully-qualified class name
|
|
$services->load('App\\', '../src/*')
|
|
->exclude('../src/{Entity,Migrations,Tests}');
|
|
|
|
// controllers are imported separately to make sure services can be injected
|
|
// as action arguments even if you don't extend any base controller class
|
|
$services->load('App\\Controller\\', '../src/Controller')
|
|
->tag('controller.service_arguments');
|
|
|
|
// add more service definitions when explicit configuration is needed
|
|
// please note that last definitions always *replace* previous ones
|
|
};
|
|
|
|
This small bit of configuration contains a paradigm shift of how services
|
|
are configured in Symfony.
|
|
|
|
.. _`service-33-changes-automatic-registration`:
|
|
|
|
1) Services are Loaded Automatically
|
|
------------------------------------
|
|
|
|
.. seealso::
|
|
|
|
Read the documentation for :ref:`automatic service loading <service-psr4-loader>`.
|
|
|
|
The first big change is that services do *not* need to be defined one-by-one anymore,
|
|
thanks to the following config:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/services.yaml
|
|
services:
|
|
# ...
|
|
|
|
# makes classes in src/ available to be used as services
|
|
# this creates a service per class whose id is the fully-qualified class name
|
|
App\:
|
|
resource: '../src/*'
|
|
exclude: '../src/{Entity,Migrations,Tests}'
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/services.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd">
|
|
|
|
<services>
|
|
<!-- ... -->
|
|
|
|
<prototype namespace="App\" resource="../src/*" exclude="../src/{Entity,Migrations,Tests}"/>
|
|
</services>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/services.php
|
|
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
|
|
|
return function(ContainerConfigurator $configurator) {
|
|
// ...
|
|
|
|
// makes classes in src/ available to be used as services
|
|
// this creates a service per class whose id is the fully-qualified class name
|
|
$services->load('App\\', '../src/*')
|
|
->exclude('../src/{Entity,Migrations,Tests}');
|
|
};
|
|
|
|
This means that every class in ``src/`` is *available* to be used as a
|
|
service. And thanks to the ``_defaults`` section at the top of the file, all of
|
|
these services are **autowired** and **private** (i.e. ``public: false``).
|
|
|
|
The service ids are equal to the class name (e.g. ``App\Service\InvoiceGenerator``).
|
|
And that's another change you'll notice in Symfony 3.3: we recommend that you use
|
|
the class name as your service id, unless you have :ref:`multiple services for the same class <services-explicitly-configure-wire-services>`.
|
|
|
|
But how does the container know the arguments to my services?
|
|
|
|
Since each service is :ref:`autowired <services-autowire>`, the container is able
|
|
to determine most arguments automatically. But, you can always override the service
|
|
and :ref:`manually configure arguments <services-manually-wire-args>` or anything
|
|
else special about your service.
|
|
|
|
But wait, if I have some model (non-service) classes in my ``src/``
|
|
directory, doesn't this mean that *they* will also be registered as services?
|
|
Isn't that a problem?
|
|
|
|
Actually, this is *not* a problem. Since all the new services are :ref:`private <container-public>`
|
|
(thanks to ``_defaults``), if any of the services are *not* used in your code, they're
|
|
automatically removed from the compiled container. This means that the number of
|
|
services in your container should be the *same* whether your explicitly configure
|
|
each service or load them all at once with this method.
|
|
|
|
Ok, but can I exclude some paths that I *know* won't contain services?
|
|
|
|
Yes! The ``exclude`` key is a glob pattern that can be used to *ignore* paths
|
|
that you do *not* want to be included as services. But, since unused services are
|
|
automatically removed from the container, ``exclude`` is not that important. The
|
|
biggest benefit is that those paths are not *tracked* by the container, and so may
|
|
result in the container needing to be rebuilt less-often in the ``dev`` environment.
|
|
|
|
2) Autowiring by Default: Use Type-hint instead of Service id
|
|
-------------------------------------------------------------
|
|
|
|
The second big change is that autowiring is enabled (via ``_defaults``) for all
|
|
services you register. This also means that service id's are now *less* important
|
|
and "types" (i.e. class or interface names) are now *more* important.
|
|
|
|
For example, before Symfony 3.3 (and this is still allowed), you could pass one
|
|
service as an argument to another with the following config:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/services.yaml
|
|
services:
|
|
app.invoice_generator:
|
|
class: App\Service\InvoiceGenerator
|
|
|
|
app.invoice_mailer:
|
|
class: App\Service\InvoiceMailer
|
|
arguments:
|
|
- '@app.invoice_generator'
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/services.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd">
|
|
|
|
<services>
|
|
<service id="app.invoice_generator"
|
|
class="App\Service\InvoiceGenerator"/>
|
|
|
|
<service id="app.invoice_mailer"
|
|
class="App\Service\InvoiceMailer">
|
|
|
|
<argument type="service" id="app.invoice_generator"/>
|
|
</service>
|
|
</services>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/services.php
|
|
use App\Service\InvoiceGenerator;
|
|
use App\Service\InvoiceMailer;
|
|
use Symfony\Component\DependencyInjection\Reference;
|
|
|
|
$container->register('app.invoice_generator', InvoiceGenerator::class);
|
|
$container->register('app.invoice_mailer', InvoiceMailer::class)
|
|
->setArguments([new Reference('app.invoice_generator')]);
|
|
|
|
To pass the ``InvoiceGenerator`` as an argument to ``InvoiceMailer``, you needed
|
|
to specify the service's *id* as an argument: ``app.invoice_generator``. Service
|
|
id's were the main way that you configured things.
|
|
|
|
But in Symfony 3.3, thanks to autowiring, all you need to do is type-hint the
|
|
argument with ``InvoiceGenerator``::
|
|
|
|
// src/Service/InvoiceMailer.php
|
|
// ...
|
|
|
|
class InvoiceMailer
|
|
{
|
|
private $generator;
|
|
|
|
public function __construct(InvoiceGenerator $generator)
|
|
{
|
|
$this->generator = $generator
|
|
}
|
|
|
|
// ...
|
|
}
|
|
|
|
That's it! Both services are :ref:`automatically registered <service-33-changes-automatic-registration>`
|
|
and set to autowire. Without *any* configuration, the container knows to pass the
|
|
auto-registered ``App\Service\InvoiceGenerator`` as the first argument. As
|
|
you can see, the *type* of the class - ``App\Service\InvoiceGenerator`` - is
|
|
what's most important, not the id. You request an *instance* of a specific type and
|
|
the container automatically passes you the correct service.
|
|
|
|
Isn't that magic? How does it know which service to pass me exactly? What if
|
|
I have multiple services of the same instance?
|
|
|
|
The autowiring system was designed to be *super* predictable. It first works by looking
|
|
for a service whose id *exactly* matches the type-hint. This means you're in full
|
|
control of what type-hint maps to what service. You can even use service aliases
|
|
to get more control. If you have multiple services for a specific type, *you* choose
|
|
which should be used for autowiring. For full details on the autowiring logic, see :ref:`autowiring-logic-explained`.
|
|
|
|
But what if I have a scalar (e.g. string) argument? How does it autowire that?
|
|
|
|
If you have an argument that is *not* an object, it can't be autowired. But that's
|
|
ok! Symfony will give you a clear exception (on the next refresh of *any* page) telling
|
|
you which argument of which service could not be autowired. To fix it, you can
|
|
:ref:`manually configure *just* that one argument <services-manually-wire-args>`.
|
|
This is the philosophy of autowiring: only configure the parts that you need to.
|
|
Most configuration is automated.
|
|
|
|
Ok, but autowiring makes your applications less stable. If you change one thing
|
|
or make a mistake, unexpected things might happen. Isn't that a problem?
|
|
|
|
Symfony has always valued stability, security and predictability first. Autowiring
|
|
was designed with that in mind. Specifically:
|
|
|
|
* If there is a problem wiring *any* argument to *any* service, a clear exception
|
|
is thrown on the next refresh of *any* page, even if you don't use that service
|
|
on that page. That's *powerful*: it is *not* possible to make an autowiring mistake
|
|
and not realize it.
|
|
|
|
* The container determines *which* service to pass in an explicit way: it looks for
|
|
a service whose id matches the type-hint exactly. It does *not* scan all services
|
|
looking for objects that have that class/interface.
|
|
|
|
Autowiring aims to *automate* configuration without magic.
|
|
|
|
3) Controllers are Registered as Services
|
|
-----------------------------------------
|
|
|
|
The third big change is that, in a new Symfony 3.3 project, your controllers are *services*:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/services.yaml
|
|
services:
|
|
# ...
|
|
|
|
# controllers are imported separately to make sure they're public
|
|
# and have a tag that allows actions to type-hint services
|
|
App\Controller\:
|
|
resource: '../src/Controller'
|
|
tags: ['controller.service_arguments']
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/services.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd">
|
|
|
|
<services>
|
|
<!-- ... -->
|
|
|
|
<prototype namespace="App\Controller\" resource="../src/Controller">
|
|
<tag name="controller.service_arguments"/>
|
|
</prototype>
|
|
</services>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/services.php
|
|
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
|
|
|
return function(ContainerConfigurator $configurator) {
|
|
// ...
|
|
|
|
// controllers are imported separately to make sure they're public
|
|
// and have a tag that allows actions to type-hint services
|
|
$services->load('App\\Controller\\', '../src/Controller')
|
|
->tag('controller.service_arguments');
|
|
};
|
|
|
|
|
|
But, you might not even notice this. First, your controllers *can* still extend
|
|
the same base controller class (``AbstractController``).
|
|
This means you have access to all of the same shortcuts as before. Additionally,
|
|
the ``@Route`` annotation and ``_controller`` syntax (e.g. ``App:Default:homepage``)
|
|
used in routing will automatically use your controller as a service (as long as its
|
|
service id matches its class name, which it *does* in this case). See :doc:`/controller/service`
|
|
for more details. You can even create :ref:`invokable controllers <controller-service-invoke>`
|
|
|
|
In other words, everything works the same. You can even add the above configuration
|
|
to your existing project without any issues: your controllers will behave the same
|
|
as before. But now that your controllers are services, you can use dependency injection
|
|
and autowiring like any other service.
|
|
|
|
To make life even easier, it's now possible to autowire arguments to your controller
|
|
action methods, like you can with the constructor of services. For example::
|
|
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
class InvoiceController extends AbstractController
|
|
{
|
|
public function listInvoices(LoggerInterface $logger)
|
|
{
|
|
$logger->info('A new way to access services!');
|
|
}
|
|
}
|
|
|
|
This is *only* possible in a controller, and your controller service must be tagged
|
|
with ``controller.service_arguments`` to make it happen. This new feature is used
|
|
throughout the documentation.
|
|
|
|
In general, the new best practice is to use normal constructor dependency injection
|
|
(or "action" injection in controllers) instead of fetching public services via
|
|
``$this->get()`` (though that does still work).
|
|
|
|
.. _service_autoconfigure:
|
|
|
|
4) Auto-tagging with autoconfigure
|
|
----------------------------------
|
|
|
|
The fourth big change is the ``autoconfigure`` key, which is set to ``true`` under
|
|
``_defaults``. Thanks to this, the container will auto-tag services registered in
|
|
this file. For example, suppose you want to create an event subscriber. First, you
|
|
create the class::
|
|
|
|
// src/EventSubscriber/SetHeaderSusbcriber.php
|
|
// ...
|
|
|
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
|
use Symfony\Component\HttpKernel\KernelEvents;
|
|
|
|
class SetHeaderSusbcriber implements EventSubscriberInterface
|
|
{
|
|
public function onKernelResponse(ResponseEvent $event)
|
|
{
|
|
$event->getResponse()->headers->set('X-SYMFONY-3.3', 'Less config');
|
|
}
|
|
|
|
public static function getSubscribedEvents()
|
|
{
|
|
return [
|
|
KernelEvents::RESPONSE => 'onKernelResponse'
|
|
];
|
|
}
|
|
}
|
|
|
|
Great! In Symfony 3.2 or lower, you would now need to register this as a service
|
|
in ``services.yaml`` and tag it with ``kernel.event_subscriber``. In Symfony 3.3,
|
|
you're already done!
|
|
|
|
The service is :ref:`automatically registered <service-33-changes-automatic-registration>`.
|
|
And thanks to ``autoconfigure``, Symfony automatically tags the service because
|
|
it implements ``EventSubscriberInterface``.
|
|
|
|
That sounds like magic - it *automatically* tags my services?
|
|
|
|
In this case, you've created a class that implements ``EventSubscriberInterface``
|
|
and registered it as a service. This is more than enough for the container to know
|
|
that you want this to be used as an event subscriber: more configuration is not needed.
|
|
And the tags system is its own, Symfony-specific mechanism. And you can
|
|
always set ``autoconfigure`` to ``false`` in ``services.yaml``, or disable it
|
|
for a specific service.
|
|
|
|
Does this mean tags are dead? Does this work for all tags?
|
|
|
|
This does *not* work for all tags. Many tags have *required* attributes, like event
|
|
*listeners*, where you also need to specify the event name and method in your tag.
|
|
Autoconfigure works only for tags without any required tag attributes, and as you
|
|
read the docs for a feature, it'll tell you whether or not the tag is needed. You
|
|
can also look at the extension classes (e.g. `FrameworkExtension for 3.3.0`_) to
|
|
see what it autoconfigures.
|
|
|
|
What if I need to add a priority to my tag?
|
|
|
|
Many autoconfigured tags have an optional priority. If you need to specify a priority
|
|
(or any other optional tag attribute), no problem! :ref:`Manually configure your service <services-manually-wire-args>`
|
|
and add the tag. Your tag will take precedence over the one added by auto-configuration.
|
|
|
|
5) Auto-configure with _instanceof
|
|
----------------------------------
|
|
|
|
And the final big change is ``_instanceof``. It acts as a default definition
|
|
template (see `service-33-default_definition`_), but only for services whose
|
|
class matches a defined one.
|
|
|
|
This can be very useful when many services share some tag that cannot be
|
|
inherited from an abstract definition:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/services.yaml
|
|
services:
|
|
# ...
|
|
|
|
_instanceof:
|
|
App\Domain\LoaderInterface:
|
|
public: true
|
|
tags: ['app.domain_loader']
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/services.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd">
|
|
|
|
<services>
|
|
<!-- ... -->
|
|
|
|
<instanceof id="App\Domain\LoaderInterface" public="true">
|
|
<tag name="app.domain_loader"/>
|
|
</instanceof>
|
|
</services>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/services.php
|
|
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
|
|
|
use App\Domain\LoaderInterface;
|
|
|
|
return function(ContainerConfigurator $configurator) {
|
|
// ...
|
|
|
|
$services->instanceof(LoaderInterface::class)
|
|
->public()
|
|
->tag('app.domain_loader');
|
|
};
|
|
|
|
What about Performance
|
|
----------------------
|
|
|
|
Symfony is unique because it has a *compiled* container. This means that there is
|
|
*no* runtime performance impact for using any of these features. That's also why
|
|
the autowiring system can give you such clear errors.
|
|
|
|
However, there is some performance impact in the ``dev`` environment. Most importantly,
|
|
your container will likely be rebuilt more often when you modify your service classes.
|
|
This is because it needs to rebuild whenever you add a new argument to a service,
|
|
or add an interface to your class that should be autoconfigured.
|
|
|
|
In very big projects, this may be a problem. If it is, you can always opt to *not*
|
|
use autowiring. If you think the cache rebuilding system could be smarter in some
|
|
situation, please open an issue!
|
|
|
|
Upgrading to the new Symfony 3.3 Configuration
|
|
----------------------------------------------
|
|
|
|
Ready to upgrade your existing project? Great! Suppose you have the following configuration:
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/services.yaml
|
|
services:
|
|
app.github_notifier:
|
|
class: App\Service\GitHubNotifier
|
|
arguments:
|
|
- '@app.api_client_github'
|
|
|
|
markdown_transformer:
|
|
class: App\Service\MarkdownTransformer
|
|
|
|
app.api_client_github:
|
|
class: App\Service\ApiClient
|
|
arguments:
|
|
- 'https://api.github.com'
|
|
|
|
app.api_client_sl_connect:
|
|
class: App\Service\ApiClient
|
|
arguments:
|
|
- 'https://connect.symfony.com/api'
|
|
|
|
It's optional, but let's upgrade this to the new Symfony 3.3 configuration step-by-step,
|
|
*without* breaking our application.
|
|
|
|
Step 1): Adding _defaults
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Start by adding a ``_defaults`` section with ``autowire`` and ``autoconfigure``.
|
|
|
|
.. code-block:: diff
|
|
|
|
# config/services.yaml
|
|
services:
|
|
+ _defaults:
|
|
+ autowire: true
|
|
+ autoconfigure: true
|
|
|
|
# ...
|
|
|
|
You're already *explicitly* configuring all of your services. So, ``autowire``
|
|
does nothing. You're also already tagging your services, so ``autoconfigure``
|
|
also doesn't change any existing services.
|
|
|
|
You have not added ``public: false`` yet. That will come in a minute.
|
|
|
|
Step 2) Using Class Service id's
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Right now, the service ids are machine names - e.g. ``app.github_notifier``. To
|
|
work well with the new configuration system, your service ids should be class names,
|
|
except when you have multiple instances of the same service.
|
|
|
|
Start by updating the service ids to class names:
|
|
|
|
.. code-block:: diff
|
|
|
|
# config/services.yaml
|
|
services:
|
|
# ...
|
|
|
|
- app.github_notifier:
|
|
- class: App\Service\GitHubNotifier
|
|
+ App\Service\GitHubNotifier:
|
|
arguments:
|
|
- '@app.api_client_github'
|
|
|
|
- markdown_transformer:
|
|
- class: App\Service\MarkdownTransformer
|
|
+ App\Service\MarkdownTransformer: ~
|
|
|
|
# keep these ids because there are multiple instances per class
|
|
app.api_client_github:
|
|
# ...
|
|
app.api_client_sl_connect:
|
|
# ...
|
|
|
|
.. caution::
|
|
|
|
Services associated with global PHP classes (i.e. not using PHP namespaces)
|
|
must maintain the ``class`` parameter. For example, when using the old Twig
|
|
classes (e.g. ``Twig_Extensions_Extension_Intl`` instead of ``Twig\Extensions\IntlExtension``),
|
|
you can't redefine the service as ``Twig_Extensions_Extension_Intl: ~`` and
|
|
you must keep the original ``class`` parameter.
|
|
|
|
.. caution::
|
|
|
|
If a service is processed by a :doc:`compiler pass </service_container/compiler_passes>`,
|
|
you could face a "You have requested a non-existent service" error.
|
|
To get rid of this, be sure that the Compiler Pass is using ``findDefinition()``
|
|
instead of ``getDefinition()``. The latter won't take aliases into
|
|
account when looking up for services.
|
|
Furthermore it is always recommended to check for definition existence
|
|
using ``has()`` function.
|
|
|
|
.. note::
|
|
|
|
If you get rid of deprecations and make your controllers extend from
|
|
``AbstractController`` instead of ``Controller``, you can skip the rest of
|
|
this step because ``AbstractController`` doesn't provide a container where
|
|
you can get the services from. All services need to be injected as explained
|
|
in the :ref:`step 5 of this article <step-5>`.
|
|
|
|
But, this change will break our app! The old service ids (e.g. ``app.github_notifier``)
|
|
no longer exist. The simplest way to fix this is to find all your old service ids
|
|
and update them to the new class id: ``app.github_notifier`` to ``App\Service\GitHubNotifier``.
|
|
|
|
In large projects, there's a better way: create legacy aliases that map the old id
|
|
to the new id. Create a new ``legacy_aliases.yaml`` file:
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/legacy_aliases.yaml
|
|
services:
|
|
_defaults:
|
|
public: true
|
|
# aliases so that the old service ids can still be accessed
|
|
# remove these if/when you are not fetching these directly
|
|
# from the container via $container->get()
|
|
app.github_notifier: '@App\Service\GitHubNotifier'
|
|
markdown_transformer: '@App\Service\MarkdownTransformer'
|
|
|
|
Then import this at the top of ``services.yaml``:
|
|
|
|
.. code-block:: diff
|
|
|
|
# config/services.yaml
|
|
+ imports:
|
|
+ - { resource: legacy_aliases.yaml }
|
|
|
|
# ...
|
|
|
|
That's it! The old service ids still work. Later, (see the cleanup step below), you
|
|
can remove these from your app.
|
|
|
|
Step 3) Make the Services Private
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Now you're ready to default all services to be private:
|
|
|
|
.. code-block:: diff
|
|
|
|
# config/services.yaml
|
|
# ...
|
|
|
|
services:
|
|
_defaults:
|
|
autowire: true
|
|
autoconfigure: true
|
|
+ public: false
|
|
|
|
Thanks to this, any services created in this file cannot be fetched directly from
|
|
the container. But, since the old service id's are aliases in a separate file (``legacy_aliases.yaml``),
|
|
these *are* still public. This makes sure the app keeps working.
|
|
|
|
If you did *not* change the id of some of your services (because there are multiple
|
|
instances of the same class), you may need to make those public:
|
|
|
|
.. code-block:: diff
|
|
|
|
# config/services.yaml
|
|
# ...
|
|
|
|
services:
|
|
# ...
|
|
|
|
app.api_client_github:
|
|
# ...
|
|
|
|
+ # remove this if/when you are not fetching this
|
|
+ # directly from the container via $container->get()
|
|
+ public: true
|
|
|
|
app.api_client_sl_connect:
|
|
# ...
|
|
+ public: true
|
|
|
|
This is to guarantee that the application doesn't break. If you're not fetching
|
|
these services directly from the container, this isn't needed. In a minute, you'll
|
|
clean that up.
|
|
|
|
Step 4) Auto-registering Services
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
You're now ready to automatically register all services in ``src/``
|
|
(and/or any other directory/bundle you have):
|
|
|
|
.. code-block:: diff
|
|
|
|
# config/services.yaml
|
|
|
|
services:
|
|
_defaults:
|
|
# ...
|
|
|
|
+ App\:
|
|
+ resource: '../src/*'
|
|
+ exclude: '../src/{Entity,Migrations,Tests}'
|
|
+
|
|
+ App\Controller\:
|
|
+ resource: '../src/Controller'
|
|
+ tags: ['controller.service_arguments']
|
|
|
|
# ...
|
|
|
|
That's it! Actually, you're already overriding and reconfiguring all the services
|
|
you're using (``App\Service\GitHubNotifier`` and ``App\Service\MarkdownTransformer``).
|
|
But now, you won't need to manually register future services.
|
|
|
|
Once again, there is one extra complication if you have multiple services of the
|
|
same class:
|
|
|
|
.. code-block:: diff
|
|
|
|
# config/services.yaml
|
|
|
|
services:
|
|
# ...
|
|
|
|
+ # alias ApiClient to one of our services below
|
|
+ # app.api_client_github will be used to autowire ApiClient type-hints
|
|
+ App\Service\ApiClient: '@app.api_client_github'
|
|
|
|
app.api_client_github:
|
|
# ...
|
|
app.api_client_sl_connect:
|
|
# ...
|
|
|
|
This guarantees that if you try to autowire an ``ApiClient`` instance, the ``app.api_client_github``
|
|
will be used. If you *don't* have this, the auto-registration feature will try to
|
|
register a third ``ApiClient`` service and use that for autowiring (which will fail,
|
|
because the class has a non-autowireable argument).
|
|
|
|
.. _step-5:
|
|
|
|
Step 5) Cleanup!
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
To make sure your application didn't break, you did some extra work. Now it's time
|
|
to clean things up! First, update your application to *not* use the old service id's (the
|
|
ones in ``legacy_aliases.yaml``). This means updating any service arguments (e.g.
|
|
``@app.github_notifier`` to ``@App\Service\GitHubNotifier``) and updating your
|
|
code to not fetch this service directly from the container. For example:
|
|
|
|
.. code-block:: diff
|
|
|
|
- public function index()
|
|
+ public function index(GitHubNotifier $gitHubNotifier, MarkdownTransformer $markdownTransformer)
|
|
{
|
|
- // the old way of fetching services
|
|
- $githubNotifier = $this->container->get('app.github_notifier');
|
|
- $markdownTransformer = $this->container->get('markdown_transformer');
|
|
|
|
// ...
|
|
}
|
|
|
|
As soon as you do this, you can delete ``legacy_aliases.yaml`` and remove its import.
|
|
You should do the same thing for any services that you made public, like
|
|
``app.api_client_github`` and ``app.api_client_sl_connect``. Once you're not fetching
|
|
these directly from the container, you can remove the ``public: true`` flag:
|
|
|
|
.. code-block:: diff
|
|
|
|
# config/services.yaml
|
|
services:
|
|
# ...
|
|
|
|
app.api_client_github:
|
|
# ...
|
|
- public: true
|
|
|
|
app.api_client_sl_connect:
|
|
# ...
|
|
- public: true
|
|
|
|
Finally, you can optionally remove any services from ``services.yaml`` whose arguments
|
|
can be autowired. The final configuration looks like this:
|
|
|
|
.. code-block:: yaml
|
|
|
|
services:
|
|
_defaults:
|
|
autowire: true
|
|
autoconfigure: true
|
|
public: false
|
|
|
|
App\:
|
|
resource: '../src/*'
|
|
exclude: '../src/{Entity,Migrations,Tests}'
|
|
|
|
App\Controller\:
|
|
resource: '../src/Controller'
|
|
tags: ['controller.service_arguments']
|
|
|
|
App\Service\GitHubNotifier:
|
|
# this could be deleted, or I can keep being explicit
|
|
arguments:
|
|
- '@app.api_client_github'
|
|
|
|
# alias ApiClient to one of our services below
|
|
# app.api_client_github will be used to autowire ApiClient type-hints
|
|
App\Service\ApiClient: '@app.api_client_github'
|
|
|
|
# keep these ids because there are multiple instances per class
|
|
app.api_client_github:
|
|
class: App\Service\ApiClient
|
|
arguments:
|
|
- 'https://api.github.com'
|
|
|
|
app.api_client_sl_connect:
|
|
class: App\Service\ApiClient
|
|
arguments:
|
|
- 'https://connect.symfony.com/api'
|
|
|
|
You can now take advantage of the new features going forward.
|
|
|
|
.. _`FrameworkExtension for 3.3.0`: https://github.com/symfony/symfony/blob/7938fdeceb03cc1df277a249cf3da70f0b50eb98/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php#L247-L284
|