Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DIC] Static injection #10991

Merged
merged 1 commit into from Nov 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions components/phpunit_bridge.rst
Expand Up @@ -621,7 +621,7 @@ toggle a behavior::
public function hello(): string
{
if (class_exists(DependencyClass::class)) {
return 'The dependency bahavior.';
return 'The dependency behavior.';
}

return 'The default behavior.';
Expand All @@ -639,7 +639,7 @@ are installed during tests) would look like::
public function testHello()
{
$class = new MyClass();
$result = $class->hello(); // "The dependency bahavior."
$result = $class->hello(); // "The dependency behavior."

// ...
}
Expand All @@ -663,7 +663,7 @@ classes, interfaces and/or traits for the code to run::
ClassExistsMock::withMockedClasses([DependencyClass::class => false]);

$class = new MyClass();
$result = $class->hello(); // "The default bahavior."
$result = $class->hello(); // "The default behavior."

// ...
}
Expand Down
2 changes: 0 additions & 2 deletions routing.rst
Expand Up @@ -431,7 +431,6 @@ defined as ``/blog/{slug}``:

<route id="blog_show" path="/blog/{slug}"
controller="App\Controller\BlogController::show"/>
</route>
</routes>

.. code-block:: php
Expand Down Expand Up @@ -525,7 +524,6 @@ the ``{page}`` parameter using the ``requirements`` option:

<route id="blog_show" path="/blog/{slug}"
controller="App\Controller\BlogController::show"/>
</route>
</routes>

.. code-block:: php
Expand Down
76 changes: 76 additions & 0 deletions service_container/calls.rst
Expand Up @@ -77,3 +77,79 @@ To configure the container to call the ``setLogger`` method, use the ``calls`` k
->call('setLogger', [ref('logger')]);
};

OskarStark marked this conversation as resolved.
Show resolved Hide resolved

OskarStark marked this conversation as resolved.
Show resolved Hide resolved
.. versionadded:: 4.3

The ``immutable-setter`` injection was introduced in Symfony 4.3.

In order to provide immutable services, some classes implement immutable setters.
Such setters return a new instance of the configured class
instead of mutating the object they were called on::

namespace App\Service;
OskarStark marked this conversation as resolved.
Show resolved Hide resolved

use Psr\Log\LoggerInterface;

class MessageGenerator
{
private $logger;

/**
* @return static
*/
public function withLogger(LoggerInterface $logger)
{
$new = clone $this;
$new->logger = $logger;

return $new;
}

// ...
}

Because the method returns a separate cloned instance, configuring such a service means using
the return value of the wither method (``$service = $service->withLogger($logger);``).
The configuration to tell the container it should do so would be like:

.. configuration-block::

.. code-block:: yaml

# config/services.yaml
services:
App\Service\MessageGenerator:
OskarStark marked this conversation as resolved.
Show resolved Hide resolved
# ...
calls:
- method: withLogger
arguments:
- '@logger'
returns_clone: true

.. code-block:: xml

<!-- config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="https://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\Service\MessageGenerator">
OskarStark marked this conversation as resolved.
Show resolved Hide resolved
<!-- ... -->
<call method="withLogger" returns-clone="true">
<argument type="service" id="logger"/>
</call>
</service>
</services>
</container>

.. code-block:: php

// config/services.php
use App\Service\MessageGenerator;
OskarStark marked this conversation as resolved.
Show resolved Hide resolved
use Symfony\Component\DependencyInjection\Reference;

$container->register(MessageGenerator::class)
->addMethodCall('withLogger', [new Reference('logger')], true);
3 changes: 3 additions & 0 deletions service_container/definitions.rst
Expand Up @@ -117,6 +117,9 @@ any method calls in the definitions as well::
// configures a new method call
$definition->addMethodCall('setLogger', [new Reference('logger')]);

// configures an immutable-setter
$definition->addMethodCall('withLogger', [new Reference('logger')], true);

// replaces all previously configured method calls with the passed array
$definition->setMethodCalls($methodCalls);

Expand Down
109 changes: 109 additions & 0 deletions service_container/injection_types.rst
Expand Up @@ -105,6 +105,112 @@ working with optional dependencies. It is also more difficult to use in
combination with class hierarchies: if a class uses constructor injection
then extending it and overriding the constructor becomes problematic.

Immutable-setter Injection
--------------------------

OskarStark marked this conversation as resolved.
Show resolved Hide resolved
.. versionadded:: 4.3

The ``immutable-setter`` injection was introduced in Symfony 4.3.

Another possible injection is to use a method which returns a separate instance
by cloning the original service, this approach allows you to make a service immutable::

// ...
OskarStark marked this conversation as resolved.
Show resolved Hide resolved
use Symfony\Component\Mailer\MailerInterface;

class NewsletterManager
{
private $mailer;

/**
* @required
* @return static
*/
public function withMailer(MailerInterface $mailer)
{
$new = clone $this;
$new->mailer = $mailer;

return $new;
}

// ...
}

In order to use this type of injection, don't forget to configure it:

.. configuration-block::

.. code-block:: yaml

# config/services.yaml
services:
# ...

app.newsletter_manager:
class: App\Mail\NewsletterManager
calls:
- [withMailer, ['@mailer'], true]

.. code-block:: xml

<!-- config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="https://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.newsletter_manager" class="App\Mail\NewsletterManager">
<call method="withMailer" returns-clone="true">
<argument type="service" id="mailer"/>
</call>
</service>
</services>
</container>

.. code-block:: php

// config/services.php
use App\Mail\NewsletterManager;
use Symfony\Component\DependencyInjection\Reference;

// ...
$container->register('app.newsletter_manager', NewsletterManager::class)
->addMethodCall('withMailer', [new Reference('mailer')], true);

.. note::

If you decide to use autowiring, this type of injection requires
that you add a ``@return static`` docblock in order for the container
to be capable of registering the method.

This approach is useful if you need to configure your service according to your needs,
so, here's the advantages of immutable-setters:

* Immutable setters works with optional dependencies, this way, if you don't need
a dependency, the setter don't need to be called.

* Like the constructor injection, using immutable setters force the dependency to stay
the same during the lifetime of a service.

* This type of injection works well with traits as the service can be composed,
this way, adapting the service to your application requirements is easier.

* The setter can be called multiple times, this way, adding a dependency to a collection
becomes easier and allows you to add a variable number of dependencies.

The disadvantages are:

* As the setter call is optional, a dependency can be null during execution,
you must check that the dependency is available before calling it.

* Unless the service is declared lazy, it is incompatible with services
that reference each other in what are called circular loops.

OskarStark marked this conversation as resolved.
Show resolved Hide resolved
Setter Injection
----------------

Expand Down Expand Up @@ -180,6 +286,9 @@ This time the advantages are:
the method adds the dependency to a collection. You can then have a variable
number of dependencies.

* Like the immutable-setter one, this type of injection works well with
traits and allows you to compose your service.

The disadvantages of setter injection are:

* The setter can be called more than just at the time of construction so
Expand Down