Skip to content

Commit

Permalink
php8-mod: service locator feat
Browse files Browse the repository at this point in the history
  • Loading branch information
partikus committed Jan 10, 2024
1 parent 1a859b4 commit 13a4b40
Show file tree
Hide file tree
Showing 16 changed files with 975 additions and 8 deletions.
20 changes: 20 additions & 0 deletions src/CompiledContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,24 @@ protected function resolveFactory($callable, $entryName, array $extraParameters
throw new InvalidDefinition("Entry \"$entryName\" cannot be resolved: " . $e->getMessage());
}
}

/**
* Resolve ServiceLocator for given subscriber class (based on \DI\Definition\ServiceLocatorDefinition::resolve).
*
* @param string $requestingName class name of a subscriber, implementing ServiceSubscriberInterface
* @param string $repositoryClass ServiceLocatorRepository
* @throws ServiceSubscriberException
*/
protected function resolveServiceLocator(string $requestingName, string $repositoryClass) : ServiceLocator
{
if (!method_exists($requestingName, 'getSubscribedServices')) {
throw new ServiceSubscriberException(sprintf('The class %s does not implement ServiceSubscriberInterface.', $requestingName));
}

/** @var ServiceLocatorRepository $repository */
$repository = $this->delegateContainer->get($repositoryClass);
$services = $requestingName::getSubscribedServices();

return $repository->create($requestingName, $services);
}
}
6 changes: 6 additions & 0 deletions src/Compiler/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,12 @@ private function compileDefinition(string $entryName, Definition $definition) :
$code = 'return ' . $this->compileValue($value) . ';';
break;
case $definition instanceof Reference:
if ($definition->isServiceLocatorEntry()) {
$requestingEntry = $definition->getRequestingName();
$serviceLocatorDefinition = $definition->getServiceLocatorDefinition();
$code = 'return $this->resolveServiceLocator(' . $this->compileValue($requestingEntry) . ', ' . $this->compileValue($serviceLocatorDefinition::$serviceLocatorRepositoryClass) . ');';
break;
}
$targetEntryName = $definition->getTargetEntryName();
$code = 'return $this->delegateContainer->get(' . $this->compileValue($targetEntryName) . ');';
// If this method is not yet compiled we store it for compilation
Expand Down
55 changes: 52 additions & 3 deletions src/Definition/Reference.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace DI\Definition;

use DI\Definition\Exception\InvalidDefinition;
use DI\ServiceLocator;
use Psr\Container\ContainerInterface;

/**
Expand All @@ -13,15 +15,25 @@
*/
class Reference implements Definition, SelfResolvingDefinition
{
public static $serviceLocatorClass = ServiceLocator::class;

/** Entry name. */
private string $name = '';

/**
* @param string $targetEntryName Name of the target entry
*/
private bool $isServiceLocatorEntry;

public function __construct(
/**
* @var string Name of the target entry
*/
private string $targetEntryName,
/**
* @var string|null name of an entry - holder of a definition requesting this entry
*/
private ?string $requestingName = null,
private ?ServiceLocatorDefinition $serviceLocatorDefinition = null
) {
$this->isServiceLocatorEntry = $targetEntryName === self::$serviceLocatorClass;
}

public function getName() : string
Expand All @@ -39,13 +51,50 @@ public function getTargetEntryName() : string
return $this->targetEntryName;
}

/**
* Returns the name of the entity requesting this entry.
*/
public function getRequestingName() : string
{
return $this->requestingName;
}

public function isServiceLocatorEntry() : bool
{
return $this->isServiceLocatorEntry;
}

public function getServiceLocatorDefinition() : ServiceLocatorDefinition
{
if (!$this->isServiceLocatorEntry || $this->requestingName === null) {
throw new InvalidDefinition(sprintf(
"Invalid service locator definition ('%s' for '%s')",
$this->targetEntryName,
$this->requestingName
));
}
if (!$this->serviceLocatorDefinition) {
$this->serviceLocatorDefinition = new ServiceLocatorDefinition($this->getTargetEntryName(), $this->requestingName);
}

return $this->serviceLocatorDefinition;
}

public function resolve(ContainerInterface $container) : mixed
{
if ($this->isServiceLocatorEntry) {
return $this->getServiceLocatorDefinition()->resolve($container);
}

return $container->get($this->getTargetEntryName());
}

public function isResolvable(ContainerInterface $container) : bool
{
if ($this->isServiceLocatorEntry) {
return $this->getServiceLocatorDefinition()->isResolvable($container);
}

return $container->has($this->getTargetEntryName());
}

Expand Down
89 changes: 89 additions & 0 deletions src/Definition/ServiceLocatorDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);

namespace DI\Definition;

use DI\ServiceLocator;
use DI\ServiceLocatorRepository;
use DI\ServiceSubscriberException;
use Psr\Container\ContainerInterface;

class ServiceLocatorDefinition implements Definition, SelfResolvingDefinition
{
public static $serviceLocatorRepositoryClass = ServiceLocatorRepository::class;

/**
* @param string $name Entry name
* @param string $requestingName name of an entry - holder of a definition requesting service locator
*/
public function __construct(
private string $name,
private string $requestingName
) {
}

/**
* Returns the name of the entry in the container.
*/
public function getName() : string
{
return $this->name;
}

public function setName(string $name) : void
{
$this->name = $name;
}

/**
* Returns the name of the holder of the definition requesting service locator.
*/
public function getRequestingName() : string
{
return $this->requestingName;
}

/**
* Resolve the definition and return the resulting value.
*
* @throws ServiceSubscriberException
*/
public function resolve(ContainerInterface $container) : ServiceLocator
{
if (!method_exists($this->requestingName, 'getSubscribedServices')) {
throw new ServiceSubscriberException(sprintf('The class %s does not implement ServiceSubscriberInterface.', $this->requestingName));
}

/** @var ServiceLocatorRepository $repository */
$repository = $container->get(self::$serviceLocatorRepositoryClass);
$services = $this->requestingName::getSubscribedServices();

return $repository->create($this->requestingName, $services);
}

/**
* Check if a definition can be resolved.
*/
public function isResolvable(ContainerInterface $container) : bool
{
return method_exists($this->requestingName, 'getSubscribedServices');
}

public function replaceNestedDefinitions(callable $replacer) : void
{
// no nested definitions
}

/**
* Definitions can be cast to string for debugging information.
*/
public function __toString() : string
{
return sprintf(
'get(%s) for \'%s\'',
$this->name,
$this->requestingName
);
}
}
6 changes: 3 additions & 3 deletions src/Definition/Source/ReflectionBasedAutowiring.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function autowire(string $name, ObjectDefinition $definition = null) : Ob
$class = new \ReflectionClass($className);
$constructor = $class->getConstructor();
if ($constructor && $constructor->isPublic()) {
$constructorInjection = MethodInjection::constructor($this->getParametersDefinition($constructor));
$constructorInjection = MethodInjection::constructor($this->getParametersDefinition($constructor, $class->getName()));
$definition->completeConstructorInjection($constructorInjection);
}

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

Expand All @@ -77,7 +77,7 @@ private function getParametersDefinition(\ReflectionFunctionAbstract $constructo
continue;
}

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

return $parameters;
Expand Down
91 changes: 91 additions & 0 deletions src/ServiceLocator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace DI;

use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;

/**
* Class ServiceLocator.
*
* Serving "lazy" dependencies for classes using ServiceSubscriberInterface.
* Suggested as a lightweight alternative for heavyweight proxies from ocramius/proxy-manager
*/
class ServiceLocator implements ContainerInterface
{
/**
* Constructor.
* @param string|null $subscriber className of a ServiceSubscriber to which this service locator instance belongs to
*/
public function __construct(
private ContainerInterface $container,
array $services,
private ?string $subscriber = null
) {
$this->setServices($services);
}

protected function setServices(array $services)
{
foreach ($services as $key => $value) {
if (is_numeric($key)) {
$key = $value;
}
$this->services[$key] = $value;
}
}

/**
* Get defined services.
*/
public function getServices() : array
{
return $this->services;
}

/**
* Get name of a class to which this service locator instance belongs to.
*/
public function getSubscriber() : string
{
return $this->subscriber;
}

/**
* Finds a service by its identifier.
*
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
*/
public function get(string $id) : mixed
{
if (!isset($this->services[$id])) {
throw new NotFoundException("Service '$id' is not defined.");
}

return $this->container->get($this->services[$id]);
}

/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $id Identifier of the entry to look for.
*/
public function has(string $id) : bool
{
if (!isset($this->services[$id])) {
return false;
}

return $this->container->has($this->services[$id]);
}
}
Loading

0 comments on commit 13a4b40

Please sign in to comment.