Skip to content

Commit 2208f44

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

16 files changed

+957
-5
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

+47
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,23 @@
1315
*/
1416
class Reference implements Definition, SelfResolvingDefinition
1517
{
18+
public static $serviceLocatorClass = ServiceLocator::class;
19+
1620
/** Entry name. */
1721
private string $name = '';
1822

23+
private bool $isServiceLocatorEntry;
24+
1925
/**
2026
* @param string $targetEntryName Name of the target entry
27+
* @param string $requestingName name of an entry - holder of a definition requesting this entry
2128
*/
2229
public function __construct(
2330
private string $targetEntryName,
31+
private string $requestingName,
32+
private ?ServiceLocatorDefinition $serviceLocatorDefinition
2433
) {
34+
$this->isServiceLocatorEntry = $targetEntryName === self::$serviceLocatorClass;
2535
}
2636

2737
public function getName() : string
@@ -39,13 +49,50 @@ public function getTargetEntryName() : string
3949
return $this->targetEntryName;
4050
}
4151

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

4790
public function isResolvable(ContainerInterface $container) : bool
4891
{
92+
if ($this->isServiceLocatorEntry) {
93+
return $this->getServiceLocatorDefinition()->isResolvable($container);
94+
}
95+
4996
return $container->has($this->getTargetEntryName());
5097
}
5198

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

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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+
/**
20+
* Constructor.
21+
* @param string|null $subscriber className of a ServiceSubscriber to which this service locator instance belongs to
22+
*/
23+
public function __construct(
24+
private ContainerInterface $container,
25+
private array $services,
26+
private ?string $subscriber = null
27+
) {
28+
$this->setServices($services);
29+
}
30+
31+
protected function setServices(array $services)
32+
{
33+
foreach ($services as $key => $value) {
34+
if (is_numeric($key)) {
35+
$key = $value;
36+
}
37+
$this->services[$key] = $value;
38+
}
39+
}
40+
41+
/**
42+
* Get defined services.
43+
*/
44+
public function getServices() : array
45+
{
46+
return $this->services;
47+
}
48+
49+
/**
50+
* Get name of a class to which this service locator instance belongs to.
51+
*/
52+
public function getSubscriber() : string
53+
{
54+
return $this->subscriber;
55+
}
56+
57+
/**
58+
* Finds a service by its identifier.
59+
*
60+
* @param string $id Identifier of the entry to look for.
61+
*
62+
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
63+
* @throws ContainerExceptionInterface Error while retrieving the entry.
64+
*/
65+
public function get(string $id) : mixed
66+
{
67+
if (!isset($this->services[$id])) {
68+
throw new NotFoundException("Service '$id' is not defined.");
69+
}
70+
71+
return $this->container->get($this->services[$id]);
72+
}
73+
74+
/**
75+
* Returns true if the container can return an entry for the given identifier.
76+
* Returns false otherwise.
77+
*
78+
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
79+
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
80+
*
81+
* @param string $id Identifier of the entry to look for.
82+
*/
83+
public function has(string $id) : bool
84+
{
85+
if (!isset($this->services[$id])) {
86+
return false;
87+
}
88+
89+
return $this->container->has($this->services[$id]);
90+
}
91+
}

0 commit comments

Comments
 (0)