Skip to content

Commit e3cfa6d

Browse files
committed
minor #18010 Improve description of ad hoc containers (nicolas-grekas)
This PR was merged into the 6.2 branch. Discussion ---------- Improve description of ad hoc containers I think we shouldn't advertise `ServiceLocator` that much. Here is a proposal to decouple a bit from DI and use more abstract concepts. Commits ------- 9a6f38f Improve description of ad hoc containers
2 parents 12880b8 + 9a6f38f commit e3cfa6d

File tree

1 file changed

+45
-53
lines changed

1 file changed

+45
-53
lines changed

service_container/service_subscribers_locators.rst

+45-53
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,22 @@ to handle their respective command when it is asked for::
3030
class CommandBus
3131
{
3232
/**
33-
* @var CommandHandler[]
33+
* @param CommandHandler[] $handlerMap
3434
*/
35-
private $handlerMap;
36-
37-
public function __construct(array $handlerMap)
38-
{
39-
$this->handlerMap = $handlerMap;
35+
public function __construct(
36+
private array $handlerMap,
37+
) {
4038
}
4139

4240
public function handle(Command $command)
4341
{
4442
$commandClass = get_class($command);
4543

46-
if (!isset($this->handlerMap[$commandClass])) {
44+
if (!$handler = $this->handlerMap[$commandClass] ?? null) {
4745
return;
4846
}
4947

50-
return $this->handlerMap[$commandClass]->handle($command);
48+
return $handler->handle($command);
5149
}
5250
}
5351

@@ -72,8 +70,7 @@ Defining a Service Subscriber
7270

7371
First, turn ``CommandBus`` into an implementation of :class:`Symfony\\Contracts\\Service\\ServiceSubscriberInterface`.
7472
Use its ``getSubscribedServices()`` method to include as many services as needed
75-
in the service subscriber and change the type hint of the container to
76-
a PSR-11 ``ContainerInterface``::
73+
in the service subscriber::
7774

7875
// src/CommandBus.php
7976
namespace App;
@@ -85,11 +82,9 @@ a PSR-11 ``ContainerInterface``::
8582

8683
class CommandBus implements ServiceSubscriberInterface
8784
{
88-
private $locator;
89-
90-
public function __construct(ContainerInterface $locator)
91-
{
92-
$this->locator = $locator;
85+
public function __construct(
86+
private ContainerInterface $locator,
87+
) {
9388
}
9489

9590
public static function getSubscribedServices(): array
@@ -119,8 +114,12 @@ a PSR-11 ``ContainerInterface``::
119114
can also manually add the ``container.service_subscriber`` tag.
120115

121116
The injected service is an instance of :class:`Symfony\\Component\\DependencyInjection\\ServiceLocator`
122-
which implements the PSR-11 ``ContainerInterface``, but it is also a callable::
117+
which implements both the PSR-11 ``ContainerInterface`` and :class:`Symfony\\Contracts\\Service\\ServiceProviderInterface`.
118+
It is also a callable and a countable::
123119

120+
// ...
121+
$numberOfHandlers = count($this->locator);
122+
$nameOfHandlers = array_keys($this->locator->getProvidedServices());
124123
// ...
125124
$handler = ($this->locator)($commandClass);
126125

@@ -312,15 +311,16 @@ argument of type ``service_locator``.
312311
Consider the following ``CommandBus`` class where you want to inject
313312
some services into it via a service locator::
314313

315-
// src/HandlerCollection.php
314+
// src/CommandBus.php
316315
namespace App;
317316

318-
use Symfony\Component\DependencyInjection\ServiceLocator;
317+
use Psr\Container\ContainerInterface;
319318

320319
class CommandBus
321320
{
322-
public function __construct(ServiceLocator $locator)
323-
{
321+
public function __construct(
322+
private ContainerInterface $locator,
323+
) {
324324
}
325325
}
326326

@@ -334,14 +334,15 @@ or directly via PHP attributes:
334334
// src/CommandBus.php
335335
namespace App;
336336
337+
use Psr\Container\ContainerInterface;
337338
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
338-
use Symfony\Component\DependencyInjection\ServiceLocator;
339339
340340
class CommandBus
341341
{
342342
public function __construct(
343343
// creates a service locator with all the services tagged with 'app.handler'
344-
#[TaggedLocator('app.handler')] ServiceLocator $locator
344+
#[TaggedLocator('app.handler')]
345+
private ContainerInterface $locator,
345346
) {
346347
}
347348
}
@@ -571,14 +572,14 @@ of the ``key`` tag attribute (as defined in the ``index_by`` locator option):
571572
// src/CommandBus.php
572573
namespace App;
573574
575+
use Psr\Container\ContainerInterface;
574576
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
575-
use Symfony\Component\DependencyInjection\ServiceLocator;
576577
577578
class CommandBus
578579
{
579580
public function __construct(
580581
#[TaggedLocator('app.handler', indexAttribute: 'key')]
581-
ServiceLocator $locator
582+
private ContainerInterface $locator,
582583
) {
583584
}
584585
}
@@ -652,13 +653,13 @@ Inside this locator you can retrieve services by index using the value of the
652653
// src/Handler/HandlerCollection.php
653654
namespace App\Handler;
654655

655-
use Symfony\Component\DependencyInjection\ServiceLocator;
656+
use Psr\Container\ContainerInterface;
656657

657658
class HandlerCollection
658659
{
659-
public function __construct(ServiceLocator $locator)
660+
public function getHandlerTwo(ContainerInterface $locator)
660661
{
661-
$handlerTwo = $locator->get('handler_two');
662+
return $locator->get('handler_two');
662663
}
663664

664665
// ...
@@ -691,14 +692,14 @@ attribute to the locator service defining the name of this custom method:
691692
// src/CommandBus.php
692693
namespace App;
693694
695+
use Psr\Container\ContainerInterface;
694696
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
695-
use Symfony\Component\DependencyInjection\ServiceLocator;
696697
697698
class CommandBus
698699
{
699700
public function __construct(
700701
#[TaggedLocator('app.handler', 'key', defaultIndexMethod: 'myOwnMethodName')]
701-
ServiceLocator $locator
702+
private ContainerInterface $locator,
702703
) {
703704
}
704705
}
@@ -758,7 +759,7 @@ The :class:`Symfony\\Contracts\\Service\\ServiceSubscriberTrait` provides an
758759
implementation for :class:`Symfony\\Contracts\\Service\\ServiceSubscriberInterface`
759760
that looks through all methods in your class that are marked with the
760761
:class:`Symfony\\Contracts\\Service\\Attribute\\SubscribedService` attribute. It
761-
provides a ``ServiceLocator`` for the services of each method's return type.
762+
describes the services needed by the class based on each method's return type.
762763
The service id is ``__METHOD__``. This allows you to add dependencies to your
763764
services based on type-hinted helper methods::
764765

@@ -916,34 +917,25 @@ Here's an example::
916917
Testing a Service Subscriber
917918
----------------------------
918919

919-
To unit test a service subscriber, you can create a fake ``ServiceLocator``::
920+
To unit test a service subscriber, you can create a fake container::
920921

921-
use Symfony\Component\DependencyInjection\ServiceLocator;
922+
use Symfony\Contracts\Service\ServiceLocatorTrait;
923+
use Symfony\Contracts\Service\ServiceProviderInterface;
922924

923-
$container = new class() extends ServiceLocator {
924-
private $services = [];
925+
// Create the fake services
926+
$foo = new stdClass();
927+
$bar = new stdClass();
928+
$bar->foo = $foo;
925929

926-
public function __construct()
927-
{
928-
parent::__construct([
929-
'foo' => function () {
930-
return $this->services['foo'] = $this->services['foo'] ?? new stdClass();
931-
},
932-
'bar' => function () {
933-
return $this->services['bar'] = $this->services['bar'] ?? $this->createBar();
934-
},
935-
]);
936-
}
937-
938-
private function createBar()
939-
{
940-
$bar = new stdClass();
941-
$bar->foo = $this->get('foo');
942-
943-
return $bar;
944-
}
930+
// Create the fake container
931+
$container = new class([
932+
'foo' => fn () => $foo,
933+
'bar' => fn () => $bar,
934+
]) implements ServiceProviderInterface {
935+
use ServiceLocatorTrait;
945936
};
946937

938+
// Create the service subscriber
947939
$serviceSubscriber = new MyService($container);
948940
// ...
949941

0 commit comments

Comments
 (0)