Skip to content

Commit 9a6f38f

Browse files
Improve description of ad hoc containers
1 parent 66a2008 commit 9a6f38f

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
@@ -25,24 +25,22 @@ to handle their respective command when it is asked for::
2525
class CommandBus
2626
{
2727
/**
28-
* @var CommandHandler[]
28+
* @param CommandHandler[] $handlerMap
2929
*/
30-
private $handlerMap;
31-
32-
public function __construct(array $handlerMap)
33-
{
34-
$this->handlerMap = $handlerMap;
30+
public function __construct(
31+
private array $handlerMap,
32+
) {
3533
}
3634

3735
public function handle(Command $command)
3836
{
3937
$commandClass = get_class($command);
4038

41-
if (!isset($this->handlerMap[$commandClass])) {
39+
if (!$handler = $this->handlerMap[$commandClass] ?? null) {
4240
return;
4341
}
4442

45-
return $this->handlerMap[$commandClass]->handle($command);
43+
return $handler->handle($command);
4644
}
4745
}
4846

@@ -67,8 +65,7 @@ Defining a Service Subscriber
6765

6866
First, turn ``CommandBus`` into an implementation of :class:`Symfony\\Contracts\\Service\\ServiceSubscriberInterface`.
6967
Use its ``getSubscribedServices()`` method to include as many services as needed
70-
in the service subscriber and change the type hint of the container to
71-
a PSR-11 ``ContainerInterface``::
68+
in the service subscriber::
7269

7370
// src/CommandBus.php
7471
namespace App;
@@ -80,11 +77,9 @@ a PSR-11 ``ContainerInterface``::
8077

8178
class CommandBus implements ServiceSubscriberInterface
8279
{
83-
private $locator;
84-
85-
public function __construct(ContainerInterface $locator)
86-
{
87-
$this->locator = $locator;
80+
public function __construct(
81+
private ContainerInterface $locator,
82+
) {
8883
}
8984

9085
public static function getSubscribedServices(): array
@@ -114,8 +109,12 @@ a PSR-11 ``ContainerInterface``::
114109
can also manually add the ``container.service_subscriber`` tag.
115110

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

115+
// ...
116+
$numberOfHandlers = count($this->locator);
117+
$nameOfHandlers = array_keys($this->locator->getProvidedServices());
119118
// ...
120119
$handler = ($this->locator)($commandClass);
121120

@@ -305,15 +304,16 @@ argument of type ``service_locator``.
305304
Consider the following ``CommandBus`` class where you want to inject
306305
some services into it via a service locator::
307306

308-
// src/HandlerCollection.php
307+
// src/CommandBus.php
309308
namespace App;
310309

311-
use Symfony\Component\DependencyInjection\ServiceLocator;
310+
use Psr\Container\ContainerInterface;
312311

313312
class CommandBus
314313
{
315-
public function __construct(ServiceLocator $locator)
316-
{
314+
public function __construct(
315+
private ContainerInterface $locator,
316+
) {
317317
}
318318
}
319319

@@ -327,14 +327,15 @@ or directly via PHP attributes:
327327
// src/CommandBus.php
328328
namespace App;
329329
330+
use Psr\Container\ContainerInterface;
330331
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
331-
use Symfony\Component\DependencyInjection\ServiceLocator;
332332
333333
class CommandBus
334334
{
335335
public function __construct(
336336
// creates a service locator with all the services tagged with 'app.handler'
337-
#[TaggedLocator('app.handler')] ServiceLocator $locator
337+
#[TaggedLocator('app.handler')]
338+
private ContainerInterface $locator,
338339
) {
339340
}
340341
}
@@ -564,14 +565,14 @@ of the ``key`` tag attribute (as defined in the ``index_by`` locator option):
564565
// src/CommandBus.php
565566
namespace App;
566567
568+
use Psr\Container\ContainerInterface;
567569
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
568-
use Symfony\Component\DependencyInjection\ServiceLocator;
569570
570571
class CommandBus
571572
{
572573
public function __construct(
573574
#[TaggedLocator('app.handler', indexAttribute: 'key')]
574-
ServiceLocator $locator
575+
private ContainerInterface $locator,
575576
) {
576577
}
577578
}
@@ -645,13 +646,13 @@ Inside this locator you can retrieve services by index using the value of the
645646
// src/Handler/HandlerCollection.php
646647
namespace App\Handler;
647648

648-
use Symfony\Component\DependencyInjection\ServiceLocator;
649+
use Psr\Container\ContainerInterface;
649650

650651
class HandlerCollection
651652
{
652-
public function __construct(ServiceLocator $locator)
653+
public function getHandlerTwo(ContainerInterface $locator)
653654
{
654-
$handlerTwo = $locator->get('handler_two');
655+
return $locator->get('handler_two');
655656
}
656657

657658
// ...
@@ -684,14 +685,14 @@ attribute to the locator service defining the name of this custom method:
684685
// src/CommandBus.php
685686
namespace App;
686687
688+
use Psr\Container\ContainerInterface;
687689
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
688-
use Symfony\Component\DependencyInjection\ServiceLocator;
689690
690691
class CommandBus
691692
{
692693
public function __construct(
693694
#[TaggedLocator('app.handler', 'key', defaultIndexMethod: 'myOwnMethodName')]
694-
ServiceLocator $locator
695+
private ContainerInterface $locator,
695696
) {
696697
}
697698
}
@@ -749,7 +750,7 @@ The :class:`Symfony\\Contracts\\Service\\ServiceSubscriberTrait` provides an
749750
implementation for :class:`Symfony\\Contracts\\Service\\ServiceSubscriberInterface`
750751
that looks through all methods in your class that are marked with the
751752
:class:`Symfony\\Contracts\\Service\\Attribute\\SubscribedService` attribute. It
752-
provides a ``ServiceLocator`` for the services of each method's return type.
753+
describes the services needed by the class based on each method's return type.
753754
The service id is ``__METHOD__``. This allows you to add dependencies to your
754755
services based on type-hinted helper methods::
755756

@@ -907,34 +908,25 @@ Here's an example::
907908
Testing a Service Subscriber
908909
----------------------------
909910

910-
To unit test a service subscriber, you can create a fake ``ServiceLocator``::
911+
To unit test a service subscriber, you can create a fake container::
911912

912-
use Symfony\Component\DependencyInjection\ServiceLocator;
913+
use Symfony\Contracts\Service\ServiceLocatorTrait;
914+
use Symfony\Contracts\Service\ServiceProviderInterface;
913915

914-
$container = new class() extends ServiceLocator {
915-
private $services = [];
916+
// Create the fake services
917+
$foo = new stdClass();
918+
$bar = new stdClass();
919+
$bar->foo = $foo;
916920

917-
public function __construct()
918-
{
919-
parent::__construct([
920-
'foo' => function () {
921-
return $this->services['foo'] = $this->services['foo'] ?? new stdClass();
922-
},
923-
'bar' => function () {
924-
return $this->services['bar'] = $this->services['bar'] ?? $this->createBar();
925-
},
926-
]);
927-
}
928-
929-
private function createBar()
930-
{
931-
$bar = new stdClass();
932-
$bar->foo = $this->get('foo');
933-
934-
return $bar;
935-
}
921+
// Create the fake container
922+
$container = new class([
923+
'foo' => fn () => $foo,
924+
'bar' => fn () => $bar,
925+
]) implements ServiceProviderInterface {
926+
use ServiceLocatorTrait;
936927
};
937928

929+
// Create the service subscriber
938930
$serviceSubscriber = new MyService($container);
939931
// ...
940932

0 commit comments

Comments
 (0)