This commit is contained in:
cutemeli
2025-12-22 10:35:30 +00:00
parent 0bfc6c8425
commit 5ce7ca2c5d
38927 changed files with 0 additions and 4594700 deletions

View File

@@ -1,105 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker;
use Closure;
use PleskRestApi\Invoker\Exception\NotCallableException;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use ReflectionException;
use ReflectionMethod;
/**
* Resolves a callable from a container.
*/
class CallableResolver
{
/** @var ContainerInterface */
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Resolve the given callable into a real PHP callable.
*
* @param callable|string|array $callable
* @return callable Real PHP callable.
* @throws NotCallableException|ReflectionException
*/
public function resolve($callable) : callable
{
if (\is_string($callable) && \strpos($callable, '::') !== \false) {
$callable = \explode('::', $callable, 2);
}
$callable = $this->resolveFromContainer($callable);
if (!\is_callable($callable)) {
throw NotCallableException::fromInvalidCallable($callable, \true);
}
return $callable;
}
/**
* @param callable|string|array $callable
* @return callable|mixed
* @throws NotCallableException|ReflectionException
*/
private function resolveFromContainer($callable)
{
// Shortcut for a very common use case
if ($callable instanceof Closure) {
return $callable;
}
// If it's already a callable there is nothing to do
if (\is_callable($callable)) {
// TODO with PHP 8 that should not be necessary to check this anymore
if (!$this->isStaticCallToNonStaticMethod($callable)) {
return $callable;
}
}
// The callable is a container entry name
if (\is_string($callable)) {
try {
return $this->container->get($callable);
} catch (NotFoundExceptionInterface $e) {
if ($this->container->has($callable)) {
throw $e;
}
throw NotCallableException::fromInvalidCallable($callable, \true);
}
}
// The callable is an array whose first item is a container entry name
// e.g. ['some-container-entry', 'methodToCall']
if (\is_array($callable) && \is_string($callable[0])) {
try {
// Replace the container entry name by the actual object
$callable[0] = $this->container->get($callable[0]);
return $callable;
} catch (NotFoundExceptionInterface $e) {
if ($this->container->has($callable[0])) {
throw $e;
}
throw new NotCallableException(\sprintf('Cannot call %s() on %s because it is not a class nor a valid container entry', $callable[1], $callable[0]));
}
}
// Unrecognized stuff, we let it fail later
return $callable;
}
/**
* Check if the callable represents a static call to a non-static method.
*
* @param mixed $callable
* @throws ReflectionException
*/
private function isStaticCallToNonStaticMethod($callable) : bool
{
if (\is_array($callable) && \is_string($callable[0])) {
[$class, $method] = $callable;
if (!\method_exists($class, $method)) {
return \false;
}
$reflection = new ReflectionMethod($class, $method);
return !$reflection->isStatic();
}
return \false;
}
}

View File

@@ -1,11 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\Exception;
/**
* Impossible to invoke the callable.
*/
class InvocationException extends \Exception
{
}

View File

@@ -1,29 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\Exception;
/**
* The given callable is not actually callable.
*/
class NotCallableException extends InvocationException
{
/**
* @param mixed $value
*/
public static function fromInvalidCallable($value, bool $containerEntry = \false) : self
{
if (\is_object($value)) {
$message = \sprintf('Instance of %s is not a callable', \get_class($value));
} elseif (\is_array($value) && isset($value[0], $value[1])) {
$class = \is_object($value[0]) ? \get_class($value[0]) : $value[0];
$extra = \method_exists($class, '__call') || \method_exists($class, '__callStatic') ? ' A __call() or __callStatic() method exists but magic methods are not supported.' : '';
$message = \sprintf('%s::%s() is not a callable.%s', $class, $value[1], $extra);
} elseif ($containerEntry) {
$message = \var_export($value, \true) . ' is neither a callable nor a valid container entry';
} else {
$message = \var_export($value, \true) . ' is not a callable';
}
return new self($message);
}
}

View File

@@ -1,11 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\Exception;
/**
* Not enough parameters could be resolved to invoke the callable.
*/
class NotEnoughParametersException extends InvocationException
{
}

View File

@@ -1,83 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker;
use PleskRestApi\Invoker\Exception\NotCallableException;
use PleskRestApi\Invoker\Exception\NotEnoughParametersException;
use PleskRestApi\Invoker\ParameterResolver\AssociativeArrayResolver;
use PleskRestApi\Invoker\ParameterResolver\DefaultValueResolver;
use PleskRestApi\Invoker\ParameterResolver\NumericArrayResolver;
use PleskRestApi\Invoker\ParameterResolver\ParameterResolver;
use PleskRestApi\Invoker\ParameterResolver\ResolverChain;
use PleskRestApi\Invoker\Reflection\CallableReflection;
use Psr\Container\ContainerInterface;
use ReflectionParameter;
/**
* Invoke a callable.
*/
class Invoker implements InvokerInterface
{
/** @var CallableResolver|null */
private $callableResolver;
/** @var ParameterResolver */
private $parameterResolver;
/** @var ContainerInterface|null */
private $container;
public function __construct(?ParameterResolver $parameterResolver = null, ?ContainerInterface $container = null)
{
$this->parameterResolver = $parameterResolver ?: $this->createParameterResolver();
$this->container = $container;
if ($container) {
$this->callableResolver = new CallableResolver($container);
}
}
/**
* {@inheritdoc}
*/
public function call($callable, array $parameters = [])
{
if ($this->callableResolver) {
$callable = $this->callableResolver->resolve($callable);
}
if (!\is_callable($callable)) {
throw new NotCallableException(\sprintf('%s is not a callable', \is_object($callable) ? 'Instance of ' . \get_class($callable) : \var_export($callable, \true)));
}
$callableReflection = CallableReflection::create($callable);
$args = $this->parameterResolver->getParameters($callableReflection, $parameters, []);
// Sort by array key because call_user_func_array ignores numeric keys
\ksort($args);
// Check all parameters are resolved
$diff = \array_diff_key($callableReflection->getParameters(), $args);
$parameter = \reset($diff);
if ($parameter && \assert($parameter instanceof ReflectionParameter) && !$parameter->isVariadic()) {
throw new NotEnoughParametersException(\sprintf('Unable to invoke the callable because no value was given for parameter %d ($%s)', $parameter->getPosition() + 1, $parameter->name));
}
return \call_user_func_array($callable, $args);
}
/**
* Create the default parameter resolver.
*/
private function createParameterResolver() : ParameterResolver
{
return new ResolverChain([new NumericArrayResolver(), new AssociativeArrayResolver(), new DefaultValueResolver()]);
}
/**
* @return ParameterResolver By default it's a ResolverChain
*/
public function getParameterResolver() : ParameterResolver
{
return $this->parameterResolver;
}
public function getContainer() : ?ContainerInterface
{
return $this->container;
}
/**
* @return CallableResolver|null Returns null if no container was given in the constructor.
*/
public function getCallableResolver() : ?CallableResolver
{
return $this->callableResolver;
}
}

View File

@@ -1,25 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker;
use PleskRestApi\Invoker\Exception\InvocationException;
use PleskRestApi\Invoker\Exception\NotCallableException;
use PleskRestApi\Invoker\Exception\NotEnoughParametersException;
/**
* Invoke a callable.
*/
interface InvokerInterface
{
/**
* Call the given function using the given parameters.
*
* @param callable|array|string $callable Function to call.
* @param array $parameters Parameters to use.
* @return mixed Result of the function.
* @throws InvocationException Base exception class for all the sub-exceptions below.
* @throws NotCallableException
* @throws NotEnoughParametersException
*/
public function call($callable, array $parameters = []);
}

View File

@@ -1,31 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Tries to map an associative array (string-indexed) to the parameter names.
*
* E.g. `->call($callable, ['foo' => 'bar'])` will inject the string `'bar'`
* in the parameter named `$foo`.
*
* Parameters that are not indexed by a string are ignored.
*/
class AssociativeArrayResolver implements ParameterResolver
{
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$parameters = \array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
if (\array_key_exists($parameter->name, $providedParameters)) {
$resolvedParameters[$index] = $providedParameters[$parameter->name];
}
}
return $resolvedParameters;
}
}

View File

@@ -1,38 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\ParameterResolver\Container;
use PleskRestApi\Invoker\ParameterResolver\ParameterResolver;
use Psr\Container\ContainerInterface;
use ReflectionFunctionAbstract;
/**
* Inject entries from a DI container using the parameter names.
*/
class ParameterNameContainerResolver implements ParameterResolver
{
/** @var ContainerInterface */
private $container;
/**
* @param ContainerInterface $container The container to get entries from.
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$parameters = \array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
$name = $parameter->name;
if ($name && $this->container->has($name)) {
$resolvedParameters[$index] = $this->container->get($name);
}
}
return $resolvedParameters;
}
}

View File

@@ -1,55 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\ParameterResolver\Container;
use PleskRestApi\Invoker\ParameterResolver\ParameterResolver;
use Psr\Container\ContainerInterface;
use ReflectionFunctionAbstract;
use ReflectionNamedType;
/**
* Inject entries from a DI container using the type-hints.
*/
class TypeHintContainerResolver implements ParameterResolver
{
/** @var ContainerInterface */
private $container;
/**
* @param ContainerInterface $container The container to get entries from.
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$parameters = \array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
$parameterType = $parameter->getType();
if (!$parameterType) {
// No type
continue;
}
if (!$parameterType instanceof ReflectionNamedType) {
// Union types are not supported
continue;
}
if ($parameterType->isBuiltin()) {
// Primitive types are not supported
continue;
}
$parameterClass = $parameterType->getName();
if ($parameterClass === 'self') {
$parameterClass = $parameter->getDeclaringClass()->getName();
}
if ($this->container->has($parameterClass)) {
$resolvedParameters[$index] = $this->container->get($parameterClass);
}
}
return $resolvedParameters;
}
}

View File

@@ -1,37 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\ParameterResolver;
use ReflectionException;
use ReflectionFunctionAbstract;
/**
* Finds the default value for a parameter, *if it exists*.
*/
class DefaultValueResolver implements ParameterResolver
{
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$parameters = \array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
\assert($parameter instanceof \ReflectionParameter);
if ($parameter->isDefaultValueAvailable()) {
try {
$resolvedParameters[$index] = $parameter->getDefaultValue();
} catch (ReflectionException $e) {
// Can't get default values from PHP internal classes and functions
}
} else {
$parameterType = $parameter->getType();
if ($parameterType && $parameterType->allowsNull()) {
$resolvedParameters[$index] = null;
}
}
}
return $resolvedParameters;
}
}

View File

@@ -1,32 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Simply returns all the values of the $providedParameters array that are
* indexed by the parameter position (i.e. a number).
*
* E.g. `->call($callable, ['foo', 'bar'])` will simply resolve the parameters
* to `['foo', 'bar']`.
*
* Parameters that are not indexed by a number (i.e. parameter position)
* will be ignored.
*/
class NumericArrayResolver implements ParameterResolver
{
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$providedParameters = \array_diff_key($providedParameters, $resolvedParameters);
}
foreach ($providedParameters as $key => $value) {
if (\is_int($key)) {
$resolvedParameters[$key] = $value;
}
}
return $resolvedParameters;
}
}

View File

@@ -1,26 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Resolves the parameters to use to call the callable.
*/
interface ParameterResolver
{
/**
* Resolves the parameters to use to call the callable.
*
* `$resolvedParameters` contains parameters that have already been resolved.
*
* Each ParameterResolver must resolve parameters that are not already
* in `$resolvedParameters`. That allows to chain multiple ParameterResolver.
*
* @param ReflectionFunctionAbstract $reflection Reflection object for the callable.
* @param array $providedParameters Parameters provided by the caller.
* @param array $resolvedParameters Parameters resolved (indexed by parameter position).
* @return array
*/
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters);
}

View File

@@ -1,47 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Dispatches the call to other resolvers until all parameters are resolved.
*
* Chain of responsibility pattern.
*/
class ResolverChain implements ParameterResolver
{
/** @var ParameterResolver[] */
private $resolvers;
public function __construct(array $resolvers = [])
{
$this->resolvers = $resolvers;
}
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$reflectionParameters = $reflection->getParameters();
foreach ($this->resolvers as $resolver) {
$resolvedParameters = $resolver->getParameters($reflection, $providedParameters, $resolvedParameters);
$diff = \array_diff_key($reflectionParameters, $resolvedParameters);
if (empty($diff)) {
// Stop traversing: all parameters are resolved
return $resolvedParameters;
}
}
return $resolvedParameters;
}
/**
* Push a parameter resolver after the ones already registered.
*/
public function appendResolver(ParameterResolver $resolver) : void
{
$this->resolvers[] = $resolver;
}
/**
* Insert a parameter resolver before the ones already registered.
*/
public function prependResolver(ParameterResolver $resolver) : void
{
\array_unshift($this->resolvers, $resolver);
}
}

View File

@@ -1,46 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
use ReflectionNamedType;
/**
* Inject entries using type-hints.
*
* Tries to match type-hints with the parameters provided.
*/
class TypeHintResolver implements ParameterResolver
{
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$parameters = \array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
$parameterType = $parameter->getType();
if (!$parameterType) {
// No type
continue;
}
if (!$parameterType instanceof ReflectionNamedType) {
// Union types are not supported
continue;
}
if ($parameterType->isBuiltin()) {
// Primitive types are not supported
continue;
}
$parameterClass = $parameterType->getName();
if ($parameterClass === 'self') {
$parameterClass = $parameter->getDeclaringClass()->getName();
}
if (\array_key_exists($parameterClass, $providedParameters)) {
$resolvedParameters[$index] = $providedParameters[$parameterClass];
}
}
return $resolvedParameters;
}
}

View File

@@ -1,47 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\Invoker\Reflection;
use Closure;
use PleskRestApi\Invoker\Exception\NotCallableException;
use ReflectionException;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use ReflectionMethod;
/**
* Create a reflection object from a callable or a callable-like.
*
* @internal
*/
class CallableReflection
{
/**
* @param callable|array|string $callable Can be a callable or a callable-like.
* @throws NotCallableException|ReflectionException
*/
public static function create($callable) : ReflectionFunctionAbstract
{
// Closure
if ($callable instanceof Closure) {
return new ReflectionFunction($callable);
}
// Array callable
if (\is_array($callable)) {
[$class, $method] = $callable;
if (!\method_exists($class, $method)) {
throw NotCallableException::fromInvalidCallable($callable);
}
return new ReflectionMethod($class, $method);
}
// Callable object (i.e. implementing __invoke())
if (\is_object($callable) && \method_exists($callable, '__invoke')) {
return new ReflectionMethod($callable, '__invoke');
}
// Standard function
if (\is_string($callable) && \function_exists($callable)) {
return new ReflectionFunction($callable);
}
throw new NotCallableException(\sprintf('%s is not a callable', \is_string($callable) ? $callable : 'Instance of ' . \get_class($callable)));
}
}

View File

@@ -1,63 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Attribute;
use Attribute;
use PleskRestApi\DI\Definition\Exception\InvalidAttribute;
/**
* #[Inject] attribute.
*
* Marks a property or method as an injection point
*
* @api
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
#[\Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD | Attribute::TARGET_PARAMETER)]
class Inject
{
/**
* Entry name.
*/
private ?string $name = null;
/**
* Parameters, indexed by the parameter number (index) or name.
*
* Used if the attribute is set on a method
*/
private array $parameters = [];
/**
* @throws InvalidAttribute
*/
public function __construct(string|array|null $name = null)
{
// #[Inject('foo')] or #[Inject(name: 'foo')]
if (\is_string($name)) {
$this->name = $name;
}
// #[Inject([...])] on a method
if (\is_array($name)) {
foreach ($name as $key => $value) {
if (!\is_string($value)) {
throw new InvalidAttribute(\sprintf("#[Inject(['param' => 'value'])] expects \"value\" to be a string, %s given.", \json_encode($value, \JSON_THROW_ON_ERROR)));
}
$this->parameters[$key] = $value;
}
}
}
/**
* @return string|null Name of the entry to inject
*/
public function getName() : ?string
{
return $this->name;
}
/**
* @return array Parameters, indexed by the parameter number (index) or name
*/
public function getParameters() : array
{
return $this->parameters;
}
}

View File

@@ -1,30 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Attribute;
use Attribute;
/**
* "Injectable" attribute.
*
* Marks a class as injectable
*
* @api
*
* @author Domenic Muskulus <domenic@muskulus.eu>
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
#[\Attribute(Attribute::TARGET_CLASS)]
class Injectable
{
/**
* @param bool|null $lazy Should the object be lazy-loaded.
*/
public function __construct(private ?bool $lazy = null)
{
}
public function isLazy() : ?bool
{
return $this->lazy;
}
}

View File

@@ -1,93 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI;
use PleskRestApi\DI\Compiler\RequestedEntryHolder;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Invoker\FactoryParameterResolver;
use PleskRestApi\Invoker\Exception\NotCallableException;
use PleskRestApi\Invoker\Exception\NotEnoughParametersException;
use PleskRestApi\Invoker\Invoker;
use PleskRestApi\Invoker\InvokerInterface;
use PleskRestApi\Invoker\ParameterResolver\AssociativeArrayResolver;
use PleskRestApi\Invoker\ParameterResolver\DefaultValueResolver;
use PleskRestApi\Invoker\ParameterResolver\NumericArrayResolver;
use PleskRestApi\Invoker\ParameterResolver\ResolverChain;
/**
* Compiled version of the dependency injection container.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
abstract class CompiledContainer extends Container
{
/**
* This const is overridden in child classes (compiled containers).
* @var array
*/
protected const METHOD_MAPPING = [];
private ?InvokerInterface $factoryInvoker = null;
public function get(string $id) : mixed
{
// Try to find the entry in the singleton map
if (isset($this->resolvedEntries[$id]) || \array_key_exists($id, $this->resolvedEntries)) {
return $this->resolvedEntries[$id];
}
/** @psalm-suppress UndefinedConstant */
$method = static::METHOD_MAPPING[$id] ?? null;
// If it's a compiled entry, then there is a method in this class
if ($method !== null) {
// Check if we are already getting this entry -> circular dependency
if (isset($this->entriesBeingResolved[$id])) {
$idList = \implode(' -> ', [...\array_keys($this->entriesBeingResolved), $id]);
throw new DependencyException("Circular dependency detected while trying to resolve entry '{$id}': Dependencies: " . $idList);
}
$this->entriesBeingResolved[$id] = \true;
try {
$value = $this->{$method}();
} finally {
unset($this->entriesBeingResolved[$id]);
}
// Store the entry to always return it without recomputing it
$this->resolvedEntries[$id] = $value;
return $value;
}
return parent::get($id);
}
public function has(string $id) : bool
{
// The parent method is overridden to check in our array, it avoids resolving definitions
/** @psalm-suppress UndefinedConstant */
if (isset(static::METHOD_MAPPING[$id])) {
return \true;
}
return parent::has($id);
}
protected function setDefinition(string $name, Definition $definition) : void
{
// It needs to be forbidden because that would mean get() must go through the definitions
// every time, which kinds of defeats the performance gains of the compiled container
throw new \LogicException('You cannot set a definition at runtime on a compiled container. You can either put your definitions in a file, disable compilation or ->set() a raw value directly (PHP object, string, int, ...) instead of a PHP-DI definition.');
}
/**
* Invoke the given callable.
*/
protected function resolveFactory($callable, $entryName, array $extraParameters = []) : mixed
{
// Initialize the factory resolver
if (!$this->factoryInvoker) {
$parameterResolver = new ResolverChain([new AssociativeArrayResolver(), new FactoryParameterResolver($this->delegateContainer), new NumericArrayResolver(), new DefaultValueResolver()]);
$this->factoryInvoker = new Invoker($parameterResolver, $this->delegateContainer);
}
$parameters = [$this->delegateContainer, new RequestedEntryHolder($entryName)];
$parameters = \array_merge($parameters, $extraParameters);
try {
return $this->factoryInvoker->call($callable, $parameters);
} catch (NotCallableException $e) {
throw new InvalidDefinition("Entry \"{$entryName}\" cannot be resolved: factory " . $e->getMessage());
} catch (NotEnoughParametersException $e) {
throw new InvalidDefinition("Entry \"{$entryName}\" cannot be resolved: " . $e->getMessage());
}
}
}

View File

@@ -1,324 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Compiler;
use function chmod;
use PleskRestApi\DI\Definition\ArrayDefinition;
use PleskRestApi\DI\Definition\DecoratorDefinition;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\EnvironmentVariableDefinition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Definition\FactoryDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition;
use PleskRestApi\DI\Definition\Reference;
use PleskRestApi\DI\Definition\Source\DefinitionSource;
use PleskRestApi\DI\Definition\StringDefinition;
use PleskRestApi\DI\Definition\ValueDefinition;
use PleskRestApi\DI\DependencyException;
use PleskRestApi\DI\Proxy\ProxyFactoryInterface;
use function dirname;
use function file_put_contents;
use InvalidArgumentException;
use PleskRestApi\Laravel\SerializableClosure\Support\ReflectionClosure;
use function rename;
use function sprintf;
use function tempnam;
use function unlink;
/**
* Compiles the container into PHP code much more optimized for performances.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Compiler
{
private string $containerClass;
private string $containerParentClass;
/**
* Definitions indexed by the entry name. The value can be null if the definition needs to be fetched.
*
* Keys are strings, values are `Definition` objects or null.
*/
private \ArrayIterator $entriesToCompile;
/**
* Progressive counter for definitions.
*
* Each key in $entriesToCompile is defined as 'SubEntry' + counter
* and each definition has always the same key in the CompiledContainer
* if PHP-DI configuration does not change.
*/
private int $subEntryCounter = 0;
/**
* Progressive counter for CompiledContainer get methods.
*
* Each CompiledContainer method name is defined as 'get' + counter
* and remains the same after each recompilation
* if PHP-DI configuration does not change.
*/
private int $methodMappingCounter = 0;
/**
* Map of entry names to method names.
*
* @var string[]
*/
private array $entryToMethodMapping = [];
/**
* @var string[]
*/
private array $methods = [];
private bool $autowiringEnabled;
public function __construct(private ProxyFactoryInterface $proxyFactory)
{
}
public function getProxyFactory() : ProxyFactoryInterface
{
return $this->proxyFactory;
}
/**
* Compile the container.
*
* @return string The compiled container file name.
*/
public function compile(DefinitionSource $definitionSource, string $directory, string $className, string $parentClassName, bool $autowiringEnabled) : string
{
$fileName = \rtrim($directory, '/') . '/' . $className . '.php';
if (\file_exists($fileName)) {
// The container is already compiled
return $fileName;
}
$this->autowiringEnabled = $autowiringEnabled;
// Validate that a valid class name was provided
$validClassName = \preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $className);
if (!$validClassName) {
throw new InvalidArgumentException("The container cannot be compiled: `{$className}` is not a valid PHP class name");
}
$this->entriesToCompile = new \ArrayIterator($definitionSource->getDefinitions());
// We use an ArrayIterator so that we can keep adding new items to the list while we compile entries
foreach ($this->entriesToCompile as $entryName => $definition) {
$silenceErrors = \false;
// This is an entry found by reference during autowiring
if (!$definition) {
$definition = $definitionSource->getDefinition($entryName);
// We silence errors for those entries because type-hints may reference interfaces/abstract classes
// which could later be defined, or even not used (we don't want to block the compilation for those)
$silenceErrors = \true;
}
if (!$definition) {
// We do not throw a `NotFound` exception here because the dependency
// could be defined at runtime
continue;
}
// Check that the definition can be compiled
$errorMessage = $this->isCompilable($definition);
if ($errorMessage !== \true) {
continue;
}
try {
$this->compileDefinition($entryName, $definition);
} catch (InvalidDefinition $e) {
if ($silenceErrors) {
// forget the entry
unset($this->entryToMethodMapping[$entryName]);
} else {
throw $e;
}
}
}
$this->containerClass = $className;
$this->containerParentClass = $parentClassName;
\ob_start();
require __DIR__ . '/Template.php';
$fileContent = \ob_get_clean();
$fileContent = "<?php\n" . $fileContent;
$this->createCompilationDirectory(dirname($fileName));
$this->writeFileAtomic($fileName, $fileContent);
return $fileName;
}
private function writeFileAtomic(string $fileName, string $content) : void
{
$tmpFile = @tempnam(dirname($fileName), 'swap-compile');
if ($tmpFile === \false) {
throw new InvalidArgumentException(sprintf('Error while creating temporary file in %s', dirname($fileName)));
}
@chmod($tmpFile, 0666);
$written = file_put_contents($tmpFile, $content);
if ($written === \false) {
@unlink($tmpFile);
throw new InvalidArgumentException(sprintf('Error while writing to %s', $tmpFile));
}
@chmod($tmpFile, 0666);
$renamed = @rename($tmpFile, $fileName);
if (!$renamed) {
@unlink($tmpFile);
throw new InvalidArgumentException(sprintf('Error while renaming %s to %s', $tmpFile, $fileName));
}
}
/**
* @return string The method name
* @throws DependencyException
* @throws InvalidDefinition
*/
private function compileDefinition(string $entryName, Definition $definition) : string
{
// Generate a unique method name
$methodName = 'get' . ++$this->methodMappingCounter;
$this->entryToMethodMapping[$entryName] = $methodName;
switch (\true) {
case $definition instanceof ValueDefinition:
$value = $definition->getValue();
$code = 'return ' . $this->compileValue($value) . ';';
break;
case $definition instanceof Reference:
$targetEntryName = $definition->getTargetEntryName();
$code = 'return $this->delegateContainer->get(' . $this->compileValue($targetEntryName) . ');';
// If this method is not yet compiled we store it for compilation
if (!isset($this->entriesToCompile[$targetEntryName])) {
$this->entriesToCompile[$targetEntryName] = null;
}
break;
case $definition instanceof StringDefinition:
$entryName = $this->compileValue($definition->getName());
$expression = $this->compileValue($definition->getExpression());
$code = 'return \\DI\\Definition\\StringDefinition::resolveExpression(' . $entryName . ', ' . $expression . ', $this->delegateContainer);';
break;
case $definition instanceof EnvironmentVariableDefinition:
$variableName = $this->compileValue($definition->getVariableName());
$isOptional = $this->compileValue($definition->isOptional());
$defaultValue = $this->compileValue($definition->getDefaultValue());
$code = <<<PHP
\$value = \$_ENV[{$variableName}] ?? \$_SERVER[{$variableName}] ?? getenv({$variableName});
if (false !== \$value) return \$value;
if (!{$isOptional}) {
throw new \\DI\\Definition\\Exception\\InvalidDefinition("The environment variable '{$definition->getVariableName()}' has not been defined");
}
return {$defaultValue};
PHP;
break;
case $definition instanceof ArrayDefinition:
try {
$code = 'return ' . $this->compileValue($definition->getValues()) . ';';
} catch (\Exception $e) {
throw new DependencyException(sprintf('Error while compiling %s. %s', $definition->getName(), $e->getMessage()), 0, $e);
}
break;
case $definition instanceof ObjectDefinition:
$compiler = new ObjectCreationCompiler($this);
$code = $compiler->compile($definition);
$code .= "\n return \$object;";
break;
case $definition instanceof DecoratorDefinition:
$decoratedDefinition = $definition->getDecoratedDefinition();
if (!$decoratedDefinition instanceof Definition) {
if (!$definition->getName()) {
throw new InvalidDefinition('Decorators cannot be nested in another definition');
}
throw new InvalidDefinition(sprintf('Entry "%s" decorates nothing: no previous definition with the same name was found', $definition->getName()));
}
$code = sprintf('return call_user_func(%s, %s, $this->delegateContainer);', $this->compileValue($definition->getCallable()), $this->compileValue($decoratedDefinition));
break;
case $definition instanceof FactoryDefinition:
$value = $definition->getCallable();
// Custom error message to help debugging
$isInvokableClass = \is_string($value) && \class_exists($value) && \method_exists($value, '__invoke');
if ($isInvokableClass && !$this->autowiringEnabled) {
throw new InvalidDefinition(sprintf('Entry "%s" cannot be compiled. Invokable classes cannot be automatically resolved if autowiring is disabled on the container, you need to enable autowiring or define the entry manually.', $entryName));
}
$definitionParameters = '';
if (!empty($definition->getParameters())) {
$definitionParameters = ', ' . $this->compileValue($definition->getParameters());
}
$code = sprintf('return $this->resolveFactory(%s, %s%s);', $this->compileValue($value), \var_export($entryName, \true), $definitionParameters);
break;
default:
// This case should not happen (so it cannot be tested)
throw new \Exception('Cannot compile definition of type ' . $definition::class);
}
$this->methods[$methodName] = $code;
return $methodName;
}
public function compileValue(mixed $value) : string
{
// Check that the value can be compiled
$errorMessage = $this->isCompilable($value);
if ($errorMessage !== \true) {
throw new InvalidDefinition($errorMessage);
}
if ($value instanceof Definition) {
// Give it an arbitrary unique name
$subEntryName = 'subEntry' . ++$this->subEntryCounter;
// Compile the sub-definition in another method
$methodName = $this->compileDefinition($subEntryName, $value);
// The value is now a method call to that method (which returns the value)
return "\$this->{$methodName}()";
}
if (\is_array($value)) {
$value = \array_map(function ($value, $key) {
$compiledValue = $this->compileValue($value);
$key = \var_export($key, \true);
return " {$key} => {$compiledValue},\n";
}, $value, \array_keys($value));
$value = \implode('', $value);
return "[\n{$value} ]";
}
if ($value instanceof \Closure) {
return $this->compileClosure($value);
}
return \var_export($value, \true);
}
private function createCompilationDirectory(string $directory) : void
{
if (!\is_dir($directory) && !@\mkdir($directory, 0777, \true) && !\is_dir($directory)) {
throw new InvalidArgumentException(sprintf('Compilation directory does not exist and cannot be created: %s.', $directory));
}
if (!\is_writable($directory)) {
throw new InvalidArgumentException(sprintf('Compilation directory is not writable: %s.', $directory));
}
}
/**
* @return string|true If true is returned that means that the value is compilable.
*/
private function isCompilable($value) : string|bool
{
if ($value instanceof ValueDefinition) {
return $this->isCompilable($value->getValue());
}
if ($value instanceof DecoratorDefinition && empty($value->getName())) {
return 'Decorators cannot be nested in another definition';
}
// All other definitions are compilable
if ($value instanceof Definition) {
return \true;
}
if ($value instanceof \Closure) {
return \true;
}
/** @psalm-suppress UndefinedClass */
if (\PHP_VERSION_ID >= 80100 && $value instanceof \UnitEnum) {
return \true;
}
if (\is_object($value)) {
return 'An object was found but objects cannot be compiled';
}
if (\is_resource($value)) {
return 'A resource was found but resources cannot be compiled';
}
return \true;
}
/**
* @throws InvalidDefinition
*/
private function compileClosure(\Closure $closure) : string
{
$reflector = new ReflectionClosure($closure);
if ($reflector->getUseVariables()) {
throw new InvalidDefinition('Cannot compile closures which import variables using the `use` keyword');
}
if ($reflector->isBindingRequired() || $reflector->isScopeRequired()) {
throw new InvalidDefinition('Cannot compile closures which use $this or self/static/parent references');
}
// Force all closures to be static (add the `static` keyword), i.e. they can't use
// $this, which makes sense since their code is copied into another class.
$code = ($reflector->isStatic() ? '' : 'static ') . $reflector->getCode();
return \trim($code, "\t\n\r;");
}
}

View File

@@ -1,138 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Compiler;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition\MethodInjection;
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
/**
* Compiles an object definition into native PHP code that, when executed, creates the object.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ObjectCreationCompiler
{
public function __construct(private Compiler $compiler)
{
}
public function compile(ObjectDefinition $definition) : string
{
$this->assertClassIsNotAnonymous($definition);
$this->assertClassIsInstantiable($definition);
/** @var class-string $className At this point we have checked the class is valid */
$className = $definition->getClassName();
// Lazy?
if ($definition->isLazy()) {
return $this->compileLazyDefinition($definition);
}
try {
$classReflection = new ReflectionClass($className);
$constructorArguments = $this->resolveParameters($definition->getConstructorInjection(), $classReflection->getConstructor());
$dumpedConstructorArguments = \array_map(function ($value) {
return $this->compiler->compileValue($value);
}, $constructorArguments);
$code = [];
$code[] = \sprintf('$object = new %s(%s);', $className, \implode(', ', $dumpedConstructorArguments));
// Property injections
foreach ($definition->getPropertyInjections() as $propertyInjection) {
$value = $propertyInjection->getValue();
$value = $this->compiler->compileValue($value);
$propertyClassName = $propertyInjection->getClassName() ?: $className;
$property = new ReflectionProperty($propertyClassName, $propertyInjection->getPropertyName());
if ($property->isPublic() && !(\PHP_VERSION_ID >= 80100 && $property->isReadOnly())) {
$code[] = \sprintf('$object->%s = %s;', $propertyInjection->getPropertyName(), $value);
} else {
// Private/protected/readonly property
$code[] = \sprintf('\\DI\\Definition\\Resolver\\ObjectCreator::setPrivatePropertyValue(%s, $object, \'%s\', %s);', \var_export($propertyInjection->getClassName(), \true), $propertyInjection->getPropertyName(), $value);
}
}
// Method injections
foreach ($definition->getMethodInjections() as $methodInjection) {
$methodReflection = new ReflectionMethod($className, $methodInjection->getMethodName());
$parameters = $this->resolveParameters($methodInjection, $methodReflection);
$dumpedParameters = \array_map(function ($value) {
return $this->compiler->compileValue($value);
}, $parameters);
$code[] = \sprintf('$object->%s(%s);', $methodInjection->getMethodName(), \implode(', ', $dumpedParameters));
}
} catch (InvalidDefinition $e) {
throw InvalidDefinition::create($definition, \sprintf('Entry "%s" cannot be compiled: %s', $definition->getName(), $e->getMessage()));
}
return \implode("\n ", $code);
}
public function resolveParameters(?MethodInjection $definition, ?ReflectionMethod $method) : array
{
$args = [];
if (!$method) {
return $args;
}
$definitionParameters = $definition ? $definition->getParameters() : [];
foreach ($method->getParameters() as $index => $parameter) {
if (\array_key_exists($index, $definitionParameters)) {
// Look in the definition
$value =& $definitionParameters[$index];
} elseif ($parameter->isOptional()) {
// If the parameter is optional and wasn't specified, we take its default value
$args[] = $this->getParameterDefaultValue($parameter, $method);
continue;
} else {
throw new InvalidDefinition(\sprintf('Parameter $%s of %s has no value defined or guessable', $parameter->getName(), $this->getFunctionName($method)));
}
$args[] =& $value;
}
return $args;
}
private function compileLazyDefinition(ObjectDefinition $definition) : string
{
$subDefinition = clone $definition;
$subDefinition->setLazy(\false);
$subDefinition = $this->compiler->compileValue($subDefinition);
/** @var class-string $className At this point we have checked the class is valid */
$className = $definition->getClassName();
$this->compiler->getProxyFactory()->generateProxyClass($className);
return <<<STR
\$object = \$this->proxyFactory->createProxy(
'{$definition->getClassName()}',
function () {
return {$subDefinition};
}
);
STR;
}
/**
* Returns the default value of a function parameter.
*
* @throws InvalidDefinition Can't get default values from PHP internal classes and functions
*/
private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function) : mixed
{
try {
return $parameter->getDefaultValue();
} catch (\ReflectionException) {
throw new InvalidDefinition(\sprintf('The parameter "%s" of %s has no type defined or guessable. It has a default value, ' . 'but the default value can\'t be read through Reflection because it is a PHP internal class.', $parameter->getName(), $this->getFunctionName($function)));
}
}
private function getFunctionName(ReflectionMethod $method) : string
{
return $method->getName() . '()';
}
private function assertClassIsNotAnonymous(ObjectDefinition $definition) : void
{
if (\str_contains($definition->getClassName(), '@')) {
throw InvalidDefinition::create($definition, \sprintf('Entry "%s" cannot be compiled: anonymous classes cannot be compiled', $definition->getName()));
}
}
private function assertClassIsInstantiable(ObjectDefinition $definition) : void
{
if ($definition->isInstantiable()) {
return;
}
$message = !$definition->classExists() ? 'Entry "%s" cannot be compiled: the class doesn\'t exist' : 'Entry "%s" cannot be compiled: the class is not instantiable';
throw InvalidDefinition::create($definition, \sprintf($message, $definition->getName()));
}
}

View File

@@ -1,19 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Compiler;
use PleskRestApi\DI\Factory\RequestedEntry;
/**
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class RequestedEntryHolder implements RequestedEntry
{
public function __construct(private string $name)
{
}
public function getName() : string
{
return $this->name;
}
}

View File

@@ -1,33 +0,0 @@
/**
* This class has been auto-generated by PHP-DI.
*/
class <?php
namespace {
echo $this->containerClass;
?> extends <?php
echo $this->containerParentClass;
?>
{
const METHOD_MAPPING = <?php
\var_export($this->entryToMethodMapping);
?>;
<?php
foreach ($this->methods as $methodName => $methodContent) {
?>
protected function <?php
echo $methodName;
?>()
{
<?php
echo $methodContent;
?>
}
<?php
}
?>
}
<?php
}

View File

@@ -1,327 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Definition\FactoryDefinition;
use PleskRestApi\DI\Definition\Helper\DefinitionHelper;
use PleskRestApi\DI\Definition\InstanceDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition;
use PleskRestApi\DI\Definition\Resolver\DefinitionResolver;
use PleskRestApi\DI\Definition\Resolver\ResolverDispatcher;
use PleskRestApi\DI\Definition\Source\DefinitionArray;
use PleskRestApi\DI\Definition\Source\MutableDefinitionSource;
use PleskRestApi\DI\Definition\Source\ReflectionBasedAutowiring;
use PleskRestApi\DI\Definition\Source\SourceChain;
use PleskRestApi\DI\Definition\ValueDefinition;
use PleskRestApi\DI\Invoker\DefinitionParameterResolver;
use PleskRestApi\DI\Proxy\NativeProxyFactory;
use PleskRestApi\DI\Proxy\ProxyFactory;
use PleskRestApi\DI\Proxy\ProxyFactoryInterface;
use InvalidArgumentException;
use PleskRestApi\Invoker\Invoker;
use PleskRestApi\Invoker\InvokerInterface;
use PleskRestApi\Invoker\ParameterResolver\AssociativeArrayResolver;
use PleskRestApi\Invoker\ParameterResolver\Container\TypeHintContainerResolver;
use PleskRestApi\Invoker\ParameterResolver\DefaultValueResolver;
use PleskRestApi\Invoker\ParameterResolver\NumericArrayResolver;
use PleskRestApi\Invoker\ParameterResolver\ResolverChain;
use Psr\Container\ContainerInterface;
/**
* Dependency Injection Container.
*
* @api
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Container implements ContainerInterface, FactoryInterface, InvokerInterface
{
/**
* Map of entries that are already resolved.
*/
protected array $resolvedEntries = [];
private MutableDefinitionSource $definitionSource;
private DefinitionResolver $definitionResolver;
/**
* Map of definitions that are already fetched (local cache).
*
* @var array<Definition|null>
*/
private array $fetchedDefinitions = [];
/**
* Array of entries being resolved. Used to avoid circular dependencies and infinite loops.
*/
protected array $entriesBeingResolved = [];
private ?InvokerInterface $invoker = null;
/**
* Container that wraps this container. If none, points to $this.
*/
protected ContainerInterface $delegateContainer;
protected ProxyFactoryInterface $proxyFactory;
public static function create(array $definitions) : static
{
$source = new SourceChain([new ReflectionBasedAutowiring()]);
$source->setMutableDefinitionSource(new DefinitionArray($definitions, new ReflectionBasedAutowiring()));
return new static($definitions);
}
/**
* Use `$container = new Container()` if you want a container with the default configuration.
*
* If you want to customize the container's behavior, you are discouraged to create and pass the
* dependencies yourself, the ContainerBuilder class is here to help you instead.
*
* @see ContainerBuilder
*
* @param ContainerInterface $wrapperContainer If the container is wrapped by another container.
*/
public function __construct(array|\PleskRestApi\DI\Definition\Source\MutableDefinitionSource $definitions = [], ?ProxyFactoryInterface $proxyFactory = null, ?ContainerInterface $wrapperContainer = null)
{
if (\is_array($definitions)) {
$this->definitionSource = $this->createDefaultDefinitionSource($definitions);
} else {
$this->definitionSource = $definitions;
}
$this->delegateContainer = $wrapperContainer ?: $this;
if ($proxyFactory === null) {
$proxyFactory = \PHP_VERSION_ID >= 80400 ? new NativeProxyFactory() : new ProxyFactory();
}
$this->proxyFactory = $proxyFactory;
$this->definitionResolver = new ResolverDispatcher($this->delegateContainer, $this->proxyFactory);
// Auto-register the container
$this->resolvedEntries = [self::class => $this, ContainerInterface::class => $this->delegateContainer, FactoryInterface::class => $this, InvokerInterface::class => $this];
}
/**
* Returns an entry of the container by its name.
*
* @template T
* @param string|class-string<T> $id Entry name or a class name.
*
* @return mixed|T
* @throws DependencyException Error while resolving the entry.
* @throws NotFoundException No entry found for the given name.
*/
public function get(string $id) : mixed
{
// If the entry is already resolved we return it
if (isset($this->resolvedEntries[$id]) || \array_key_exists($id, $this->resolvedEntries)) {
return $this->resolvedEntries[$id];
}
$definition = $this->getDefinition($id);
if (!$definition) {
throw new NotFoundException("No entry or class found for '{$id}'");
}
$value = $this->resolveDefinition($definition);
$this->resolvedEntries[$id] = $value;
return $value;
}
private function getDefinition(string $name) : ?Definition
{
// Local cache that avoids fetching the same definition twice
if (!\array_key_exists($name, $this->fetchedDefinitions)) {
$this->fetchedDefinitions[$name] = $this->definitionSource->getDefinition($name);
}
return $this->fetchedDefinitions[$name];
}
/**
* Build an entry of the container by its name.
*
* This method behave like get() except resolves the entry again every time.
* For example if the entry is a class then a new instance will be created each time.
*
* This method makes the container behave like a factory.
*
* @template T
* @param string|class-string<T> $name Entry name or a class name.
* @param array $parameters Optional parameters to use to build the entry. Use this to force
* specific parameters to specific values. Parameters not defined in this
* array will be resolved using the container.
*
* @return mixed|T
* @throws InvalidArgumentException The name parameter must be of type string.
* @throws DependencyException Error while resolving the entry.
* @throws NotFoundException No entry found for the given name.
*/
public function make(string $name, array $parameters = []) : mixed
{
$definition = $this->getDefinition($name);
if (!$definition) {
// If the entry is already resolved we return it
if (\array_key_exists($name, $this->resolvedEntries)) {
return $this->resolvedEntries[$name];
}
throw new NotFoundException("No entry or class found for '{$name}'");
}
return $this->resolveDefinition($definition, $parameters);
}
public function has(string $id) : bool
{
if (\array_key_exists($id, $this->resolvedEntries)) {
return \true;
}
$definition = $this->getDefinition($id);
if ($definition === null) {
return \false;
}
return $this->definitionResolver->isResolvable($definition);
}
/**
* Inject all dependencies on an existing instance.
*
* @template T
* @param object|T $instance Object to perform injection upon
* @return object|T $instance Returns the same instance
* @throws InvalidArgumentException
* @throws DependencyException Error while injecting dependencies
*/
public function injectOn(object $instance) : object
{
$className = $instance::class;
// If the class is anonymous, don't cache its definition
// Checking for anonymous classes is cleaner via Reflection, but also slower
$objectDefinition = \str_contains($className, '@anonymous') ? $this->definitionSource->getDefinition($className) : $this->getDefinition($className);
if (!$objectDefinition instanceof ObjectDefinition) {
return $instance;
}
$definition = new InstanceDefinition($instance, $objectDefinition);
$this->definitionResolver->resolve($definition);
return $instance;
}
/**
* Call the given function using the given parameters.
*
* Missing parameters will be resolved from the container.
*
* @param callable|array|string $callable Function to call.
* @param array $parameters Parameters to use. Can be indexed by the parameter names
* or not indexed (same order as the parameters).
* The array can also contain DI definitions, e.g. DI\get().
*
* @return mixed Result of the function.
*/
public function call($callable, array $parameters = []) : mixed
{
return $this->getInvoker()->call($callable, $parameters);
}
/**
* Define an object or a value in the container.
*
* @param string $name Entry name
* @param mixed|DefinitionHelper $value Value, use definition helpers to define objects
*/
public function set(string $name, mixed $value) : void
{
if ($value instanceof DefinitionHelper) {
$value = $value->getDefinition($name);
} elseif ($value instanceof \Closure) {
$value = new FactoryDefinition($name, $value);
}
if ($value instanceof ValueDefinition) {
$this->resolvedEntries[$name] = $value->getValue();
} elseif ($value instanceof Definition) {
$value->setName($name);
$this->setDefinition($name, $value);
} else {
$this->resolvedEntries[$name] = $value;
}
}
/**
* Get defined container entries.
*
* @return string[]
*/
public function getKnownEntryNames() : array
{
$entries = \array_unique(\array_merge(\array_keys($this->definitionSource->getDefinitions()), \array_keys($this->resolvedEntries)));
\sort($entries);
return $entries;
}
/**
* Get entry debug information.
*
* @param string $name Entry name
*
* @throws InvalidDefinition
* @throws NotFoundException
*/
public function debugEntry(string $name) : string
{
$definition = $this->definitionSource->getDefinition($name);
if ($definition instanceof Definition) {
return (string) $definition;
}
if (\array_key_exists($name, $this->resolvedEntries)) {
return $this->getEntryType($this->resolvedEntries[$name]);
}
throw new NotFoundException("No entry or class found for '{$name}'");
}
/**
* Get formatted entry type.
*/
private function getEntryType(mixed $entry) : string
{
if (\is_object($entry)) {
return \sprintf("Object (\n class = %s\n)", $entry::class);
}
if (\is_array($entry)) {
return \preg_replace(['/^array \\(/', '/\\)$/'], ['[', ']'], \var_export($entry, \true));
}
if (\is_string($entry)) {
return \sprintf('Value (\'%s\')', $entry);
}
if (\is_bool($entry)) {
return \sprintf('Value (%s)', $entry === \true ? 'true' : 'false');
}
return \sprintf('Value (%s)', \is_scalar($entry) ? (string) $entry : \ucfirst(\gettype($entry)));
}
/**
* Resolves a definition.
*
* Checks for circular dependencies while resolving the definition.
*
* @throws DependencyException Error while resolving the entry.
*/
private function resolveDefinition(Definition $definition, array $parameters = []) : mixed
{
$entryName = $definition->getName();
// Check if we are already getting this entry -> circular dependency
if (isset($this->entriesBeingResolved[$entryName])) {
$entryList = \implode(' -> ', [...\array_keys($this->entriesBeingResolved), $entryName]);
throw new DependencyException("Circular dependency detected while trying to resolve entry '{$entryName}': Dependencies: " . $entryList);
}
$this->entriesBeingResolved[$entryName] = \true;
// Resolve the definition
try {
$value = $this->definitionResolver->resolve($definition, $parameters);
} finally {
unset($this->entriesBeingResolved[$entryName]);
}
return $value;
}
protected function setDefinition(string $name, Definition $definition) : void
{
// Clear existing entry if it exists
if (\array_key_exists($name, $this->resolvedEntries)) {
unset($this->resolvedEntries[$name]);
}
$this->fetchedDefinitions = [];
// Completely clear this local cache
$this->definitionSource->addDefinition($definition);
}
private function getInvoker() : InvokerInterface
{
if (!$this->invoker) {
$parameterResolver = new ResolverChain([new DefinitionParameterResolver($this->definitionResolver), new NumericArrayResolver(), new AssociativeArrayResolver(), new DefaultValueResolver(), new TypeHintContainerResolver($this->delegateContainer)]);
$this->invoker = new Invoker($parameterResolver, $this);
}
return $this->invoker;
}
private function createDefaultDefinitionSource(array $definitions) : SourceChain
{
$autowiring = new ReflectionBasedAutowiring();
$source = new SourceChain([$autowiring]);
$source->setMutableDefinitionSource(new DefinitionArray($definitions, $autowiring));
return $source;
}
}

View File

@@ -1,276 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI;
use PleskRestApi\DI\Compiler\Compiler;
use PleskRestApi\DI\Definition\Source\AttributeBasedAutowiring;
use PleskRestApi\DI\Definition\Source\DefinitionArray;
use PleskRestApi\DI\Definition\Source\DefinitionFile;
use PleskRestApi\DI\Definition\Source\DefinitionSource;
use PleskRestApi\DI\Definition\Source\NoAutowiring;
use PleskRestApi\DI\Definition\Source\ReflectionBasedAutowiring;
use PleskRestApi\DI\Definition\Source\SourceCache;
use PleskRestApi\DI\Definition\Source\SourceChain;
use PleskRestApi\DI\Proxy\NativeProxyFactory;
use PleskRestApi\DI\Proxy\ProxyFactory;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
/**
* Helper to create and configure a Container.
*
* With the default options, the container created is appropriate for the development environment.
*
* Example:
*
* $builder = new ContainerBuilder();
* $container = $builder->build();
*
* @api
*
* @since 3.2
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*
* @psalm-template ContainerClass of Container
*/
class ContainerBuilder
{
/**
* Name of the container class, used to create the container.
* @var class-string<Container>
* @psalm-var class-string<ContainerClass>
*/
private string $containerClass;
/**
* Name of the container parent class, used on compiled container.
* @var class-string<Container>
* @psalm-var class-string<ContainerClass>
*/
private string $containerParentClass;
private bool $useAutowiring = \true;
private bool $useAttributes = \false;
/**
* If set, write the proxies to disk in this directory to improve performances.
*/
private ?string $proxyDirectory = null;
/**
* If PHP-DI is wrapped in another container, this references the wrapper.
*/
private ?ContainerInterface $wrapperContainer = null;
/**
* @var DefinitionSource[]|string[]|array[]
*/
private array $definitionSources = [];
/**
* Whether the container has already been built.
*/
private bool $locked = \false;
private ?string $compileToDirectory = null;
private bool $sourceCache = \false;
protected string $sourceCacheNamespace = '';
/**
* @param class-string<Container> $containerClass Name of the container class, used to create the container.
* @psalm-param class-string<ContainerClass> $containerClass
*/
public function __construct(string $containerClass = Container::class)
{
$this->containerClass = $containerClass;
}
/**
* Build and return a container.
*
* @return Container
* @psalm-return ContainerClass
*/
public function build()
{
$sources = \array_reverse($this->definitionSources);
if ($this->useAttributes) {
$autowiring = new AttributeBasedAutowiring();
$sources[] = $autowiring;
} elseif ($this->useAutowiring) {
$autowiring = new ReflectionBasedAutowiring();
$sources[] = $autowiring;
} else {
$autowiring = new NoAutowiring();
}
$sources = \array_map(function ($definitions) use($autowiring) {
if (\is_string($definitions)) {
// File
return new DefinitionFile($definitions, $autowiring);
}
if (\is_array($definitions)) {
return new DefinitionArray($definitions, $autowiring);
}
return $definitions;
}, $sources);
$source = new SourceChain($sources);
// Mutable definition source
$source->setMutableDefinitionSource(new DefinitionArray([], $autowiring));
if ($this->sourceCache) {
if (!SourceCache::isSupported()) {
throw new \Exception('APCu is not enabled, PHP-DI cannot use it as a cache');
}
// Wrap the source with the cache decorator
$source = new SourceCache($source, $this->sourceCacheNamespace);
}
$proxyFactory = \PHP_VERSION_ID >= 80400 ? new NativeProxyFactory() : new ProxyFactory($this->proxyDirectory);
$this->locked = \true;
$containerClass = $this->containerClass;
if ($this->compileToDirectory) {
$compiler = new Compiler($proxyFactory);
$compiledContainerFile = $compiler->compile($source, $this->compileToDirectory, $containerClass, $this->containerParentClass, $this->useAutowiring);
// Only load the file if it hasn't been already loaded
// (the container can be created multiple times in the same process)
if (!\class_exists($containerClass, \false)) {
require $compiledContainerFile;
}
}
return new $containerClass($source, $proxyFactory, $this->wrapperContainer);
}
/**
* Compile the container for optimum performances.
*
* Be aware that the container is compiled once and never updated!
*
* Therefore:
*
* - in production you should clear that directory every time you deploy
* - in development you should not compile the container
*
* @see https://php-di.org/doc/performances.html
*
* @psalm-template T of CompiledContainer
*
* @param string $directory Directory in which to put the compiled container.
* @param string $containerClass Name of the compiled class. Customize only if necessary.
* @param class-string<Container> $containerParentClass Name of the compiled container parent class. Customize only if necessary.
* @psalm-param class-string<T> $containerParentClass
*
* @psalm-return self<T>
*/
public function enableCompilation(string $directory, string $containerClass = 'CompiledContainer', string $containerParentClass = CompiledContainer::class) : self
{
$this->ensureNotLocked();
$this->compileToDirectory = $directory;
$this->containerClass = $containerClass;
$this->containerParentClass = $containerParentClass;
return $this;
}
/**
* Enable or disable the use of autowiring to guess injections.
*
* Enabled by default.
*
* @return $this
*/
public function useAutowiring(bool $bool) : self
{
$this->ensureNotLocked();
$this->useAutowiring = $bool;
return $this;
}
/**
* Enable or disable the use of PHP 8 attributes to configure injections.
*
* Disabled by default.
*
* @return $this
*/
public function useAttributes(bool $bool) : self
{
$this->ensureNotLocked();
$this->useAttributes = $bool;
return $this;
}
/**
* Configure the proxy generation.
*
* For dev environment, use `writeProxiesToFile(false)` (default configuration)
* For production environment, use `writeProxiesToFile(true, 'tmp/proxies')`
*
* @see https://php-di.org/doc/lazy-injection.html
*
* @param bool $writeToFile If true, write the proxies to disk to improve performances
* @param string|null $proxyDirectory Directory where to write the proxies
* @return $this
* @throws InvalidArgumentException when writeToFile is set to true and the proxy directory is null
*/
public function writeProxiesToFile(bool $writeToFile, ?string $proxyDirectory = null) : self
{
$this->ensureNotLocked();
if ($writeToFile && $proxyDirectory === null) {
throw new InvalidArgumentException('The proxy directory must be specified if you want to write proxies on disk');
}
$this->proxyDirectory = $writeToFile ? $proxyDirectory : null;
return $this;
}
/**
* If PHP-DI's container is wrapped by another container, we can
* set this so that PHP-DI will use the wrapper rather than itself for building objects.
*
* @return $this
*/
public function wrapContainer(ContainerInterface $otherContainer) : self
{
$this->ensureNotLocked();
$this->wrapperContainer = $otherContainer;
return $this;
}
/**
* Add definitions to the container.
*
* @param string|array|DefinitionSource ...$definitions Can be an array of definitions, the
* name of a file containing definitions
* or a DefinitionSource object.
* @return $this
*/
public function addDefinitions(string|array|\PleskRestApi\DI\Definition\Source\DefinitionSource ...$definitions) : self
{
$this->ensureNotLocked();
foreach ($definitions as $definition) {
$this->definitionSources[] = $definition;
}
return $this;
}
/**
* Enables the use of APCu to cache definitions.
*
* You must have APCu enabled to use it.
*
* Before using this feature, you should try these steps first:
* - enable compilation if not already done (see `enableCompilation()`)
* - if you use autowiring or attributes, add all the classes you are using into your configuration so that
* PHP-DI knows about them and compiles them
* Once this is done, you can try to optimize performances further with APCu. It can also be useful if you use
* `Container::make()` instead of `get()` (`make()` calls cannot be compiled so they are not optimized).
*
* Remember to clear APCu on each deploy else your application will have a stale cache. Do not enable the cache
* in development environment: any change you will make to the code will be ignored because of the cache.
*
* @see https://php-di.org/doc/performances.html
*
* @param string $cacheNamespace use unique namespace per container when sharing a single APC memory pool to prevent cache collisions
* @return $this
*/
public function enableDefinitionCache(string $cacheNamespace = '') : self
{
$this->ensureNotLocked();
$this->sourceCache = \true;
$this->sourceCacheNamespace = $cacheNamespace;
return $this;
}
/**
* Are we building a compiled container?
*/
public function isCompilationEnabled() : bool
{
return (bool) $this->compileToDirectory;
}
private function ensureNotLocked() : void
{
if ($this->locked) {
throw new \LogicException('The ContainerBuilder cannot be modified after the container has been built');
}
}
}

View File

@@ -1,52 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
/**
* Definition of an array containing values or references.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ArrayDefinition implements Definition
{
/** Entry name. */
private string $name = '';
public function __construct(private array $values)
{
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : void
{
$this->name = $name;
}
public function getValues() : array
{
return $this->values;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
$this->values = \array_map($replacer, $this->values);
}
public function __toString() : string
{
$str = '[' . \PHP_EOL;
foreach ($this->values as $key => $value) {
if (\is_string($key)) {
$key = "'" . $key . "'";
}
$str .= ' ' . $key . ' => ';
if ($value instanceof Definition) {
$str .= \str_replace(\PHP_EOL, \PHP_EOL . ' ', (string) $value);
} else {
$str .= \var_export($value, \true);
}
$str .= ',' . \PHP_EOL;
}
return $str . ']';
}
}

View File

@@ -1,30 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
/**
* Extends an array definition by adding new elements into it.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ArrayDefinitionExtension extends ArrayDefinition implements ExtendsPreviousDefinition
{
private ?ArrayDefinition $subDefinition = null;
public function getValues() : array
{
if (!$this->subDefinition) {
return parent::getValues();
}
return \array_merge($this->subDefinition->getValues(), parent::getValues());
}
public function setExtendedDefinition(Definition $definition) : void
{
if (!$definition instanceof ArrayDefinition) {
throw new InvalidDefinition(\sprintf('Definition %s tries to add array entries but the previous definition is not an array', $this->getName()));
}
$this->subDefinition = $definition;
}
}

View File

@@ -1,11 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
/**
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AutowireDefinition extends ObjectDefinition
{
}

View File

@@ -1,31 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
/**
* Factory that decorates a sub-definition.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DecoratorDefinition extends FactoryDefinition implements Definition, ExtendsPreviousDefinition
{
private ?Definition $decorated = null;
public function setExtendedDefinition(Definition $definition) : void
{
$this->decorated = $definition;
}
public function getDecoratedDefinition() : ?Definition
{
return $this->decorated;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
// no nested definitions
}
public function __toString() : string
{
return 'Decorate(' . $this->getName() . ')';
}
}

View File

@@ -1,32 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
use PleskRestApi\DI\Factory\RequestedEntry;
/**
* Definition.
*
* @internal This interface is internal to PHP-DI and may change between minor versions.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface Definition extends RequestedEntry, \Stringable
{
/**
* Returns the name of the entry in the container.
*/
public function getName() : string;
/**
* Set the name of the entry in the container.
*/
public function setName(string $name) : void;
/**
* Apply a callable that replaces the definitions nested in this definition.
*/
public function replaceNestedDefinitions(callable $replacer) : void;
/**
* Definitions can be cast to string for debugging information.
*/
public function __toString() : string;
}

View File

@@ -1,109 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Dumper;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\ObjectDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition\MethodInjection;
use ReflectionException;
/**
* Dumps object definitions to string for debugging purposes.
*
* @since 4.1
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ObjectDefinitionDumper
{
/**
* Returns the definition as string representation.
*/
public function dump(ObjectDefinition $definition) : string
{
$className = $definition->getClassName();
$classExist = \class_exists($className) || \interface_exists($className);
// Class
if (!$classExist) {
$warning = '#UNKNOWN# ';
} else {
$class = new \ReflectionClass($className);
$warning = $class->isInstantiable() ? '' : '#NOT INSTANTIABLE# ';
}
$str = \sprintf(' class = %s%s', $warning, $className);
// Lazy
$str .= \PHP_EOL . ' lazy = ' . \var_export($definition->isLazy(), \true);
if ($classExist) {
// Constructor
$str .= $this->dumpConstructor($className, $definition);
// Properties
$str .= $this->dumpProperties($definition);
// Methods
$str .= $this->dumpMethods($className, $definition);
}
return \sprintf('Object (' . \PHP_EOL . '%s' . \PHP_EOL . ')', $str);
}
/**
* @param class-string $className
*/
private function dumpConstructor(string $className, ObjectDefinition $definition) : string
{
$str = '';
$constructorInjection = $definition->getConstructorInjection();
if ($constructorInjection !== null) {
$parameters = $this->dumpMethodParameters($className, $constructorInjection);
$str .= \sprintf(\PHP_EOL . ' __construct(' . \PHP_EOL . ' %s' . \PHP_EOL . ' )', $parameters);
}
return $str;
}
private function dumpProperties(ObjectDefinition $definition) : string
{
$str = '';
foreach ($definition->getPropertyInjections() as $propertyInjection) {
$value = $propertyInjection->getValue();
$valueStr = $value instanceof Definition ? (string) $value : \var_export($value, \true);
$str .= \sprintf(\PHP_EOL . ' $%s = %s', $propertyInjection->getPropertyName(), $valueStr);
}
return $str;
}
/**
* @param class-string $className
*/
private function dumpMethods(string $className, ObjectDefinition $definition) : string
{
$str = '';
foreach ($definition->getMethodInjections() as $methodInjection) {
$parameters = $this->dumpMethodParameters($className, $methodInjection);
$str .= \sprintf(\PHP_EOL . ' %s(' . \PHP_EOL . ' %s' . \PHP_EOL . ' )', $methodInjection->getMethodName(), $parameters);
}
return $str;
}
/**
* @param class-string $className
*/
private function dumpMethodParameters(string $className, MethodInjection $methodInjection) : string
{
$methodReflection = new \ReflectionMethod($className, $methodInjection->getMethodName());
$args = [];
$definitionParameters = $methodInjection->getParameters();
foreach ($methodReflection->getParameters() as $index => $parameter) {
if (\array_key_exists($index, $definitionParameters)) {
$value = $definitionParameters[$index];
$valueStr = $value instanceof Definition ? (string) $value : \var_export($value, \true);
$args[] = \sprintf('$%s = %s', $parameter->getName(), $valueStr);
continue;
}
// If the parameter is optional and wasn't specified, we take its default value
if ($parameter->isOptional()) {
try {
$value = $parameter->getDefaultValue();
$args[] = \sprintf('$%s = (default value) %s', $parameter->getName(), \var_export($value, \true));
continue;
} catch (ReflectionException) {
// The default value can't be read through Reflection because it is a PHP internal class
}
}
$args[] = \sprintf('$%s = #UNDEFINED#', $parameter->getName());
}
return \implode(\PHP_EOL . ' ', $args);
}
}

View File

@@ -1,71 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
/**
* Defines a reference to an environment variable, with fallback to a default
* value if the environment variable is not defined.
*
* @author James Harris <james.harris@icecave.com.au>
*/
class EnvironmentVariableDefinition implements Definition
{
/** Entry name. */
private string $name = '';
/**
* @param string $variableName The name of the environment variable
* @param bool $isOptional Whether or not the environment variable definition is optional. If true and the environment variable given by $variableName has not been defined, $defaultValue is used.
* @param mixed $defaultValue The default value to use if the environment variable is optional and not provided
*/
public function __construct(private string $variableName, private bool $isOptional = \false, private mixed $defaultValue = null)
{
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : void
{
$this->name = $name;
}
/**
* @return string The name of the environment variable
*/
public function getVariableName() : string
{
return $this->variableName;
}
/**
* @return bool Whether or not the environment variable definition is optional
*/
public function isOptional() : bool
{
return $this->isOptional;
}
/**
* @return mixed The default value to use if the environment variable is optional and not provided
*/
public function getDefaultValue() : mixed
{
return $this->defaultValue;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
$this->defaultValue = $replacer($this->defaultValue);
}
public function __toString() : string
{
$str = ' variable = ' . $this->variableName . \PHP_EOL . ' optional = ' . ($this->isOptional ? 'yes' : 'no');
if ($this->isOptional) {
if ($this->defaultValue instanceof Definition) {
$nestedDefinition = (string) $this->defaultValue;
$defaultValueStr = \str_replace(\PHP_EOL, \PHP_EOL . ' ', $nestedDefinition);
} else {
$defaultValueStr = \var_export($this->defaultValue, \true);
}
$str .= \PHP_EOL . ' default = ' . $defaultValueStr;
}
return \sprintf('Environment variable (' . \PHP_EOL . '%s' . \PHP_EOL . ')', $str);
}
}

View File

@@ -1,13 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Exception;
/**
* Error in the definitions using PHP attributes.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class InvalidAttribute extends InvalidDefinition
{
}

View File

@@ -1,19 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Exception;
use PleskRestApi\DI\Definition\Definition;
use Psr\Container\ContainerExceptionInterface;
/**
* Invalid DI definitions.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class InvalidDefinition extends \Exception implements ContainerExceptionInterface
{
public static function create(Definition $definition, string $message, ?\Exception $previous = null) : self
{
return new self(\sprintf('%s' . \PHP_EOL . 'Full definition:' . \PHP_EOL . '%s', $message, (string) $definition), 0, $previous);
}
}

View File

@@ -1,14 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
/**
* A definition that extends a previous definition with the same name.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface ExtendsPreviousDefinition extends Definition
{
public function setExtendedDefinition(Definition $definition) : void;
}

View File

@@ -1,68 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
/**
* Definition of a value or class with a factory.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class FactoryDefinition implements Definition
{
/**
* Entry name.
*/
private string $name;
/**
* Callable that returns the value.
* @var callable
*/
private $factory;
/**
* Factory parameters.
* @var mixed[]
*/
private array $parameters;
/**
* @param string $name Entry name
* @param callable|array|string $factory Callable that returns the value associated to the entry name.
* @param array $parameters Parameters to be passed to the callable
*/
public function __construct(string $name, callable|array|string $factory, array $parameters = [])
{
$this->name = $name;
$this->factory = $factory;
$this->parameters = $parameters;
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : void
{
$this->name = $name;
}
/**
* @return callable|array|string Callable that returns the value associated to the entry name.
*/
public function getCallable() : callable|array|string
{
return $this->factory;
}
/**
* @return array Array containing the parameters to be passed to the callable, indexed by name.
*/
public function getParameters() : array
{
return $this->parameters;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
$this->parameters = \array_map($replacer, $this->parameters);
}
public function __toString() : string
{
return 'Factory';
}
}

View File

@@ -1,63 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Helper;
use PleskRestApi\DI\Definition\AutowireDefinition;
/**
* Helps defining how to create an instance of a class using autowiring.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AutowireDefinitionHelper extends CreateDefinitionHelper
{
public const DEFINITION_CLASS = AutowireDefinition::class;
/**
* Defines a value for a specific argument of the constructor.
*
* This method is usually used together with attributes or autowiring, when a parameter
* is not (or cannot be) type-hinted. Using this method instead of constructor() allows to
* avoid defining all the parameters (letting them being resolved using attributes or autowiring)
* and only define one.
*
* @param string|int $parameter Parameter name of position for which the value will be given.
* @param mixed $value Value to give to this parameter.
*
* @return $this
*/
public function constructorParameter(string|int $parameter, mixed $value) : self
{
$this->constructor[$parameter] = $value;
return $this;
}
/**
* Defines a method to call and a value for a specific argument.
*
* This method is usually used together with attributes or autowiring, when a parameter
* is not (or cannot be) type-hinted. Using this method instead of method() allows to
* avoid defining all the parameters (letting them being resolved using attributes or
* autowiring) and only define one.
*
* If multiple calls to the method have been configured already (e.g. in a previous definition)
* then this method only overrides the parameter for the *first* call.
*
* @param string $method Name of the method to call.
* @param string|int $parameter Parameter name of position for which the value will be given.
* @param mixed $value Value to give to this parameter.
*
* @return $this
*/
public function methodParameter(string $method, string|int $parameter, mixed $value) : self
{
// Special case for the constructor
if ($method === '__construct') {
$this->constructor[$parameter] = $value;
return $this;
}
if (!isset($this->methods[$method])) {
$this->methods[$method] = [0 => []];
}
$this->methods[$method][0][$parameter] = $value;
return $this;
}
}

View File

@@ -1,158 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Helper;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition\MethodInjection;
use PleskRestApi\DI\Definition\ObjectDefinition\PropertyInjection;
/**
* Helps defining how to create an instance of a class.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class CreateDefinitionHelper implements DefinitionHelper
{
private const DEFINITION_CLASS = ObjectDefinition::class;
private ?string $className;
private ?bool $lazy = null;
/**
* Array of constructor parameters.
*/
protected array $constructor = [];
/**
* Array of properties and their value.
*/
private array $properties = [];
/**
* Array of methods and their parameters.
*/
protected array $methods = [];
/**
* Helper for defining an object.
*
* @param string|null $className Class name of the object.
* If null, the name of the entry (in the container) will be used as class name.
*/
public function __construct(?string $className = null)
{
$this->className = $className;
}
/**
* Define the entry as lazy.
*
* A lazy entry is created only when it is used, a proxy is injected instead.
*
* @return $this
*/
public function lazy() : self
{
$this->lazy = \true;
return $this;
}
/**
* Defines the arguments to use to call the constructor.
*
* This method takes a variable number of arguments, example:
* ->constructor($param1, $param2, $param3)
*
* @param mixed ...$parameters Parameters to use for calling the constructor of the class.
*
* @return $this
*/
public function constructor(mixed ...$parameters) : self
{
$this->constructor = $parameters;
return $this;
}
/**
* Defines a value to inject in a property of the object.
*
* @param string $property Entry in which to inject the value.
* @param mixed $value Value to inject in the property.
*
* @return $this
*/
public function property(string $property, mixed $value) : self
{
$this->properties[$property] = $value;
return $this;
}
/**
* Defines a method to call and the arguments to use.
*
* This method takes a variable number of arguments after the method name, example:
*
* ->method('myMethod', $param1, $param2)
*
* Can be used multiple times to declare multiple calls.
*
* @param string $method Name of the method to call.
* @param mixed ...$parameters Parameters to use for calling the method.
*
* @return $this
*/
public function method(string $method, mixed ...$parameters) : self
{
if (!isset($this->methods[$method])) {
$this->methods[$method] = [];
}
$this->methods[$method][] = $parameters;
return $this;
}
public function getDefinition(string $entryName) : ObjectDefinition
{
$class = $this::DEFINITION_CLASS;
/** @var ObjectDefinition $definition */
$definition = new $class($entryName, $this->className);
if ($this->lazy !== null) {
$definition->setLazy($this->lazy);
}
if (!empty($this->constructor)) {
$parameters = $this->fixParameters($definition, '__construct', $this->constructor);
$constructorInjection = MethodInjection::constructor($parameters);
$definition->setConstructorInjection($constructorInjection);
}
if (!empty($this->properties)) {
foreach ($this->properties as $property => $value) {
$definition->addPropertyInjection(new PropertyInjection($property, $value));
}
}
if (!empty($this->methods)) {
foreach ($this->methods as $method => $calls) {
foreach ($calls as $parameters) {
$parameters = $this->fixParameters($definition, $method, $parameters);
$methodInjection = new MethodInjection($method, $parameters);
$definition->addMethodInjection($methodInjection);
}
}
}
return $definition;
}
/**
* Fixes parameters indexed by the parameter name -> reindex by position.
*
* This is necessary so that merging definitions between sources is possible.
*
* @throws InvalidDefinition
*/
private function fixParameters(ObjectDefinition $definition, string $method, array $parameters) : array
{
$fixedParameters = [];
foreach ($parameters as $index => $parameter) {
// Parameter indexed by the parameter name, we reindex it with its position
if (\is_string($index)) {
$callable = [$definition->getClassName(), $method];
try {
$reflectionParameter = new \ReflectionParameter($callable, $index);
} catch (\ReflectionException $e) {
throw InvalidDefinition::create($definition, \sprintf("Parameter with name '%s' could not be found. %s.", $index, $e->getMessage()));
}
$index = $reflectionParameter->getPosition();
}
$fixedParameters[$index] = $parameter;
}
return $fixedParameters;
}
}

View File

@@ -1,18 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Helper;
use PleskRestApi\DI\Definition\Definition;
/**
* Helps defining container entries.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface DefinitionHelper
{
/**
* @param string $entryName Container entry name
*/
public function getDefinition(string $entryName) : Definition;
}

View File

@@ -1,54 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Helper;
use PleskRestApi\DI\Definition\DecoratorDefinition;
use PleskRestApi\DI\Definition\FactoryDefinition;
/**
* Helps defining how to create an instance of a class using a factory (callable).
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class FactoryDefinitionHelper implements DefinitionHelper
{
/**
* @var callable
*/
private $factory;
private bool $decorate;
private array $parameters = [];
/**
* @param bool $decorate Is the factory decorating a previous definition?
*/
public function __construct(callable|array|string $factory, bool $decorate = \false)
{
$this->factory = $factory;
$this->decorate = $decorate;
}
public function getDefinition(string $entryName) : FactoryDefinition
{
if ($this->decorate) {
return new DecoratorDefinition($entryName, $this->factory, $this->parameters);
}
return new FactoryDefinition($entryName, $this->factory, $this->parameters);
}
/**
* Defines arguments to pass to the factory.
*
* Because factory methods do not yet support attributes or autowiring, this method
* should be used to define all parameters except the ContainerInterface and RequestedEntry.
*
* Multiple calls can be made to the method to override individual values.
*
* @param string $parameter Name or index of the parameter for which the value will be given.
* @param mixed $value Value to give to this parameter.
*
* @return $this
*/
public function parameter(string $parameter, mixed $value) : self
{
$this->parameters[$parameter] = $value;
return $this;
}
}

View File

@@ -1,45 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
/**
* Defines injections on an existing class instance.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class InstanceDefinition implements Definition
{
/**
* @param object $instance Instance on which to inject dependencies.
*/
public function __construct(private object $instance, private ObjectDefinition $objectDefinition)
{
}
public function getName() : string
{
// Name are superfluous for instance definitions
return '';
}
public function setName(string $name) : void
{
// Name are superfluous for instance definitions
}
public function getInstance() : object
{
return $this->instance;
}
public function getObjectDefinition() : ObjectDefinition
{
return $this->objectDefinition;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
$this->objectDefinition->replaceNestedDefinitions($replacer);
}
public function __toString() : string
{
return 'Instance';
}
}

View File

@@ -1,199 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
use PleskRestApi\DI\Definition\Dumper\ObjectDefinitionDumper;
use PleskRestApi\DI\Definition\ObjectDefinition\MethodInjection;
use PleskRestApi\DI\Definition\ObjectDefinition\PropertyInjection;
use PleskRestApi\DI\Definition\Source\DefinitionArray;
use ReflectionClass;
/**
* Defines how an object can be instantiated.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ObjectDefinition implements Definition
{
/**
* Entry name (most of the time, same as $classname).
*/
private string $name;
/**
* Class name (if null, then the class name is $name).
*/
protected ?string $className = null;
protected ?MethodInjection $constructorInjection = null;
protected array $propertyInjections = [];
/**
* Method calls.
* @var MethodInjection[][]
*/
protected array $methodInjections = [];
protected ?bool $lazy = null;
/**
* Store if the class exists. Storing it (in cache) avoids recomputing this.
*/
private bool $classExists;
/**
* Store if the class is instantiable. Storing it (in cache) avoids recomputing this.
*/
private bool $isInstantiable;
/**
* @param string $name Entry name
*/
public function __construct(string $name, ?string $className = null)
{
$this->name = $name;
$this->setClassName($className);
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : void
{
$this->name = $name;
}
public function setClassName(?string $className) : void
{
$this->className = $className;
$this->updateCache();
}
public function getClassName() : string
{
return $this->className ?? $this->name;
}
public function getConstructorInjection() : ?MethodInjection
{
return $this->constructorInjection;
}
public function setConstructorInjection(MethodInjection $constructorInjection) : void
{
$this->constructorInjection = $constructorInjection;
}
public function completeConstructorInjection(MethodInjection $injection) : void
{
if ($this->constructorInjection !== null) {
// Merge
$this->constructorInjection->merge($injection);
} else {
// Set
$this->constructorInjection = $injection;
}
}
/**
* @return PropertyInjection[] Property injections
*/
public function getPropertyInjections() : array
{
return $this->propertyInjections;
}
public function addPropertyInjection(PropertyInjection $propertyInjection) : void
{
$className = $propertyInjection->getClassName();
if ($className) {
// Index with the class name to avoid collisions between parent and
// child private properties with the same name
$key = $className . '::' . $propertyInjection->getPropertyName();
} else {
$key = $propertyInjection->getPropertyName();
}
$this->propertyInjections[$key] = $propertyInjection;
}
/**
* @return MethodInjection[] Method injections
*/
public function getMethodInjections() : array
{
// Return array leafs
$injections = [];
\array_walk_recursive($this->methodInjections, function ($injection) use(&$injections) {
$injections[] = $injection;
});
return $injections;
}
public function addMethodInjection(MethodInjection $methodInjection) : void
{
$method = $methodInjection->getMethodName();
if (!isset($this->methodInjections[$method])) {
$this->methodInjections[$method] = [];
}
$this->methodInjections[$method][] = $methodInjection;
}
public function completeFirstMethodInjection(MethodInjection $injection) : void
{
$method = $injection->getMethodName();
if (isset($this->methodInjections[$method][0])) {
// Merge
$this->methodInjections[$method][0]->merge($injection);
} else {
// Set
$this->addMethodInjection($injection);
}
}
public function setLazy(?bool $lazy = null) : void
{
$this->lazy = $lazy;
}
public function isLazy() : bool
{
if ($this->lazy !== null) {
return $this->lazy;
}
// Default value
return \false;
}
public function classExists() : bool
{
return $this->classExists;
}
public function isInstantiable() : bool
{
return $this->isInstantiable;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
\array_walk($this->propertyInjections, function (PropertyInjection $propertyInjection) use($replacer) {
$propertyInjection->replaceNestedDefinition($replacer);
});
$this->constructorInjection?->replaceNestedDefinitions($replacer);
\array_walk($this->methodInjections, function ($injectionArray) use($replacer) {
\array_walk($injectionArray, function (MethodInjection $methodInjection) use($replacer) {
$methodInjection->replaceNestedDefinitions($replacer);
});
});
}
/**
* Replaces all the wildcards in the string with the given replacements.
*
* @param string[] $replacements
*/
public function replaceWildcards(array $replacements) : void
{
$className = $this->getClassName();
foreach ($replacements as $replacement) {
$pos = \strpos($className, DefinitionArray::WILDCARD);
if ($pos !== \false) {
$className = \substr_replace($className, $replacement, $pos, 1);
}
}
$this->setClassName($className);
}
public function __toString() : string
{
return (new ObjectDefinitionDumper())->dump($this);
}
private function updateCache() : void
{
$className = $this->getClassName();
$this->classExists = \class_exists($className) || \interface_exists($className);
if (!$this->classExists) {
$this->isInstantiable = \false;
return;
}
/** @var class-string $className */
$class = new ReflectionClass($className);
$this->isInstantiable = $class->isInstantiable();
}
}

View File

@@ -1,63 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\ObjectDefinition;
use PleskRestApi\DI\Definition\Definition;
/**
* Describe an injection in an object method.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class MethodInjection implements Definition
{
/**
* @param mixed[] $parameters
*/
public function __construct(private string $methodName, private array $parameters = [])
{
}
public static function constructor(array $parameters = []) : self
{
return new self('__construct', $parameters);
}
public function getMethodName() : string
{
return $this->methodName;
}
/**
* @return mixed[]
*/
public function getParameters() : array
{
return $this->parameters;
}
/**
* Replace the parameters of the definition by a new array of parameters.
*/
public function replaceParameters(array $parameters) : void
{
$this->parameters = $parameters;
}
public function merge(self $definition) : void
{
// In case of conflicts, the current definition prevails.
$this->parameters += $definition->parameters;
}
public function getName() : string
{
return '';
}
public function setName(string $name) : void
{
// The name does not matter for method injections
}
public function replaceNestedDefinitions(callable $replacer) : void
{
$this->parameters = \array_map($replacer, $this->parameters);
}
public function __toString() : string
{
return \sprintf('method(%s)', $this->methodName);
}
}

View File

@@ -1,53 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\ObjectDefinition;
/**
* Describe an injection in a class property.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class PropertyInjection
{
private string $propertyName;
/**
* Value that should be injected in the property.
*/
private mixed $value;
/**
* Use for injecting in properties of parent classes: the class name
* must be the name of the parent class because private properties
* can be attached to the parent classes, not the one we are resolving.
*/
private ?string $className;
/**
* @param string $propertyName Property name
* @param mixed $value Value that should be injected in the property
*/
public function __construct(string $propertyName, mixed $value, ?string $className = null)
{
$this->propertyName = $propertyName;
$this->value = $value;
$this->className = $className;
}
public function getPropertyName() : string
{
return $this->propertyName;
}
/**
* @return mixed Value that should be injected in the property
*/
public function getValue() : mixed
{
return $this->value;
}
public function getClassName() : ?string
{
return $this->className;
}
public function replaceNestedDefinition(callable $replacer) : void
{
$this->value = $replacer($this->value);
}
}

View File

@@ -1,50 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
use Psr\Container\ContainerInterface;
/**
* Represents a reference to another entry.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Reference implements Definition, SelfResolvingDefinition
{
/** Entry name. */
private string $name = '';
/**
* @param string $targetEntryName Name of the target entry
*/
public function __construct(private string $targetEntryName)
{
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : void
{
$this->name = $name;
}
public function getTargetEntryName() : string
{
return $this->targetEntryName;
}
public function resolve(ContainerInterface $container) : mixed
{
return $container->get($this->getTargetEntryName());
}
public function isResolvable(ContainerInterface $container) : bool
{
return $container->has($this->getTargetEntryName());
}
public function replaceNestedDefinitions(callable $replacer) : void
{
// no nested definitions
}
public function __toString() : string
{
return \sprintf('get(%s)', $this->targetEntryName);
}
}

View File

@@ -1,63 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Resolver;
use PleskRestApi\DI\Definition\ArrayDefinition;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\DependencyException;
use Exception;
/**
* Resolves an array definition to a value.
*
* @template-implements DefinitionResolver<ArrayDefinition>
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ArrayResolver implements DefinitionResolver
{
/**
* @param DefinitionResolver $definitionResolver Used to resolve nested definitions.
*/
public function __construct(private DefinitionResolver $definitionResolver)
{
}
/**
* {@inheritDoc}
*
* Resolve an array definition to a value.
*
* An array definition can contain simple values or references to other entries.
*
* @param ArrayDefinition $definition
*/
public function resolve(Definition $definition, array $parameters = []) : array
{
$values = $definition->getValues();
// Resolve nested definitions
\array_walk_recursive($values, function (&$value, $key) use($definition) {
if ($value instanceof Definition) {
$value = $this->resolveDefinition($value, $definition, $key);
}
});
return $values;
}
public function isResolvable(Definition $definition, array $parameters = []) : bool
{
return \true;
}
/**
* @throws DependencyException
*/
private function resolveDefinition(Definition $value, ArrayDefinition $definition, int|string $key) : mixed
{
try {
return $this->definitionResolver->resolve($value);
} catch (DependencyException $e) {
throw $e;
} catch (Exception $e) {
throw new DependencyException(\sprintf('Error while resolving %s[%s]. %s', $definition->getName(), $key, $e->getMessage()), 0, $e);
}
}
}

View File

@@ -1,56 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Resolver;
use PleskRestApi\DI\Definition\DecoratorDefinition;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use Psr\Container\ContainerInterface;
/**
* Resolves a decorator definition to a value.
*
* @template-implements DefinitionResolver<DecoratorDefinition>
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DecoratorResolver implements DefinitionResolver
{
/**
* The resolver needs a container. This container will be passed to the factory as a parameter
* so that the factory can access other entries of the container.
*
* @param DefinitionResolver $definitionResolver Used to resolve nested definitions.
*/
public function __construct(private ContainerInterface $container, private DefinitionResolver $definitionResolver)
{
}
/**
* Resolve a decorator definition to a value.
*
* This will call the callable of the definition and pass it the decorated entry.
*
* @param DecoratorDefinition $definition
*/
public function resolve(Definition $definition, array $parameters = []) : mixed
{
$callable = $definition->getCallable();
if (!\is_callable($callable)) {
throw new InvalidDefinition(\sprintf('The decorator "%s" is not callable', $definition->getName()));
}
$decoratedDefinition = $definition->getDecoratedDefinition();
if (!$decoratedDefinition instanceof Definition) {
if (!$definition->getName()) {
throw new InvalidDefinition('Decorators cannot be nested in another definition');
}
throw new InvalidDefinition(\sprintf('Entry "%s" decorates nothing: no previous definition with the same name was found', $definition->getName()));
}
$decorated = $this->definitionResolver->resolve($decoratedDefinition, $parameters);
return $callable($decorated, $this->container);
}
public function isResolvable(Definition $definition, array $parameters = []) : bool
{
return \true;
}
}

View File

@@ -1,39 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Resolver;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\DependencyException;
/**
* Resolves a definition to a value.
*
* @since 4.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*
* @template T of Definition
*/
interface DefinitionResolver
{
/**
* Resolve a definition to a value.
*
* @param Definition $definition Object that defines how the value should be obtained.
* @psalm-param T $definition
* @param array $parameters Optional parameters to use to build the entry.
* @return mixed Value obtained from the definition.
*
* @throws InvalidDefinition If the definition cannot be resolved.
* @throws DependencyException
*/
public function resolve(Definition $definition, array $parameters = []) : mixed;
/**
* Check if a definition can be resolved.
*
* @param Definition $definition Object that defines how the value should be obtained.
* @psalm-param T $definition
* @param array $parameters Optional parameters to use to build the entry.
*/
public function isResolvable(Definition $definition, array $parameters = []) : bool;
}

View File

@@ -1,53 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Resolver;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\EnvironmentVariableDefinition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
/**
* Resolves a environment variable definition to a value.
*
* @template-implements DefinitionResolver<EnvironmentVariableDefinition>
*
* @author James Harris <james.harris@icecave.com.au>
*/
class EnvironmentVariableResolver implements DefinitionResolver
{
/** @var callable */
private $variableReader;
public function __construct(private DefinitionResolver $definitionResolver, $variableReader = null)
{
$this->variableReader = $variableReader ?? [$this, 'getEnvVariable'];
}
/**
* Resolve an environment variable definition to a value.
*
* @param EnvironmentVariableDefinition $definition
*/
public function resolve(Definition $definition, array $parameters = []) : mixed
{
$value = \call_user_func($this->variableReader, $definition->getVariableName());
if (\false !== $value) {
return $value;
}
if (!$definition->isOptional()) {
throw new InvalidDefinition(\sprintf("The environment variable '%s' has not been defined", $definition->getVariableName()));
}
$value = $definition->getDefaultValue();
// Nested definition
if ($value instanceof Definition) {
return $this->definitionResolver->resolve($value);
}
return $value;
}
public function isResolvable(Definition $definition, array $parameters = []) : bool
{
return \true;
}
protected function getEnvVariable(string $variableName)
{
return $_ENV[$variableName] ?? $_SERVER[$variableName] ?? \getenv($variableName);
}
}

View File

@@ -1,81 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Resolver;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Definition\FactoryDefinition;
use PleskRestApi\DI\Invoker\FactoryParameterResolver;
use PleskRestApi\Invoker\Exception\NotCallableException;
use PleskRestApi\Invoker\Exception\NotEnoughParametersException;
use PleskRestApi\Invoker\Invoker;
use PleskRestApi\Invoker\ParameterResolver\AssociativeArrayResolver;
use PleskRestApi\Invoker\ParameterResolver\DefaultValueResolver;
use PleskRestApi\Invoker\ParameterResolver\NumericArrayResolver;
use PleskRestApi\Invoker\ParameterResolver\ResolverChain;
use Psr\Container\ContainerInterface;
/**
* Resolves a factory definition to a value.
*
* @template-implements DefinitionResolver<FactoryDefinition>
*
* @since 4.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class FactoryResolver implements DefinitionResolver
{
private ?Invoker $invoker = null;
/**
* The resolver needs a container. This container will be passed to the factory as a parameter
* so that the factory can access other entries of the container.
*/
public function __construct(private ContainerInterface $container, private DefinitionResolver $resolver)
{
}
/**
* Resolve a factory definition to a value.
*
* This will call the callable of the definition.
*
* @param FactoryDefinition $definition
*/
public function resolve(Definition $definition, array $parameters = []) : mixed
{
if (!$this->invoker) {
$parameterResolver = new ResolverChain([new AssociativeArrayResolver(), new FactoryParameterResolver($this->container), new NumericArrayResolver(), new DefaultValueResolver()]);
$this->invoker = new Invoker($parameterResolver, $this->container);
}
$callable = $definition->getCallable();
try {
$providedParams = [$this->container, $definition];
$extraParams = $this->resolveExtraParams($definition->getParameters());
$providedParams = \array_merge($providedParams, $extraParams, $parameters);
return $this->invoker->call($callable, $providedParams);
} catch (NotCallableException $e) {
// Custom error message to help debugging
if (\is_string($callable) && \class_exists($callable) && \method_exists($callable, '__invoke')) {
throw new InvalidDefinition(\sprintf('Entry "%s" cannot be resolved: factory %s. Invokable classes cannot be automatically resolved if autowiring is disabled on the container, you need to enable autowiring or define the entry manually.', $definition->getName(), $e->getMessage()));
}
throw new InvalidDefinition(\sprintf('Entry "%s" cannot be resolved: factory %s', $definition->getName(), $e->getMessage()));
} catch (NotEnoughParametersException $e) {
throw new InvalidDefinition(\sprintf('Entry "%s" cannot be resolved: %s', $definition->getName(), $e->getMessage()));
}
}
public function isResolvable(Definition $definition, array $parameters = []) : bool
{
return \true;
}
private function resolveExtraParams(array $params) : array
{
$resolved = [];
foreach ($params as $key => $value) {
// Nested definitions
if ($value instanceof Definition) {
$value = $this->resolver->resolve($value);
}
$resolved[$key] = $value;
}
return $resolved;
}
}

View File

@@ -1,41 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Resolver;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\InstanceDefinition;
use PleskRestApi\DI\DependencyException;
use Psr\Container\NotFoundExceptionInterface;
/**
* Injects dependencies on an existing instance.
*
* @template-implements DefinitionResolver<InstanceDefinition>
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class InstanceInjector extends ObjectCreator implements DefinitionResolver
{
/**
* Injects dependencies on an existing instance.
*
* @param InstanceDefinition $definition
* @psalm-suppress ImplementedParamTypeMismatch
*/
public function resolve(Definition $definition, array $parameters = []) : ?object
{
/** @psalm-suppress InvalidCatch */
try {
$this->injectMethodsAndProperties($definition->getInstance(), $definition->getObjectDefinition());
} catch (NotFoundExceptionInterface $e) {
$message = \sprintf('Error while injecting dependencies into %s: %s', \get_class($definition->getInstance()), $e->getMessage());
throw new DependencyException($message, 0, $e);
}
return $definition;
}
public function isResolvable(Definition $definition, array $parameters = []) : bool
{
return \true;
}
}

View File

@@ -1,150 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Resolver;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition\PropertyInjection;
use PleskRestApi\DI\DependencyException;
use PleskRestApi\DI\Proxy\ProxyFactoryInterface;
use Exception;
use Psr\Container\NotFoundExceptionInterface;
use ReflectionClass;
use ReflectionProperty;
/**
* Create objects based on an object definition.
*
* @template-implements DefinitionResolver<ObjectDefinition>
*
* @since 4.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ObjectCreator implements DefinitionResolver
{
private ParameterResolver $parameterResolver;
/**
* @param DefinitionResolver $definitionResolver Used to resolve nested definitions.
* @param ProxyFactoryInterface $proxyFactory Used to create proxies for lazy injections.
*/
public function __construct(private DefinitionResolver $definitionResolver, private ProxyFactoryInterface $proxyFactory)
{
$this->parameterResolver = new ParameterResolver($definitionResolver);
}
/**
* Resolve a class definition to a value.
*
* This will create a new instance of the class using the injections points defined.
*
* @param ObjectDefinition $definition
*/
public function resolve(Definition $definition, array $parameters = []) : ?object
{
// Lazy?
if ($definition->isLazy()) {
return $this->createProxy($definition, $parameters);
}
return $this->createInstance($definition, $parameters);
}
/**
* The definition is not resolvable if the class is not instantiable (interface or abstract)
* or if the class doesn't exist.
*
* @param ObjectDefinition $definition
*/
public function isResolvable(Definition $definition, array $parameters = []) : bool
{
return $definition->isInstantiable();
}
/**
* Returns a proxy instance.
*/
private function createProxy(ObjectDefinition $definition, array $parameters) : object
{
/** @var class-string $className */
$className = $definition->getClassName();
return $this->proxyFactory->createProxy($className, function () use($definition, $parameters) {
return $this->createInstance($definition, $parameters);
});
}
/**
* Creates an instance of the class and injects dependencies..
*
* @param array $parameters Optional parameters to use to create the instance.
*
* @throws DependencyException
* @throws InvalidDefinition
*/
private function createInstance(ObjectDefinition $definition, array $parameters) : object
{
// Check that the class is instantiable
if (!$definition->isInstantiable()) {
// Check that the class exists
if (!$definition->classExists()) {
throw InvalidDefinition::create($definition, \sprintf('Entry "%s" cannot be resolved: the class doesn\'t exist', $definition->getName()));
}
throw InvalidDefinition::create($definition, \sprintf('Entry "%s" cannot be resolved: the class is not instantiable', $definition->getName()));
}
/** @psalm-var class-string $classname */
$classname = $definition->getClassName();
$classReflection = new ReflectionClass($classname);
$constructorInjection = $definition->getConstructorInjection();
/** @psalm-suppress InvalidCatch */
try {
$args = $this->parameterResolver->resolveParameters($constructorInjection, $classReflection->getConstructor(), $parameters);
$object = new $classname(...$args);
$this->injectMethodsAndProperties($object, $definition);
} catch (NotFoundExceptionInterface $e) {
throw new DependencyException(\sprintf('Error while injecting dependencies into %s: %s', $classReflection->getName(), $e->getMessage()), 0, $e);
} catch (InvalidDefinition $e) {
throw InvalidDefinition::create($definition, \sprintf('Entry "%s" cannot be resolved: %s', $definition->getName(), $e->getMessage()));
}
return $object;
}
protected function injectMethodsAndProperties(object $object, ObjectDefinition $objectDefinition) : void
{
// Property injections
foreach ($objectDefinition->getPropertyInjections() as $propertyInjection) {
$this->injectProperty($object, $propertyInjection);
}
// Method injections
foreach ($objectDefinition->getMethodInjections() as $methodInjection) {
$methodReflection = new \ReflectionMethod($object, $methodInjection->getMethodName());
$args = $this->parameterResolver->resolveParameters($methodInjection, $methodReflection);
$methodReflection->invokeArgs($object, $args);
}
}
/**
* Inject dependencies into properties.
*
* @param object $object Object to inject dependencies into
* @param PropertyInjection $propertyInjection Property injection definition
*
* @throws DependencyException
*/
private function injectProperty(object $object, PropertyInjection $propertyInjection) : void
{
$propertyName = $propertyInjection->getPropertyName();
$value = $propertyInjection->getValue();
if ($value instanceof Definition) {
try {
$value = $this->definitionResolver->resolve($value);
} catch (DependencyException $e) {
throw $e;
} catch (Exception $e) {
throw new DependencyException(\sprintf('Error while injecting in %s::%s. %s', $object::class, $propertyName, $e->getMessage()), 0, $e);
}
}
self::setPrivatePropertyValue($propertyInjection->getClassName(), $object, $propertyName, $value);
}
public static function setPrivatePropertyValue(?string $className, $object, string $propertyName, mixed $propertyValue) : void
{
$className = $className ?: $object::class;
$property = new ReflectionProperty($className, $propertyName);
if (!$property->isPublic() && \PHP_VERSION_ID < 80100) {
$property->setAccessible(\true);
}
$property->setValue($object, $propertyValue);
}
}

View File

@@ -1,81 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Resolver;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition\MethodInjection;
use ReflectionMethod;
use ReflectionParameter;
/**
* Resolves parameters for a function call.
*
* @since 4.2
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ParameterResolver
{
/**
* @param DefinitionResolver $definitionResolver Will be used to resolve nested definitions.
*/
public function __construct(private DefinitionResolver $definitionResolver)
{
}
/**
* @return array Parameters to use to call the function.
* @throws InvalidDefinition A parameter has no value defined or guessable.
*/
public function resolveParameters(?MethodInjection $definition = null, ?ReflectionMethod $method = null, array $parameters = []) : array
{
$args = [];
if (!$method) {
return $args;
}
$definitionParameters = $definition ? $definition->getParameters() : [];
foreach ($method->getParameters() as $index => $parameter) {
if (\array_key_exists($parameter->getName(), $parameters)) {
// Look in the $parameters array
$value =& $parameters[$parameter->getName()];
} elseif (\array_key_exists($index, $definitionParameters)) {
// Look in the definition
$value =& $definitionParameters[$index];
} else {
// If the parameter is optional and wasn't specified, we take its default value
if ($parameter->isDefaultValueAvailable() || $parameter->isOptional()) {
$args[] = $this->getParameterDefaultValue($parameter, $method);
continue;
}
throw new InvalidDefinition(\sprintf('Parameter $%s of %s has no value defined or guessable', $parameter->getName(), $this->getFunctionName($method)));
}
// Nested definitions
if ($value instanceof Definition) {
// If the container cannot produce the entry, we can use the default parameter value
if ($parameter->isOptional() && !$this->definitionResolver->isResolvable($value)) {
$value = $this->getParameterDefaultValue($parameter, $method);
} else {
$value = $this->definitionResolver->resolve($value);
}
}
$args[] =& $value;
}
return $args;
}
/**
* Returns the default value of a function parameter.
*
* @throws InvalidDefinition Can't get default values from PHP internal classes and functions
*/
private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function) : mixed
{
try {
return $parameter->getDefaultValue();
} catch (\ReflectionException) {
throw new InvalidDefinition(\sprintf('The parameter "%s" of %s has no type defined or guessable. It has a default value, ' . 'but the default value can\'t be read through Reflection because it is a PHP internal class.', $parameter->getName(), $this->getFunctionName($function)));
}
}
private function getFunctionName(ReflectionMethod $method) : string
{
return $method->getName() . '()';
}
}

View File

@@ -1,107 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Resolver;
use PleskRestApi\DI\Definition\ArrayDefinition;
use PleskRestApi\DI\Definition\DecoratorDefinition;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\EnvironmentVariableDefinition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Definition\FactoryDefinition;
use PleskRestApi\DI\Definition\InstanceDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition;
use PleskRestApi\DI\Definition\SelfResolvingDefinition;
use PleskRestApi\DI\Proxy\ProxyFactoryInterface;
use Psr\Container\ContainerInterface;
/**
* Dispatches to more specific resolvers.
*
* Dynamic dispatch pattern.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*
* @psalm-suppress MissingTemplateParam
*/
class ResolverDispatcher implements DefinitionResolver
{
private ?ArrayResolver $arrayResolver = null;
private ?FactoryResolver $factoryResolver = null;
private ?DecoratorResolver $decoratorResolver = null;
private ?ObjectCreator $objectResolver = null;
private ?InstanceInjector $instanceResolver = null;
private ?EnvironmentVariableResolver $envVariableResolver = null;
public function __construct(private ContainerInterface $container, private ProxyFactoryInterface $proxyFactory)
{
}
/**
* Resolve a definition to a value.
*
* @param Definition $definition Object that defines how the value should be obtained.
* @param array $parameters Optional parameters to use to build the entry.
*
* @return mixed Value obtained from the definition.
* @throws InvalidDefinition If the definition cannot be resolved.
*/
public function resolve(Definition $definition, array $parameters = []) : mixed
{
// Special case, tested early for speed
if ($definition instanceof SelfResolvingDefinition) {
return $definition->resolve($this->container);
}
$definitionResolver = $this->getDefinitionResolver($definition);
return $definitionResolver->resolve($definition, $parameters);
}
public function isResolvable(Definition $definition, array $parameters = []) : bool
{
// Special case, tested early for speed
if ($definition instanceof SelfResolvingDefinition) {
return $definition->isResolvable($this->container);
}
$definitionResolver = $this->getDefinitionResolver($definition);
return $definitionResolver->isResolvable($definition, $parameters);
}
/**
* Returns a resolver capable of handling the given definition.
*
* @throws \RuntimeException No definition resolver was found for this type of definition.
*/
private function getDefinitionResolver(Definition $definition) : DefinitionResolver
{
switch (\true) {
case $definition instanceof ObjectDefinition:
if (!$this->objectResolver) {
$this->objectResolver = new ObjectCreator($this, $this->proxyFactory);
}
return $this->objectResolver;
case $definition instanceof DecoratorDefinition:
if (!$this->decoratorResolver) {
$this->decoratorResolver = new DecoratorResolver($this->container, $this);
}
return $this->decoratorResolver;
case $definition instanceof FactoryDefinition:
if (!$this->factoryResolver) {
$this->factoryResolver = new FactoryResolver($this->container, $this);
}
return $this->factoryResolver;
case $definition instanceof ArrayDefinition:
if (!$this->arrayResolver) {
$this->arrayResolver = new ArrayResolver($this);
}
return $this->arrayResolver;
case $definition instanceof EnvironmentVariableDefinition:
if (!$this->envVariableResolver) {
$this->envVariableResolver = new EnvironmentVariableResolver($this);
}
return $this->envVariableResolver;
case $definition instanceof InstanceDefinition:
if (!$this->instanceResolver) {
$this->instanceResolver = new InstanceInjector($this, $this->proxyFactory);
}
return $this->instanceResolver;
default:
throw new \RuntimeException('No definition resolver was configured for definition of type ' . $definition::class);
}
}
}

View File

@@ -1,22 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
use Psr\Container\ContainerInterface;
/**
* Describes a definition that can resolve itself.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface SelfResolvingDefinition
{
/**
* Resolve the definition and return the resulting value.
*/
public function resolve(ContainerInterface $container) : mixed;
/**
* Check if a definition can be resolved.
*/
public function isResolvable(ContainerInterface $container) : bool;
}

View File

@@ -1,207 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Source;
use PleskRestApi\DI\Attribute\Inject;
use PleskRestApi\DI\Attribute\Injectable;
use PleskRestApi\DI\Definition\Exception\InvalidAttribute;
use PleskRestApi\DI\Definition\ObjectDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition\MethodInjection;
use PleskRestApi\DI\Definition\ObjectDefinition\PropertyInjection;
use PleskRestApi\DI\Definition\Reference;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionProperty;
use Throwable;
/**
* Provides DI definitions by reading PHP 8 attributes such as #[Inject] and #[Injectable].
*
* This source automatically includes the reflection source.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AttributeBasedAutowiring implements DefinitionSource, Autowiring
{
/**
* @throws InvalidAttribute
*/
public function autowire(string $name, ?ObjectDefinition $definition = null) : ?ObjectDefinition
{
$className = $definition ? $definition->getClassName() : $name;
if (!\class_exists($className) && !\interface_exists($className)) {
return $definition;
}
$definition = $definition ?: new ObjectDefinition($name);
$class = new ReflectionClass($className);
$this->readInjectableAttribute($class, $definition);
// Browse the class properties looking for annotated properties
$this->readProperties($class, $definition);
// Browse the object's methods looking for annotated methods
$this->readMethods($class, $definition);
return $definition;
}
/**
* @throws InvalidAttribute
* @throws InvalidArgumentException The class doesn't exist
*/
public function getDefinition(string $name) : ?ObjectDefinition
{
return $this->autowire($name);
}
/**
* Autowiring cannot guess all existing definitions.
*/
public function getDefinitions() : array
{
return [];
}
/**
* Browse the class properties looking for annotated properties.
*/
private function readProperties(ReflectionClass $class, ObjectDefinition $definition) : void
{
foreach ($class->getProperties() as $property) {
$this->readProperty($property, $definition);
}
// Read also the *private* properties of the parent classes
/** @noinspection PhpAssignmentInConditionInspection */
while ($class = $class->getParentClass()) {
foreach ($class->getProperties(ReflectionProperty::IS_PRIVATE) as $property) {
$this->readProperty($property, $definition, $class->getName());
}
}
}
/**
* @throws InvalidAttribute
*/
private function readProperty(ReflectionProperty $property, ObjectDefinition $definition, ?string $classname = null) : void
{
if ($property->isStatic() || $property->isPromoted()) {
return;
}
// Look for #[Inject] attribute
try {
$attribute = $property->getAttributes(Inject::class)[0] ?? null;
if (!$attribute) {
return;
}
/** @var Inject $inject */
$inject = $attribute->newInstance();
} catch (Throwable $e) {
throw new InvalidAttribute(\sprintf('#[Inject] annotation on property %s::%s is malformed. %s', $property->getDeclaringClass()->getName(), $property->getName(), $e->getMessage()), 0, $e);
}
// Try to #[Inject("name")] or look for the property type
$entryName = $inject->getName();
// Try using typed properties
$propertyType = $property->getType();
if ($entryName === null && $propertyType instanceof ReflectionNamedType) {
if (!\class_exists($propertyType->getName()) && !\interface_exists($propertyType->getName())) {
throw new InvalidAttribute(\sprintf('#[Inject] found on property %s::%s but unable to guess what to inject, the type of the property does not look like a valid class or interface name', $property->getDeclaringClass()->getName(), $property->getName()));
}
$entryName = $propertyType->getName();
}
if ($entryName === null) {
throw new InvalidAttribute(\sprintf('#[Inject] found on property %s::%s but unable to guess what to inject, please add a type to the property', $property->getDeclaringClass()->getName(), $property->getName()));
}
$definition->addPropertyInjection(new PropertyInjection($property->getName(), new Reference($entryName), $classname));
}
/**
* Browse the object's methods looking for annotated methods.
*/
private function readMethods(ReflectionClass $class, ObjectDefinition $objectDefinition) : void
{
// This will look in all the methods, including those of the parent classes
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
if ($method->isStatic()) {
continue;
}
$methodInjection = $this->getMethodInjection($method);
if (!$methodInjection) {
continue;
}
if ($method->isConstructor()) {
$objectDefinition->completeConstructorInjection($methodInjection);
} else {
$objectDefinition->completeFirstMethodInjection($methodInjection);
}
}
}
private function getMethodInjection(ReflectionMethod $method) : ?MethodInjection
{
// Look for #[Inject] attribute
$attribute = $method->getAttributes(Inject::class)[0] ?? null;
if ($attribute) {
/** @var Inject $inject */
$inject = $attribute->newInstance();
$annotationParameters = $inject->getParameters();
} elseif ($method->isConstructor()) {
// #[Inject] on constructor is implicit, we continue
$annotationParameters = [];
} else {
return null;
}
$parameters = [];
foreach ($method->getParameters() as $index => $parameter) {
$entryName = $this->getMethodParameter($index, $parameter, $annotationParameters);
if ($entryName !== null) {
$parameters[$index] = new Reference($entryName);
}
}
if ($method->isConstructor()) {
return MethodInjection::constructor($parameters);
}
return new MethodInjection($method->getName(), $parameters);
}
/**
* @return string|null Entry name or null if not found.
*/
private function getMethodParameter(int $parameterIndex, ReflectionParameter $parameter, array $annotationParameters) : ?string
{
// Let's check if this parameter has an #[Inject] attribute
$attribute = $parameter->getAttributes(Inject::class)[0] ?? null;
if ($attribute) {
/** @var Inject $inject */
$inject = $attribute->newInstance();
return $inject->getName();
}
// #[Inject] has definition for this parameter (by index, or by name)
if (isset($annotationParameters[$parameterIndex])) {
return $annotationParameters[$parameterIndex];
}
if (isset($annotationParameters[$parameter->getName()])) {
return $annotationParameters[$parameter->getName()];
}
// Skip optional parameters if not explicitly defined
if ($parameter->isOptional()) {
return null;
}
// Look for the property type
$parameterType = $parameter->getType();
if ($parameterType instanceof ReflectionNamedType && !$parameterType->isBuiltin()) {
return $parameterType->getName();
}
return null;
}
/**
* @throws InvalidAttribute
*/
private function readInjectableAttribute(ReflectionClass $class, ObjectDefinition $definition) : void
{
try {
$attribute = $class->getAttributes(Injectable::class)[0] ?? null;
if (!$attribute) {
return;
}
$attribute = $attribute->newInstance();
} catch (Throwable $e) {
throw new InvalidAttribute(\sprintf('Error while reading #[Injectable] on %s: %s', $class->getName(), $e->getMessage()), 0, $e);
}
if ($attribute->isLazy() !== null) {
$definition->setLazy($attribute->isLazy());
}
}
}

View File

@@ -1,21 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Source;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition;
/**
* Source of definitions for entries of the container.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface Autowiring
{
/**
* Autowire the given definition.
*
* @throws InvalidDefinition An invalid definition was found.
*/
public function autowire(string $name, ?ObjectDefinition $definition = null) : ?ObjectDefinition;
}

View File

@@ -1,91 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Source;
use PleskRestApi\DI\Definition\Definition;
/**
* Reads DI definitions from a PHP array.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DefinitionArray implements DefinitionSource, MutableDefinitionSource
{
public const WILDCARD = '*';
/**
* Matches anything except "\".
*/
private const WILDCARD_PATTERN = '([^\\\\]+)';
/** DI definitions in a PHP array. */
private array $definitions;
/** Cache of wildcard definitions. */
private ?array $wildcardDefinitions = null;
private DefinitionNormalizer $normalizer;
public function __construct(array $definitions = [], ?Autowiring $autowiring = null)
{
if (isset($definitions[0])) {
throw new \Exception('The PHP-DI definition is not indexed by an entry name in the definition array');
}
$this->definitions = $definitions;
$this->normalizer = new DefinitionNormalizer($autowiring ?: new NoAutowiring());
}
/**
* @param array $definitions DI definitions in a PHP array indexed by the definition name.
*/
public function addDefinitions(array $definitions) : void
{
if (isset($definitions[0])) {
throw new \Exception('The PHP-DI definition is not indexed by an entry name in the definition array');
}
// The newly added data prevails
// "for keys that exist in both arrays, the elements from the left-hand array will be used"
$this->definitions = $definitions + $this->definitions;
// Clear cache
$this->wildcardDefinitions = null;
}
public function addDefinition(Definition $definition) : void
{
$this->definitions[$definition->getName()] = $definition;
// Clear cache
$this->wildcardDefinitions = null;
}
public function getDefinition(string $name) : ?Definition
{
// Look for the definition by name
if (\array_key_exists($name, $this->definitions)) {
$definition = $this->definitions[$name];
return $this->normalizer->normalizeRootDefinition($definition, $name);
}
// Build the cache of wildcard definitions
if ($this->wildcardDefinitions === null) {
$this->wildcardDefinitions = [];
foreach ($this->definitions as $key => $definition) {
if (\str_contains($key, self::WILDCARD)) {
$this->wildcardDefinitions[$key] = $definition;
}
}
}
// Look in wildcards definitions
foreach ($this->wildcardDefinitions as $key => $definition) {
// Turn the pattern into a regex
$key = \preg_quote($key, '#');
$key = '#^' . \str_replace('\\' . self::WILDCARD, self::WILDCARD_PATTERN, $key) . '#';
if (\preg_match($key, $name, $matches) === 1) {
\array_shift($matches);
return $this->normalizer->normalizeRootDefinition($definition, $name, $matches);
}
}
return null;
}
public function getDefinitions() : array
{
// Return all definitions except wildcard definitions
$definitions = [];
foreach ($this->definitions as $key => $definition) {
if (!\str_contains($key, self::WILDCARD)) {
$definitions[$key] = $definition;
}
}
return $definitions;
}
}

View File

@@ -1,48 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Source;
use PleskRestApi\DI\Definition\Definition;
/**
* Reads DI definitions from a file returning a PHP array.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DefinitionFile extends DefinitionArray
{
private bool $initialized = \false;
/**
* @param string $file File in which the definitions are returned as an array.
*/
public function __construct(private string $file, ?Autowiring $autowiring = null)
{
// Lazy-loading to improve performances
parent::__construct([], $autowiring);
}
public function getDefinition(string $name) : ?Definition
{
$this->initialize();
return parent::getDefinition($name);
}
public function getDefinitions() : array
{
$this->initialize();
return parent::getDefinitions();
}
/**
* Lazy-loading of the definitions.
*/
private function initialize() : void
{
if ($this->initialized === \true) {
return;
}
$definitions = (require $this->file);
if (!\is_array($definitions)) {
throw new \Exception("File {$this->file} should return an array of definitions");
}
$this->addDefinitions($definitions);
$this->initialized = \true;
}
}

View File

@@ -1,92 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Source;
use PleskRestApi\DI\Definition\ArrayDefinition;
use PleskRestApi\DI\Definition\AutowireDefinition;
use PleskRestApi\DI\Definition\DecoratorDefinition;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Definition\FactoryDefinition;
use PleskRestApi\DI\Definition\Helper\DefinitionHelper;
use PleskRestApi\DI\Definition\ObjectDefinition;
use PleskRestApi\DI\Definition\ValueDefinition;
/**
* Turns raw definitions/definition helpers into definitions ready
* to be resolved or compiled.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DefinitionNormalizer
{
public function __construct(private Autowiring $autowiring)
{
}
/**
* Normalize a definition that is *not* nested in another one.
*
* This is usually a definition declared at the root of a definition array.
*
* @param string $name The definition name.
* @param string[] $wildcardsReplacements Replacements for wildcard definitions.
*
* @throws InvalidDefinition
*/
public function normalizeRootDefinition(mixed $definition, string $name, ?array $wildcardsReplacements = null) : Definition
{
if ($definition instanceof DefinitionHelper) {
$definition = $definition->getDefinition($name);
} elseif (\is_array($definition)) {
$definition = new ArrayDefinition($definition);
} elseif ($definition instanceof \Closure) {
$definition = new FactoryDefinition($name, $definition);
} elseif (!$definition instanceof Definition) {
$definition = new ValueDefinition($definition);
}
// For a class definition, we replace * in the class name with the matches
// *Interface -> *Impl => FooInterface -> FooImpl
if ($wildcardsReplacements && $definition instanceof ObjectDefinition) {
$definition->replaceWildcards($wildcardsReplacements);
}
if ($definition instanceof AutowireDefinition) {
/** @var AutowireDefinition $definition */
$definition = $this->autowiring->autowire($name, $definition);
}
$definition->setName($name);
try {
$definition->replaceNestedDefinitions([$this, 'normalizeNestedDefinition']);
} catch (InvalidDefinition $e) {
throw InvalidDefinition::create($definition, \sprintf('Definition "%s" contains an error: %s', $definition->getName(), $e->getMessage()), $e);
}
return $definition;
}
/**
* Normalize a definition that is nested in another one.
*
* @throws InvalidDefinition
*/
public function normalizeNestedDefinition(mixed $definition) : mixed
{
$name = '<nested definition>';
if ($definition instanceof DefinitionHelper) {
$definition = $definition->getDefinition($name);
} elseif (\is_array($definition)) {
$definition = new ArrayDefinition($definition);
} elseif ($definition instanceof \Closure) {
$definition = new FactoryDefinition($name, $definition);
}
if ($definition instanceof DecoratorDefinition) {
throw new InvalidDefinition('Decorators cannot be nested in another definition');
}
if ($definition instanceof AutowireDefinition) {
$definition = $this->autowiring->autowire($name, $definition);
}
if ($definition instanceof Definition) {
$definition->setName($name);
// Recursively traverse nested definitions
$definition->replaceNestedDefinitions([$this, 'normalizeNestedDefinition']);
}
return $definition;
}
}

View File

@@ -1,25 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Source;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
/**
* Source of definitions for entries of the container.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface DefinitionSource
{
/**
* Returns the DI definition for the entry name.
*
* @throws InvalidDefinition An invalid definition was found.
*/
public function getDefinition(string $name) : ?Definition;
/**
* @return array<string,Definition> Definitions indexed by their name.
*/
public function getDefinitions() : array;
}

View File

@@ -1,15 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Source;
use PleskRestApi\DI\Definition\Definition;
/**
* Describes a definition source to which we can add new definitions.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface MutableDefinitionSource extends DefinitionSource
{
public function addDefinition(Definition $definition) : void;
}

View File

@@ -1,19 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Source;
use PleskRestApi\DI\Definition\Exception\InvalidDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition;
/**
* Implementation used when autowiring is completely disabled.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class NoAutowiring implements Autowiring
{
public function autowire(string $name, ?ObjectDefinition $definition = null) : ?ObjectDefinition
{
throw new InvalidDefinition(\sprintf('Cannot autowire entry "%s" because autowiring is disabled', $name));
}
}

View File

@@ -1,72 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Source;
use PleskRestApi\DI\Definition\ObjectDefinition;
use PleskRestApi\DI\Definition\ObjectDefinition\MethodInjection;
use PleskRestApi\DI\Definition\Reference;
use ReflectionNamedType;
/**
* Reads DI class definitions using reflection.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ReflectionBasedAutowiring implements DefinitionSource, Autowiring
{
public function autowire(string $name, ?ObjectDefinition $definition = null) : ?ObjectDefinition
{
$className = $definition ? $definition->getClassName() : $name;
if (!\class_exists($className) && !\interface_exists($className)) {
return $definition;
}
$definition = $definition ?: new ObjectDefinition($name);
// Constructor
$class = new \ReflectionClass($className);
$constructor = $class->getConstructor();
if ($constructor && $constructor->isPublic()) {
$constructorInjection = MethodInjection::constructor($this->getParametersDefinition($constructor));
$definition->completeConstructorInjection($constructorInjection);
}
return $definition;
}
public function getDefinition(string $name) : ?ObjectDefinition
{
return $this->autowire($name);
}
/**
* Autowiring cannot guess all existing definitions.
*/
public function getDefinitions() : array
{
return [];
}
/**
* Read the type-hinting from the parameters of the function.
*/
private function getParametersDefinition(\ReflectionFunctionAbstract $constructor) : array
{
$parameters = [];
foreach ($constructor->getParameters() as $index => $parameter) {
// Skip optional parameters
if ($parameter->isOptional()) {
continue;
}
$parameterType = $parameter->getType();
if (!$parameterType) {
// No type
continue;
}
if (!$parameterType instanceof ReflectionNamedType) {
// Union types are not supported
continue;
}
if ($parameterType->isBuiltin()) {
// Primitive types are not supported
continue;
}
$parameters[$index] = new Reference($parameterType->getName());
}
return $parameters;
}
}

View File

@@ -1,55 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Source;
use PleskRestApi\DI\Definition\AutowireDefinition;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\ObjectDefinition;
/**
* Decorator that caches another definition source.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class SourceCache implements DefinitionSource, MutableDefinitionSource
{
public const CACHE_KEY = 'php-di.definitions.';
public function __construct(private DefinitionSource $cachedSource, private string $cacheNamespace = '')
{
}
public function getDefinition(string $name) : ?Definition
{
$definition = \apcu_fetch($this->getCacheKey($name));
if ($definition === \false) {
$definition = $this->cachedSource->getDefinition($name);
// Update the cache
if ($this->shouldBeCached($definition)) {
\apcu_store($this->getCacheKey($name), $definition);
}
}
return $definition;
}
/**
* Used only for the compilation so we can skip the cache safely.
*/
public function getDefinitions() : array
{
return $this->cachedSource->getDefinitions();
}
public static function isSupported() : bool
{
return \function_exists('apcu_fetch') && \ini_get('apc.enabled') && !('cli' === \PHP_SAPI && !\ini_get('apc.enable_cli'));
}
public function getCacheKey(string $name) : string
{
return self::CACHE_KEY . $this->cacheNamespace . $name;
}
public function addDefinition(Definition $definition) : void
{
throw new \LogicException('You cannot set a definition at runtime on a container that has caching enabled. Doing so would risk caching the definition for the next execution, where it might be different. You can either put your definitions in a file, remove the cache or ->set() a raw value directly (PHP object, string, int, ...) instead of a PHP-DI definition.');
}
private function shouldBeCached(?Definition $definition = null) : bool
{
return $definition === null || $definition instanceof ObjectDefinition || $definition instanceof AutowireDefinition;
}
}

View File

@@ -1,71 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition\Source;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\ExtendsPreviousDefinition;
/**
* Manages a chain of other definition sources.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class SourceChain implements DefinitionSource, MutableDefinitionSource
{
private ?MutableDefinitionSource $mutableSource;
/**
* @param list<DefinitionSource> $sources
*/
public function __construct(private array $sources)
{
}
/**
* @param int $startIndex Use this parameter to start looking from a specific
* point in the source chain.
*/
public function getDefinition(string $name, int $startIndex = 0) : ?Definition
{
$count = \count($this->sources);
for ($i = $startIndex; $i < $count; ++$i) {
$source = $this->sources[$i];
$definition = $source->getDefinition($name);
if ($definition) {
if ($definition instanceof ExtendsPreviousDefinition) {
$this->resolveExtendedDefinition($definition, $i);
}
return $definition;
}
}
return null;
}
public function getDefinitions() : array
{
$allDefinitions = \array_merge(...\array_map(fn($source) => $source->getDefinitions(), $this->sources));
/** @var string[] $allNames */
$allNames = \array_keys($allDefinitions);
$allValues = \array_filter(\array_map(fn($name) => $this->getDefinition($name), $allNames));
return \array_combine($allNames, $allValues);
}
public function addDefinition(Definition $definition) : void
{
if (!$this->mutableSource) {
throw new \LogicException("The container's definition source has not been initialized correctly");
}
$this->mutableSource->addDefinition($definition);
}
private function resolveExtendedDefinition(ExtendsPreviousDefinition $definition, int $currentIndex)
{
// Look in the next sources only (else infinite recursion, and we can only extend
// entries defined in the previous definition files - a previous == next here because
// the array was reversed ;) )
$subDefinition = $this->getDefinition($definition->getName(), $currentIndex + 1);
if ($subDefinition) {
$definition->setExtendedDefinition($subDefinition);
}
}
public function setMutableDefinitionSource(MutableDefinitionSource $mutableSource) : void
{
$this->mutableSource = $mutableSource;
\array_unshift($this->sources, $mutableSource);
}
}

View File

@@ -1,69 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
use PleskRestApi\DI\DependencyException;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* Definition of a string composed of other strings.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class StringDefinition implements Definition, SelfResolvingDefinition
{
/** Entry name. */
private string $name = '';
public function __construct(private string $expression)
{
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : void
{
$this->name = $name;
}
public function getExpression() : string
{
return $this->expression;
}
public function resolve(ContainerInterface $container) : string
{
return self::resolveExpression($this->name, $this->expression, $container);
}
public function isResolvable(ContainerInterface $container) : bool
{
return \true;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
// no nested definitions
}
public function __toString() : string
{
return $this->expression;
}
/**
* Resolve a string expression.
*/
public static function resolveExpression(string $entryName, string $expression, ContainerInterface $container) : string
{
$callback = function (array $matches) use($entryName, $container) {
/** @psalm-suppress InvalidCatch */
try {
return $container->get($matches[1]);
} catch (NotFoundExceptionInterface $e) {
throw new DependencyException(\sprintf("Error while parsing string expression for entry '%s': %s", $entryName, $e->getMessage()), 0, $e);
}
};
$result = \preg_replace_callback('#\\{([^{}]+)}#', $callback, $expression);
if ($result === null) {
throw new \RuntimeException(\sprintf('An unknown error occurred while parsing the string definition: \'%s\'', $expression));
}
return $result;
}
}

View File

@@ -1,49 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Definition;
use Psr\Container\ContainerInterface;
/**
* Definition of a value for dependency injection.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ValueDefinition implements Definition, SelfResolvingDefinition
{
/**
* Entry name.
*/
private string $name = '';
public function __construct(private mixed $value)
{
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : void
{
$this->name = $name;
}
public function getValue() : mixed
{
return $this->value;
}
public function resolve(ContainerInterface $container) : mixed
{
return $this->getValue();
}
public function isResolvable(ContainerInterface $container) : bool
{
return \true;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
// no nested definitions
}
public function __toString() : string
{
return \sprintf('Value (%s)', \var_export($this->value, \true));
}
}

View File

@@ -1,12 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI;
use Psr\Container\ContainerExceptionInterface;
/**
* Exception for the Container.
*/
class DependencyException extends \Exception implements ContainerExceptionInterface
{
}

View File

@@ -1,22 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Factory;
/**
* Represents the container entry that was requested.
*
* Implementations of this interface can be injected in factory parameters in order
* to know what was the name of the requested entry.
*
* @api
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface RequestedEntry
{
/**
* Returns the name of the entry that was requested by the container.
*/
public function getName() : string;
}

View File

@@ -1,29 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI;
/**
* Describes the basic interface of a factory.
*
* @api
*
* @since 4.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface FactoryInterface
{
/**
* Resolves an entry by its name. If given a class name, it will return a new instance of that class.
*
* @param string $name Entry name or a class name.
* @param array $parameters Optional parameters to use to build the entry. Use this to force specific
* parameters to specific values. Parameters not defined in this array will
* be automatically resolved.
*
* @throws \InvalidArgumentException The name parameter must be of type string.
* @throws DependencyException Error while resolving the entry.
* @throws NotFoundException No entry or class found for the given name.
*/
public function make(string $name, array $parameters = []) : mixed;
}

View File

@@ -1,52 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Invoker;
use PleskRestApi\DI\Definition\Definition;
use PleskRestApi\DI\Definition\Helper\DefinitionHelper;
use PleskRestApi\DI\Definition\Resolver\DefinitionResolver;
use PleskRestApi\Invoker\ParameterResolver\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Resolves callable parameters using definitions.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DefinitionParameterResolver implements ParameterResolver
{
public function __construct(private DefinitionResolver $definitionResolver)
{
}
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$providedParameters = \array_diff_key($providedParameters, $resolvedParameters);
}
foreach ($providedParameters as $key => $value) {
if ($value instanceof DefinitionHelper) {
$value = $value->getDefinition('');
}
if (!$value instanceof Definition) {
continue;
}
$value = $this->definitionResolver->resolve($value);
if (\is_int($key)) {
// Indexed by position
$resolvedParameters[$key] = $value;
} else {
// Indexed by parameter name
// TODO optimize?
$reflectionParameters = $reflection->getParameters();
foreach ($reflectionParameters as $reflectionParameter) {
if ($key === $reflectionParameter->name) {
$resolvedParameters[$reflectionParameter->getPosition()] = $value;
}
}
}
}
return $resolvedParameters;
}
}

View File

@@ -1,57 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Invoker;
use PleskRestApi\Invoker\ParameterResolver\ParameterResolver;
use Psr\Container\ContainerInterface;
use ReflectionFunctionAbstract;
use ReflectionNamedType;
/**
* Inject the container, the definition or any other service using type-hints.
*
* {@internal This class is similar to TypeHintingResolver and TypeHintingContainerResolver,
* we use this instead for performance reasons}
*
* @author Quim Calpe <quim@kalpe.com>
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class FactoryParameterResolver implements ParameterResolver
{
public function __construct(private ContainerInterface $container)
{
}
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$parameters = \array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
$parameterType = $parameter->getType();
if (!$parameterType) {
// No type
continue;
}
if (!$parameterType instanceof ReflectionNamedType) {
// Union types are not supported
continue;
}
if ($parameterType->isBuiltin()) {
// Primitive types are not supported
continue;
}
$parameterClass = $parameterType->getName();
if ($parameterClass === 'Psr\\Container\\ContainerInterface') {
$resolvedParameters[$index] = $this->container;
} elseif ($parameterClass === 'DI\\Factory\\RequestedEntry') {
// By convention the second parameter is the definition
$resolvedParameters[$index] = $providedParameters[1];
} elseif ($this->container->has($parameterClass)) {
$resolvedParameters[$index] = $this->container->get($parameterClass);
}
}
return $resolvedParameters;
}
}

View File

@@ -1,12 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI;
use Psr\Container\NotFoundExceptionInterface;
/**
* Exception thrown when a class or a value is not found in the container.
*/
class NotFoundException extends \Exception implements NotFoundExceptionInterface
{
}

View File

@@ -1,33 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Proxy;
use LogicException;
/**
* Uses PHP 8.4+'s native support for lazy proxies to generate proxy objects.
*
* @since 7.1
* @author Buster Neece <buster@busterneece.com>
*/
class NativeProxyFactory implements ProxyFactoryInterface
{
/**
* Creates a new lazy proxy instance of the given class with
* the given initializer.
*
* {@inheritDoc}
*/
public function createProxy(string $className, \Closure $createFunction) : object
{
if (\PHP_VERSION_ID < 80400) {
throw new LogicException('Lazy loading proxies require PHP 8.4 or higher.');
}
$reflector = new \ReflectionClass($className);
return $reflector->newLazyProxy($createFunction);
}
public function generateProxyClass(string $className) : void
{
// Noop for this type.
}
}

View File

@@ -1,78 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Proxy;
use PleskRestApi\ProxyManager\Configuration;
use PleskRestApi\ProxyManager\Factory\LazyLoadingValueHolderFactory;
use PleskRestApi\ProxyManager\FileLocator\FileLocator;
use PleskRestApi\ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
use PleskRestApi\ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy;
/**
* Creates proxy classes.
*
* Wraps Ocramius/ProxyManager LazyLoadingValueHolderFactory.
*
* @see LazyLoadingValueHolderFactory
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ProxyFactory implements ProxyFactoryInterface
{
private ?LazyLoadingValueHolderFactory $proxyManager = null;
/**
* @param string|null $proxyDirectory If set, write the proxies to disk in this directory to improve performances.
*/
public function __construct(private ?string $proxyDirectory = null)
{
}
/**
* Creates a new lazy proxy instance of the given class with
* the given initializer.
*
* {@inheritDoc}
*/
public function createProxy(string $className, \Closure $createFunction) : object
{
return $this->proxyManager()->createProxy($className, function (&$wrappedObject, $proxy, $method, $params, &$initializer) use($createFunction) {
$wrappedObject = $createFunction();
$initializer = null;
// turning off further lazy initialization
return \true;
});
}
/**
* Generates and writes the proxy class to file.
*
* @param class-string $className name of the class to be proxied
*/
public function generateProxyClass(string $className) : void
{
// If proxy classes a written to file then we pre-generate the class
// If they are not written to file then there is no point to do this
if ($this->proxyDirectory) {
$this->createProxy($className, function () {
});
}
}
private function proxyManager() : LazyLoadingValueHolderFactory
{
if ($this->proxyManager === null) {
if (!\class_exists(Configuration::class)) {
throw new \RuntimeException('The ocramius/proxy-manager library is not installed. Lazy injection requires that library to be installed with Composer in order to work. Run "composer require ocramius/proxy-manager:~2.0".');
}
$config = new Configuration();
if ($this->proxyDirectory) {
$config->setProxiesTargetDir($this->proxyDirectory);
$config->setGeneratorStrategy(new FileWriterGeneratorStrategy(new FileLocator($this->proxyDirectory)));
// @phpstan-ignore-next-line
\spl_autoload_register($config->getProxyAutoloader());
} else {
$config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
}
$this->proxyManager = new LazyLoadingValueHolderFactory($config);
}
return $this->proxyManager;
}
}

View File

@@ -1,30 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI\Proxy;
/**
* Generic interface for proxy factories.
*
* @since 7.1
* @author Buster Neece <buster@busterneece.com>
*/
interface ProxyFactoryInterface
{
/**
* Creates a new lazy proxy instance of the given class with
* the given initializer.
*
* @param class-string $className name of the class to be proxied
* @param \Closure $createFunction initializer to be passed to the proxy initializer to be passed to the proxy
*/
public function createProxy(string $className, \Closure $createFunction) : object;
/**
* If the proxy generator depends on a filesystem component,
* this step writes the proxy for that class to file. Otherwise,
* it is a no-op.
*
* @param class-string $className name of the class to be proxied
*/
public function generateProxyClass(string $className) : void;
}

View File

@@ -1,142 +0,0 @@
<?php
declare (strict_types=1);
namespace PleskRestApi\DI;
use PleskRestApi\DI\Definition\ArrayDefinitionExtension;
use PleskRestApi\DI\Definition\EnvironmentVariableDefinition;
use PleskRestApi\DI\Definition\Helper\AutowireDefinitionHelper;
use PleskRestApi\DI\Definition\Helper\CreateDefinitionHelper;
use PleskRestApi\DI\Definition\Helper\FactoryDefinitionHelper;
use PleskRestApi\DI\Definition\Reference;
use PleskRestApi\DI\Definition\StringDefinition;
use PleskRestApi\DI\Definition\ValueDefinition;
if (!\function_exists('PleskRestApi\\DI\\value')) {
/**
* Helper for defining a value.
*/
function value(mixed $value) : ValueDefinition
{
return new ValueDefinition($value);
}
}
if (!\function_exists('PleskRestApi\\DI\\create')) {
/**
* Helper for defining an object.
*
* @param string|null $className Class name of the object.
* If null, the name of the entry (in the container) will be used as class name.
*/
function create(?string $className = null) : CreateDefinitionHelper
{
return new CreateDefinitionHelper($className);
}
}
if (!\function_exists('PleskRestApi\\DI\\autowire')) {
/**
* Helper for autowiring an object.
*
* @param string|null $className Class name of the object.
* If null, the name of the entry (in the container) will be used as class name.
*/
function autowire(?string $className = null) : AutowireDefinitionHelper
{
return new AutowireDefinitionHelper($className);
}
}
if (!\function_exists('PleskRestApi\\DI\\factory')) {
/**
* Helper for defining a container entry using a factory function/callable.
*
* @param callable|array|string $factory The factory is a callable that takes the container as parameter
* and returns the value to register in the container.
*/
function factory(callable|array|string $factory) : FactoryDefinitionHelper
{
return new FactoryDefinitionHelper($factory);
}
}
if (!\function_exists('PleskRestApi\\DI\\decorate')) {
/**
* Decorate the previous definition using a callable.
*
* Example:
*
* 'foo' => decorate(function ($foo, $container) {
* return new CachedFoo($foo, $container->get('cache'));
* })
*
* @param callable $callable The callable takes the decorated object as first parameter and
* the container as second.
*/
function decorate(callable|array|string $callable) : FactoryDefinitionHelper
{
return new FactoryDefinitionHelper($callable, \true);
}
}
if (!\function_exists('PleskRestApi\\DI\\get')) {
/**
* Helper for referencing another container entry in an object definition.
*/
function get(string $entryName) : Reference
{
return new Reference($entryName);
}
}
if (!\function_exists('PleskRestApi\\DI\\env')) {
/**
* Helper for referencing environment variables.
*
* @param string $variableName The name of the environment variable.
* @param mixed $defaultValue The default value to be used if the environment variable is not defined.
*/
function env(string $variableName, mixed $defaultValue = null) : EnvironmentVariableDefinition
{
// Only mark as optional if the default value was *explicitly* provided.
$isOptional = 2 === \func_num_args();
return new EnvironmentVariableDefinition($variableName, $isOptional, $defaultValue);
}
}
if (!\function_exists('PleskRestApi\\DI\\add')) {
/**
* Helper for extending another definition.
*
* Example:
*
* 'log.backends' => DI\add(DI\get('My\Custom\LogBackend'))
*
* or:
*
* 'log.backends' => DI\add([
* DI\get('My\Custom\LogBackend')
* ])
*
* @param mixed|array $values A value or an array of values to add to the array.
*
* @since 5.0
*/
function add($values) : ArrayDefinitionExtension
{
if (!\is_array($values)) {
$values = [$values];
}
return new ArrayDefinitionExtension($values);
}
}
if (!\function_exists('PleskRestApi\\DI\\string')) {
/**
* Helper for concatenating strings.
*
* Example:
*
* 'log.filename' => DI\string('{app.path}/app.log')
*
* @param string $expression A string expression. Use the `{}` placeholders to reference other container entries.
*
* @since 5.0
*/
function string(string $expression) : StringDefinition
{
return new StringDefinition($expression);
}
}