Skip to content

Commit aab1878

Browse files
committed
php8-mod: service locator feat
1 parent 1a859b4 commit aab1878

16 files changed

+974
-8
lines changed

src/CompiledContainer.php

+20
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,24 @@ protected function resolveFactory($callable, $entryName, array $extraParameters
112112
throw new InvalidDefinition("Entry \"$entryName\" cannot be resolved: " . $e->getMessage());
113113
}
114114
}
115+
116+
/**
117+
* Resolve ServiceLocator for given subscriber class (based on \DI\Definition\ServiceLocatorDefinition::resolve).
118+
*
119+
* @param string $requestingName class name of a subscriber, implementing ServiceSubscriberInterface
120+
* @param string $repositoryClass ServiceLocatorRepository
121+
* @throws ServiceSubscriberException
122+
*/
123+
protected function resolveServiceLocator(string $requestingName, string $repositoryClass) : ServiceLocator
124+
{
125+
if (!method_exists($requestingName, 'getSubscribedServices')) {
126+
throw new ServiceSubscriberException(sprintf('The class %s does not implement ServiceSubscriberInterface.', $requestingName));
127+
}
128+
129+
/** @var ServiceLocatorRepository $repository */
130+
$repository = $this->delegateContainer->get($repositoryClass);
131+
$services = $requestingName::getSubscribedServices();
132+
133+
return $repository->create($requestingName, $services);
134+
}
115135
}

src/Compiler/Compiler.php

+6
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ private function compileDefinition(string $entryName, Definition $definition) :
206206
$code = 'return ' . $this->compileValue($value) . ';';
207207
break;
208208
case $definition instanceof Reference:
209+
if ($definition->isServiceLocatorEntry()) {
210+
$requestingEntry = $definition->getRequestingName();
211+
$serviceLocatorDefinition = $definition->getServiceLocatorDefinition();
212+
$code = 'return $this->resolveServiceLocator(' . $this->compileValue($requestingEntry) . ', ' . $this->compileValue($serviceLocatorDefinition::$serviceLocatorRepositoryClass) . ');';
213+
break;
214+
}
209215
$targetEntryName = $definition->getTargetEntryName();
210216
$code = 'return $this->delegateContainer->get(' . $this->compileValue($targetEntryName) . ');';
211217
// If this method is not yet compiled we store it for compilation

src/Definition/Reference.php

+52-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace DI\Definition;
66

7+
use DI\Definition\Exception\InvalidDefinition;
8+
use DI\ServiceLocator;
79
use Psr\Container\ContainerInterface;
810

911
/**
@@ -13,15 +15,25 @@
1315
*/
1416
class Reference implements Definition, SelfResolvingDefinition
1517
{
18+
public static $serviceLocatorClass = ServiceLocator::class;
19+
1620
/** Entry name. */
1721
private string $name = '';
1822

19-
/**
20-
* @param string $targetEntryName Name of the target entry
21-
*/
23+
private bool $isServiceLocatorEntry;
24+
2225
public function __construct(
26+
/**
27+
* @var string Name of the target entry
28+
*/
2329
private string $targetEntryName,
30+
/**
31+
* @var string|null name of an entry - holder of a definition requesting this entry
32+
*/
33+
private ?string $requestingName = null,
34+
private ?ServiceLocatorDefinition $serviceLocatorDefinition = null
2435
) {
36+
$this->isServiceLocatorEntry = $targetEntryName === self::$serviceLocatorClass;
2537
}
2638

2739
public function getName() : string
@@ -39,13 +51,50 @@ public function getTargetEntryName() : string
3951
return $this->targetEntryName;
4052
}
4153

54+
/**
55+
* Returns the name of the entity requesting this entry.
56+
*/
57+
public function getRequestingName() : string
58+
{
59+
return $this->requestingName;
60+
}
61+
62+
public function isServiceLocatorEntry() : bool
63+
{
64+
return $this->isServiceLocatorEntry;
65+
}
66+
67+
public function getServiceLocatorDefinition() : ServiceLocatorDefinition
68+
{
69+
if (!$this->isServiceLocatorEntry || $this->requestingName === null) {
70+
throw new InvalidDefinition(sprintf(
71+
"Invalid service locator definition ('%s' for '%s')",
72+
$this->targetEntryName,
73+
$this->requestingName
74+
));
75+
}
76+
if (!$this->serviceLocatorDefinition) {
77+
$this->serviceLocatorDefinition = new ServiceLocatorDefinition($this->getTargetEntryName(), $this->requestingName);
78+
}
79+
80+
return $this->serviceLocatorDefinition;
81+
}
82+
4283
public function resolve(ContainerInterface $container) : mixed
4384
{
85+
if ($this->isServiceLocatorEntry) {
86+
return $this->getServiceLocatorDefinition()->resolve($container);
87+
}
88+
4489
return $container->get($this->getTargetEntryName());
4590
}
4691

4792
public function isResolvable(ContainerInterface $container) : bool
4893
{
94+
if ($this->isServiceLocatorEntry) {
95+
return $this->getServiceLocatorDefinition()->isResolvable($container);
96+
}
97+
4998
return $container->has($this->getTargetEntryName());
5099
}
51100

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DI\Definition;
6+
7+
use DI\ServiceLocator;
8+
use DI\ServiceLocatorRepository;
9+
use DI\ServiceSubscriberException;
10+
use Psr\Container\ContainerInterface;
11+
12+
class ServiceLocatorDefinition implements Definition, SelfResolvingDefinition
13+
{
14+
public static $serviceLocatorRepositoryClass = ServiceLocatorRepository::class;
15+
16+
/**
17+
* @param string $name Entry name
18+
* @param string $requestingName name of an entry - holder of a definition requesting service locator
19+
*/
20+
public function __construct(
21+
private string $name,
22+
private string $requestingName
23+
) {
24+
}
25+
26+
/**
27+
* Returns the name of the entry in the container.
28+
*/
29+
public function getName() : string
30+
{
31+
return $this->name;
32+
}
33+
34+
public function setName(string $name) : void
35+
{
36+
$this->name = $name;
37+
}
38+
39+
/**
40+
* Returns the name of the holder of the definition requesting service locator.
41+
*/
42+
public function getRequestingName() : string
43+
{
44+
return $this->requestingName;
45+
}
46+
47+
/**
48+
* Resolve the definition and return the resulting value.
49+
*
50+
* @throws ServiceSubscriberException
51+
*/
52+
public function resolve(ContainerInterface $container) : ServiceLocator
53+
{
54+
if (!method_exists($this->requestingName, 'getSubscribedServices')) {
55+
throw new ServiceSubscriberException(sprintf('The class %s does not implement ServiceSubscriberInterface.', $this->requestingName));
56+
}
57+
58+
/** @var ServiceLocatorRepository $repository */
59+
$repository = $container->get(self::$serviceLocatorRepositoryClass);
60+
$services = $this->requestingName::getSubscribedServices();
61+
62+
return $repository->create($this->requestingName, $services);
63+
}
64+
65+
/**
66+
* Check if a definition can be resolved.
67+
*/
68+
public function isResolvable(ContainerInterface $container) : bool
69+
{
70+
return method_exists($this->requestingName, 'getSubscribedServices');
71+
}
72+
73+
public function replaceNestedDefinitions(callable $replacer) : void
74+
{
75+
// no nested definitions
76+
}
77+
78+
/**
79+
* Definitions can be cast to string for debugging information.
80+
*/
81+
public function __toString() : string
82+
{
83+
return sprintf(
84+
'get(%s) for \'%s\'',
85+
$this->name,
86+
$this->requestingName
87+
);
88+
}
89+
}

src/Definition/Source/ReflectionBasedAutowiring.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function autowire(string $name, ObjectDefinition $definition = null) : Ob
3030
$class = new \ReflectionClass($className);
3131
$constructor = $class->getConstructor();
3232
if ($constructor && $constructor->isPublic()) {
33-
$constructorInjection = MethodInjection::constructor($this->getParametersDefinition($constructor));
33+
$constructorInjection = MethodInjection::constructor($this->getParametersDefinition($constructor, $class->getName()));
3434
$definition->completeConstructorInjection($constructorInjection);
3535
}
3636

@@ -53,7 +53,7 @@ public function getDefinitions() : array
5353
/**
5454
* Read the type-hinting from the parameters of the function.
5555
*/
56-
private function getParametersDefinition(\ReflectionFunctionAbstract $constructor) : array
56+
private function getParametersDefinition(\ReflectionFunctionAbstract $constructor, string $className) : array
5757
{
5858
$parameters = [];
5959

@@ -77,7 +77,7 @@ private function getParametersDefinition(\ReflectionFunctionAbstract $constructo
7777
continue;
7878
}
7979

80-
$parameters[$index] = new Reference($parameterType->getName());
80+
$parameters[$index] = new Reference($parameterType->getName(), $className);
8181
}
8282

8383
return $parameters;

src/ServiceLocator.php

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DI;
6+
7+
use Psr\Container\ContainerExceptionInterface;
8+
use Psr\Container\ContainerInterface;
9+
use Psr\Container\NotFoundExceptionInterface;
10+
11+
/**
12+
* Class ServiceLocator.
13+
*
14+
* Serving "lazy" dependencies for classes using ServiceSubscriberInterface.
15+
* Suggested as a lightweight alternative for heavyweight proxies from ocramius/proxy-manager
16+
*/
17+
class ServiceLocator implements ContainerInterface
18+
{
19+
private array $services = [];
20+
21+
public function __construct(
22+
private ContainerInterface $container,
23+
array $services,
24+
/** @var string|null className of a ServiceSubscriber to which this service locator instance belongs to */
25+
private ?string $subscriber = null
26+
) {
27+
$this->setServices($services);
28+
}
29+
30+
protected function setServices(array $services) : void
31+
{
32+
foreach ($services as $key => $value) {
33+
if (is_numeric($key)) {
34+
$key = $value;
35+
}
36+
$this->services[$key] = $value;
37+
}
38+
}
39+
40+
/**
41+
* Get defined services.
42+
*/
43+
public function getServices() : array
44+
{
45+
return $this->services;
46+
}
47+
48+
/**
49+
* Get name of a class to which this service locator instance belongs to.
50+
*/
51+
public function getSubscriber() : string
52+
{
53+
return $this->subscriber;
54+
}
55+
56+
/**
57+
* Finds a service by its identifier.
58+
*
59+
* @param string $id Identifier of the entry to look for.
60+
*
61+
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
62+
* @throws ContainerExceptionInterface Error while retrieving the entry.
63+
*/
64+
public function get(string $id) : mixed
65+
{
66+
if (!isset($this->services[$id])) {
67+
throw new NotFoundException("Service '$id' is not defined.");
68+
}
69+
70+
return $this->container->get($this->services[$id]);
71+
}
72+
73+
/**
74+
* Returns true if the container can return an entry for the given identifier.
75+
* Returns false otherwise.
76+
*
77+
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
78+
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
79+
*
80+
* @param string $id Identifier of the entry to look for.
81+
*/
82+
public function has(string $id) : bool
83+
{
84+
if (!isset($this->services[$id])) {
85+
return false;
86+
}
87+
88+
return $this->container->has($this->services[$id]);
89+
}
90+
}

0 commit comments

Comments
 (0)