first commit
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Matthieu Napoli
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "php-di\/invoker",
|
||||
"description": "Generic and extensible callable invoker",
|
||||
"keywords": [
|
||||
"invoker",
|
||||
"dependency-injection",
|
||||
"dependency",
|
||||
"injection",
|
||||
"callable",
|
||||
"invoke"
|
||||
],
|
||||
"homepage": "https:\/\/github.com\/PHP-DI\/Invoker",
|
||||
"license": "MIT",
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ElementorProDeps\\Invoker\\": "src\/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"ElementorProDeps\\Invoker\\Test\\": "tests\/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.3",
|
||||
"psr\/container": "^1.0|^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit\/phpunit": "^9.0",
|
||||
"athletic\/athletic": "~0.1.8",
|
||||
"mnapoli\/hard-mode": "~0.3.0"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect\/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\Invoker;
|
||||
|
||||
use Closure;
|
||||
use ElementorProDeps\Invoker\Exception\NotCallableException;
|
||||
use ElementorProDeps\Psr\Container\ContainerInterface;
|
||||
use ElementorProDeps\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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\Invoker\Exception;
|
||||
|
||||
/**
|
||||
* Impossible to invoke the callable.
|
||||
*/
|
||||
class InvocationException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\Invoker\Exception;
|
||||
|
||||
/**
|
||||
* Not enough parameters could be resolved to invoke the callable.
|
||||
*/
|
||||
class NotEnoughParametersException extends InvocationException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\Invoker;
|
||||
|
||||
use ElementorProDeps\Invoker\Exception\NotCallableException;
|
||||
use ElementorProDeps\Invoker\Exception\NotEnoughParametersException;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\AssociativeArrayResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\DefaultValueResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\NumericArrayResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\ParameterResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\ResolverChain;
|
||||
use ElementorProDeps\Invoker\Reflection\CallableReflection;
|
||||
use ElementorProDeps\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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\Invoker;
|
||||
|
||||
use ElementorProDeps\Invoker\Exception\InvocationException;
|
||||
use ElementorProDeps\Invoker\Exception\NotCallableException;
|
||||
use ElementorProDeps\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 = []);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\Invoker\ParameterResolver\Container;
|
||||
|
||||
use ElementorProDeps\Invoker\ParameterResolver\ParameterResolver;
|
||||
use ElementorProDeps\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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\Invoker\ParameterResolver\Container;
|
||||
|
||||
use ElementorProDeps\Invoker\ParameterResolver\ParameterResolver;
|
||||
use ElementorProDeps\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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\Invoker\Reflection;
|
||||
|
||||
use Closure;
|
||||
use ElementorProDeps\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)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Matthieu Napoli
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "php-di\/php-di",
|
||||
"type": "library",
|
||||
"description": "The dependency injection container for humans",
|
||||
"keywords": [
|
||||
"di",
|
||||
"dependency injection",
|
||||
"container",
|
||||
"ioc",
|
||||
"psr-11",
|
||||
"psr11",
|
||||
"container-interop"
|
||||
],
|
||||
"homepage": "https:\/\/php-di.org\/",
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ElementorProDeps\\DI\\": "src\/"
|
||||
},
|
||||
"files": [
|
||||
"src\/functions.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"ElementorProDeps\\DI\\Test\\IntegrationTest\\": "tests\/IntegrationTest\/",
|
||||
"ElementorProDeps\\DI\\Test\\UnitTest\\": "tests\/UnitTest\/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit",
|
||||
"format-code": "php-cs-fixer fix --allow-risky=yes",
|
||||
"phpstan": "phpstan analyse -l 5 -c phpstan.neon src"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4.0",
|
||||
"psr\/container": "^1.0",
|
||||
"php-di\/invoker": "^2.0",
|
||||
"php-di\/phpdoc-reader": "^2.0.1",
|
||||
"laravel\/serializable-closure": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit\/phpunit": "^9.5",
|
||||
"mnapoli\/phpunit-easymock": "^1.2",
|
||||
"doctrine\/annotations": "~1.10",
|
||||
"ocramius\/proxy-manager": "^2.11.2",
|
||||
"friendsofphp\/php-cs-fixer": "^2.4",
|
||||
"phpstan\/phpstan": "^0.12"
|
||||
},
|
||||
"provide": {
|
||||
"psr\/container-implementation": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"doctrine\/annotations": "Install it if you want to use annotations (version ~1.2)",
|
||||
"ocramius\/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Annotation;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidAnnotation;
|
||||
/**
|
||||
* "Inject" annotation.
|
||||
*
|
||||
* Marks a property or method as an injection point
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @Annotation
|
||||
* @Target({"METHOD","PROPERTY"})
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
final class Inject
|
||||
{
|
||||
/**
|
||||
* Entry name.
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* Parameters, indexed by the parameter number (index) or name.
|
||||
*
|
||||
* Used if the annotation is set on a method
|
||||
* @var array
|
||||
*/
|
||||
private $parameters = [];
|
||||
/**
|
||||
* @throws InvalidAnnotation
|
||||
*/
|
||||
public function __construct(array $values)
|
||||
{
|
||||
// Process the parameters as a list AND as a parameter array (we don't know on what the annotation is)
|
||||
// @Inject(name="foo")
|
||||
if (isset($values['name']) && \is_string($values['name'])) {
|
||||
$this->name = $values['name'];
|
||||
return;
|
||||
}
|
||||
// @Inject
|
||||
if (!isset($values['value'])) {
|
||||
return;
|
||||
}
|
||||
$values = $values['value'];
|
||||
// @Inject("foo")
|
||||
if (\is_string($values)) {
|
||||
$this->name = $values;
|
||||
}
|
||||
// @Inject({...}) on a method
|
||||
if (\is_array($values)) {
|
||||
foreach ($values as $key => $value) {
|
||||
if (!\is_string($value)) {
|
||||
throw new InvalidAnnotation(\sprintf('@Inject({"param" = "value"}) expects "value" to be a string, %s given.', \json_encode($value)));
|
||||
}
|
||||
$this->parameters[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return string|null Name of the entry to inject
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
/**
|
||||
* @return array Parameters, indexed by the parameter number (index) or name
|
||||
*/
|
||||
public function getParameters() : array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Annotation;
|
||||
|
||||
/**
|
||||
* "Injectable" annotation.
|
||||
*
|
||||
* Marks a class as injectable
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*
|
||||
* @author Domenic Muskulus <domenic@muskulus.eu>
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
final class Injectable
|
||||
{
|
||||
/**
|
||||
* Should the object be lazy-loaded.
|
||||
* @var bool|null
|
||||
*/
|
||||
private $lazy;
|
||||
public function __construct(array $values)
|
||||
{
|
||||
if (isset($values['lazy'])) {
|
||||
$this->lazy = (bool) $values['lazy'];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return bool|null
|
||||
*/
|
||||
public function isLazy()
|
||||
{
|
||||
return $this->lazy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI;
|
||||
|
||||
use ElementorProDeps\DI\Compiler\RequestedEntryHolder;
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\DI\Invoker\FactoryParameterResolver;
|
||||
use ElementorProDeps\Invoker\Exception\NotCallableException;
|
||||
use ElementorProDeps\Invoker\Exception\NotEnoughParametersException;
|
||||
use ElementorProDeps\Invoker\Invoker;
|
||||
use ElementorProDeps\Invoker\InvokerInterface;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\AssociativeArrayResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\DefaultValueResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\NumericArrayResolver;
|
||||
use ElementorProDeps\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 = [];
|
||||
/**
|
||||
* @var InvokerInterface
|
||||
*/
|
||||
private $factoryInvoker;
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
// Try to find the entry in the singleton map
|
||||
if (isset($this->resolvedEntries[$name]) || \array_key_exists($name, $this->resolvedEntries)) {
|
||||
return $this->resolvedEntries[$name];
|
||||
}
|
||||
$method = static::METHOD_MAPPING[$name] ?? 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[$name])) {
|
||||
throw new DependencyException("Circular dependency detected while trying to resolve entry '{$name}'");
|
||||
}
|
||||
$this->entriesBeingResolved[$name] = \true;
|
||||
try {
|
||||
$value = $this->{$method}();
|
||||
} finally {
|
||||
unset($this->entriesBeingResolved[$name]);
|
||||
}
|
||||
// Store the entry to always return it without recomputing it
|
||||
$this->resolvedEntries[$name] = $value;
|
||||
return $value;
|
||||
}
|
||||
return parent::get($name);
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
if (!\is_string($name)) {
|
||||
throw new \InvalidArgumentException(\sprintf('The name parameter must be of type string, %s given', \is_object($name) ? \get_class($name) : \gettype($name)));
|
||||
}
|
||||
// The parent method is overridden to check in our array, it avoids resolving definitions
|
||||
if (isset(static::METHOD_MAPPING[$name])) {
|
||||
return \true;
|
||||
}
|
||||
return parent::has($name);
|
||||
}
|
||||
protected function setDefinition(string $name, Definition $definition)
|
||||
{
|
||||
// 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 = [])
|
||||
{
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Compiler;
|
||||
|
||||
use function chmod;
|
||||
use ElementorProDeps\DI\Definition\ArrayDefinition;
|
||||
use ElementorProDeps\DI\Definition\DecoratorDefinition;
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\EnvironmentVariableDefinition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\DI\Definition\FactoryDefinition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
use ElementorProDeps\DI\Definition\Reference;
|
||||
use ElementorProDeps\DI\Definition\Source\DefinitionSource;
|
||||
use ElementorProDeps\DI\Definition\StringDefinition;
|
||||
use ElementorProDeps\DI\Definition\ValueDefinition;
|
||||
use ElementorProDeps\DI\DependencyException;
|
||||
use ElementorProDeps\DI\Proxy\ProxyFactory;
|
||||
use function dirname;
|
||||
use function file_put_contents;
|
||||
use InvalidArgumentException;
|
||||
use ElementorProDeps\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
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $containerClass;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $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.
|
||||
*
|
||||
* @var \ArrayIterator
|
||||
*/
|
||||
private $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.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $subEntryCounter;
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $methodMappingCounter;
|
||||
/**
|
||||
* Map of entry names to method names.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $entryToMethodMapping = [];
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $methods = [];
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $autowiringEnabled;
|
||||
/**
|
||||
* @var ProxyFactory
|
||||
*/
|
||||
private $proxyFactory;
|
||||
public function __construct(ProxyFactory $proxyFactory)
|
||||
{
|
||||
$this->proxyFactory = $proxyFactory;
|
||||
}
|
||||
public function getProxyFactory() : ProxyFactory
|
||||
{
|
||||
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) : int
|
||||
{
|
||||
$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 $written;
|
||||
}
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws InvalidDefinition
|
||||
* @return string The method name
|
||||
*/
|
||||
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 ' . \get_class($definition));
|
||||
}
|
||||
$this->methods[$methodName] = $code;
|
||||
return $methodName;
|
||||
}
|
||||
public function compileValue($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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if ($value instanceof ValueDefinition) {
|
||||
return $this->isCompilable($value->getValue());
|
||||
}
|
||||
if ($value instanceof DecoratorDefinition) {
|
||||
if (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;
|
||||
}
|
||||
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 \DI\Definition\Exception\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();
|
||||
$code = \trim($code, "\t\n\r;");
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Compiler;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
use ElementorProDeps\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
|
||||
{
|
||||
/**
|
||||
* @var Compiler
|
||||
*/
|
||||
private $compiler;
|
||||
public function __construct(Compiler $compiler)
|
||||
{
|
||||
$this->compiler = $compiler;
|
||||
}
|
||||
public function compile(ObjectDefinition $definition) : string
|
||||
{
|
||||
$this->assertClassIsNotAnonymous($definition);
|
||||
$this->assertClassIsInstantiable($definition);
|
||||
// Lazy?
|
||||
if ($definition->isLazy()) {
|
||||
return $this->compileLazyDefinition($definition);
|
||||
}
|
||||
try {
|
||||
$classReflection = new ReflectionClass($definition->getClassName());
|
||||
$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);', $definition->getClassName(), \implode(', ', $dumpedConstructorArguments));
|
||||
// Property injections
|
||||
foreach ($definition->getPropertyInjections() as $propertyInjection) {
|
||||
$value = $propertyInjection->getValue();
|
||||
$value = $this->compiler->compileValue($value);
|
||||
$className = $propertyInjection->getClassName() ?: $definition->getClassName();
|
||||
$property = new ReflectionProperty($className, $propertyInjection->getPropertyName());
|
||||
if ($property->isPublic()) {
|
||||
$code[] = \sprintf('$object->%s = %s;', $propertyInjection->getPropertyName(), $value);
|
||||
} else {
|
||||
// Private/protected 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($definition->getClassName(), $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 = null, ReflectionMethod $method = null) : 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);
|
||||
$this->compiler->getProxyFactory()->generateProxyClass($definition->getClassName());
|
||||
return <<<PHP
|
||||
\$object = \$this->proxyFactory->createProxy(
|
||||
'{$definition->getClassName()}',
|
||||
function (&\$wrappedObject, \$proxy, \$method, \$params, &\$initializer) {
|
||||
\$wrappedObject = {$subDefinition};
|
||||
\$initializer = null; // turning off further lazy initialization
|
||||
return true;
|
||||
}
|
||||
);
|
||||
PHP;
|
||||
}
|
||||
/**
|
||||
* Returns the default value of a function parameter.
|
||||
*
|
||||
* @throws InvalidDefinition Can't get default values from PHP internal classes and functions
|
||||
* @return mixed
|
||||
*/
|
||||
private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function)
|
||||
{
|
||||
try {
|
||||
return $parameter->getDefaultValue();
|
||||
} catch (\ReflectionException $e) {
|
||||
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)
|
||||
{
|
||||
if (\strpos($definition->getClassName(), '@') !== \false) {
|
||||
throw InvalidDefinition::create($definition, \sprintf('Entry "%s" cannot be compiled: anonymous classes cannot be compiled', $definition->getName()));
|
||||
}
|
||||
}
|
||||
private function assertClassIsInstantiable(ObjectDefinition $definition)
|
||||
{
|
||||
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()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Compiler;
|
||||
|
||||
use ElementorProDeps\DI\Factory\RequestedEntry;
|
||||
/**
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class RequestedEntryHolder implements RequestedEntry
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* This class has been auto-generated by PHP-DI.
|
||||
*/
|
||||
class <?=$this->containerClass; ?> extends <?=$this->containerParentClass; ?>
|
||||
{
|
||||
const METHOD_MAPPING = <?php var_export($this->entryToMethodMapping); ?>;
|
||||
|
||||
<?php foreach ($this->methods as $methodName => $methodContent) : ?>
|
||||
protected function <?=$methodName; ?>()
|
||||
{
|
||||
<?=$methodContent; ?>
|
||||
|
||||
}
|
||||
|
||||
<?php endforeach; ?>
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\DI\Definition\FactoryDefinition;
|
||||
use ElementorProDeps\DI\Definition\Helper\DefinitionHelper;
|
||||
use ElementorProDeps\DI\Definition\InstanceDefinition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
use ElementorProDeps\DI\Definition\Resolver\DefinitionResolver;
|
||||
use ElementorProDeps\DI\Definition\Resolver\ResolverDispatcher;
|
||||
use ElementorProDeps\DI\Definition\Source\DefinitionArray;
|
||||
use ElementorProDeps\DI\Definition\Source\MutableDefinitionSource;
|
||||
use ElementorProDeps\DI\Definition\Source\ReflectionBasedAutowiring;
|
||||
use ElementorProDeps\DI\Definition\Source\SourceChain;
|
||||
use ElementorProDeps\DI\Definition\ValueDefinition;
|
||||
use ElementorProDeps\DI\Invoker\DefinitionParameterResolver;
|
||||
use ElementorProDeps\DI\Proxy\ProxyFactory;
|
||||
use InvalidArgumentException;
|
||||
use ElementorProDeps\Invoker\Invoker;
|
||||
use ElementorProDeps\Invoker\InvokerInterface;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\AssociativeArrayResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\Container\TypeHintContainerResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\DefaultValueResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\NumericArrayResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\ResolverChain;
|
||||
use ElementorProDeps\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.
|
||||
* @var array
|
||||
*/
|
||||
protected $resolvedEntries = [];
|
||||
/**
|
||||
* @var MutableDefinitionSource
|
||||
*/
|
||||
private $definitionSource;
|
||||
/**
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $definitionResolver;
|
||||
/**
|
||||
* Map of definitions that are already fetched (local cache).
|
||||
*
|
||||
* @var (Definition|null)[]
|
||||
*/
|
||||
private $fetchedDefinitions = [];
|
||||
/**
|
||||
* Array of entries being resolved. Used to avoid circular dependencies and infinite loops.
|
||||
* @var array
|
||||
*/
|
||||
protected $entriesBeingResolved = [];
|
||||
/**
|
||||
* @var InvokerInterface|null
|
||||
*/
|
||||
private $invoker;
|
||||
/**
|
||||
* Container that wraps this container. If none, points to $this.
|
||||
*
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $delegateContainer;
|
||||
/**
|
||||
* @var ProxyFactory
|
||||
*/
|
||||
protected $proxyFactory;
|
||||
/**
|
||||
* 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(MutableDefinitionSource $definitionSource = null, ProxyFactory $proxyFactory = null, ContainerInterface $wrapperContainer = null)
|
||||
{
|
||||
$this->delegateContainer = $wrapperContainer ?: $this;
|
||||
$this->definitionSource = $definitionSource ?: $this->createDefaultDefinitionSource();
|
||||
$this->proxyFactory = $proxyFactory ?: new ProxyFactory(\false);
|
||||
$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> $name Entry name or a class name.
|
||||
*
|
||||
* @throws DependencyException Error while resolving the entry.
|
||||
* @throws NotFoundException No entry found for the given name.
|
||||
* @return mixed|T
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
// If the entry is already resolved we return it
|
||||
if (isset($this->resolvedEntries[$name]) || \array_key_exists($name, $this->resolvedEntries)) {
|
||||
return $this->resolvedEntries[$name];
|
||||
}
|
||||
$definition = $this->getDefinition($name);
|
||||
if (!$definition) {
|
||||
throw new NotFoundException("No entry or class found for '{$name}'");
|
||||
}
|
||||
$value = $this->resolveDefinition($definition);
|
||||
$this->resolvedEntries[$name] = $value;
|
||||
return $value;
|
||||
}
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return Definition|null
|
||||
*/
|
||||
private function getDefinition($name)
|
||||
{
|
||||
// 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.
|
||||
*
|
||||
* @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.
|
||||
* @return mixed|T
|
||||
*/
|
||||
public function make($name, array $parameters = [])
|
||||
{
|
||||
if (!\is_string($name)) {
|
||||
throw new InvalidArgumentException(\sprintf('The name parameter must be of type string, %s given', \is_object($name) ? \get_class($name) : \gettype($name)));
|
||||
}
|
||||
$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);
|
||||
}
|
||||
/**
|
||||
* Test if the container can provide something for the given name.
|
||||
*
|
||||
* @param string $name Entry name or a class name.
|
||||
*
|
||||
* @throws InvalidArgumentException The name parameter must be of type string.
|
||||
* @return bool
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
if (!\is_string($name)) {
|
||||
throw new InvalidArgumentException(\sprintf('The name parameter must be of type string, %s given', \is_object($name) ? \get_class($name) : \gettype($name)));
|
||||
}
|
||||
if (\array_key_exists($name, $this->resolvedEntries)) {
|
||||
return \true;
|
||||
}
|
||||
$definition = $this->getDefinition($name);
|
||||
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
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DependencyException Error while injecting dependencies
|
||||
* @return object|T $instance Returns the same instance
|
||||
*/
|
||||
public function injectOn($instance)
|
||||
{
|
||||
if (!$instance) {
|
||||
return $instance;
|
||||
}
|
||||
$className = \get_class($instance);
|
||||
// If the class is anonymous, don't cache its definition
|
||||
// Checking for anonymous classes is cleaner via Reflection, but also slower
|
||||
$objectDefinition = \false !== \strpos($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 $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 = [])
|
||||
{
|
||||
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, $value)
|
||||
{
|
||||
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.
|
||||
*
|
||||
* @param mixed $entry
|
||||
*/
|
||||
private function getEntryType($entry) : string
|
||||
{
|
||||
if (\is_object($entry)) {
|
||||
return \sprintf("Object (\n class = %s\n)", \get_class($entry));
|
||||
}
|
||||
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) ? $entry : \ucfirst(\gettype($entry)));
|
||||
}
|
||||
/**
|
||||
* Resolves a definition.
|
||||
*
|
||||
* Checks for circular dependencies while resolving the definition.
|
||||
*
|
||||
* @throws DependencyException Error while resolving the entry.
|
||||
* @return mixed
|
||||
*/
|
||||
private function resolveDefinition(Definition $definition, array $parameters = [])
|
||||
{
|
||||
$entryName = $definition->getName();
|
||||
// Check if we are already getting this entry -> circular dependency
|
||||
if (isset($this->entriesBeingResolved[$entryName])) {
|
||||
throw new DependencyException("Circular dependency detected while trying to resolve entry '{$entryName}'");
|
||||
}
|
||||
$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)
|
||||
{
|
||||
// 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() : SourceChain
|
||||
{
|
||||
$source = new SourceChain([new ReflectionBasedAutowiring()]);
|
||||
$source->setMutableDefinitionSource(new DefinitionArray([], new ReflectionBasedAutowiring()));
|
||||
return $source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI;
|
||||
|
||||
use ElementorProDeps\DI\Compiler\Compiler;
|
||||
use ElementorProDeps\DI\Definition\Source\AnnotationBasedAutowiring;
|
||||
use ElementorProDeps\DI\Definition\Source\DefinitionArray;
|
||||
use ElementorProDeps\DI\Definition\Source\DefinitionFile;
|
||||
use ElementorProDeps\DI\Definition\Source\DefinitionSource;
|
||||
use ElementorProDeps\DI\Definition\Source\NoAutowiring;
|
||||
use ElementorProDeps\DI\Definition\Source\ReflectionBasedAutowiring;
|
||||
use ElementorProDeps\DI\Definition\Source\SourceCache;
|
||||
use ElementorProDeps\DI\Definition\Source\SourceChain;
|
||||
use ElementorProDeps\DI\Proxy\ProxyFactory;
|
||||
use InvalidArgumentException;
|
||||
use ElementorProDeps\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>
|
||||
*/
|
||||
class ContainerBuilder
|
||||
{
|
||||
/**
|
||||
* Name of the container class, used to create the container.
|
||||
* @var string
|
||||
*/
|
||||
private $containerClass;
|
||||
/**
|
||||
* Name of the container parent class, used on compiled container.
|
||||
* @var string
|
||||
*/
|
||||
private $containerParentClass;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $useAutowiring = \true;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $useAnnotations = \false;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $ignorePhpDocErrors = \false;
|
||||
/**
|
||||
* If true, write the proxies to disk to improve performances.
|
||||
* @var bool
|
||||
*/
|
||||
private $writeProxiesToFile = \false;
|
||||
/**
|
||||
* Directory where to write the proxies (if $writeProxiesToFile is enabled).
|
||||
* @var string|null
|
||||
*/
|
||||
private $proxyDirectory;
|
||||
/**
|
||||
* If PHP-DI is wrapped in another container, this references the wrapper.
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $wrapperContainer;
|
||||
/**
|
||||
* @var DefinitionSource[]|string[]|array[]
|
||||
*/
|
||||
private $definitionSources = [];
|
||||
/**
|
||||
* Whether the container has already been built.
|
||||
* @var bool
|
||||
*/
|
||||
private $locked = \false;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $compileToDirectory;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $sourceCache = \false;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sourceCacheNamespace;
|
||||
/**
|
||||
* Build a container configured for the dev environment.
|
||||
*/
|
||||
public static function buildDevContainer() : Container
|
||||
{
|
||||
return new Container();
|
||||
}
|
||||
/**
|
||||
* @param string $containerClass Name of the container class, used to create the container.
|
||||
*/
|
||||
public function __construct(string $containerClass = Container::class)
|
||||
{
|
||||
$this->containerClass = $containerClass;
|
||||
}
|
||||
/**
|
||||
* Build and return a container.
|
||||
*
|
||||
* @return Container
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$sources = \array_reverse($this->definitionSources);
|
||||
if ($this->useAnnotations) {
|
||||
$autowiring = new AnnotationBasedAutowiring($this->ignorePhpDocErrors);
|
||||
$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);
|
||||
} elseif (\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 = new ProxyFactory($this->writeProxiesToFile, $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 || $this->useAnnotations);
|
||||
// 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
|
||||
*
|
||||
* @param string $directory Directory in which to put the compiled container.
|
||||
* @param string $containerClass Name of the compiled class. Customize only if necessary.
|
||||
* @param string $containerParentClass Name of the compiled container parent class. Customize only if necessary.
|
||||
*/
|
||||
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 annotations to guess injections.
|
||||
*
|
||||
* Disabled by default.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function useAnnotations(bool $bool) : self
|
||||
{
|
||||
$this->ensureNotLocked();
|
||||
$this->useAnnotations = $bool;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Enable or disable ignoring phpdoc errors (non-existent classes in `@param` or `@var`).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function ignorePhpDocErrors(bool $bool) : self
|
||||
{
|
||||
$this->ensureNotLocked();
|
||||
$this->ignorePhpDocErrors = $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
|
||||
* @throws InvalidArgumentException when writeToFile is set to true and the proxy directory is null
|
||||
* @return $this
|
||||
*/
|
||||
public function writeProxiesToFile(bool $writeToFile, string $proxyDirectory = null) : self
|
||||
{
|
||||
$this->ensureNotLocked();
|
||||
$this->writeProxiesToFile = $writeToFile;
|
||||
if ($writeToFile && $proxyDirectory === null) {
|
||||
throw new InvalidArgumentException('The proxy directory must be specified if you want to write proxies on disk');
|
||||
}
|
||||
$this->proxyDirectory = $proxyDirectory;
|
||||
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(...$definitions) : self
|
||||
{
|
||||
$this->ensureNotLocked();
|
||||
foreach ($definitions as $definition) {
|
||||
if (!\is_string($definition) && !\is_array($definition) && !$definition instanceof DefinitionSource) {
|
||||
throw new InvalidArgumentException(\sprintf('%s parameter must be a string, an array or a DefinitionSource object, %s given', 'ContainerBuilder::addDefinitions()', \is_object($definition) ? \get_class($definition) : \gettype($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 annotations, 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()
|
||||
{
|
||||
if ($this->locked) {
|
||||
throw new \LogicException('The ContainerBuilder cannot be modified after the container has been built');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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.
|
||||
* @var string
|
||||
*/
|
||||
private $name = '';
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $values;
|
||||
public function __construct(array $values)
|
||||
{
|
||||
$this->values = $values;
|
||||
}
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
public function getValues() : array
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
public function replaceNestedDefinitions(callable $replacer)
|
||||
{
|
||||
$this->values = \array_map($replacer, $this->values);
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
$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 . ']';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition;
|
||||
|
||||
use ElementorProDeps\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
|
||||
{
|
||||
/**
|
||||
* @var ArrayDefinition
|
||||
*/
|
||||
private $subDefinition;
|
||||
public function getValues() : array
|
||||
{
|
||||
if (!$this->subDefinition) {
|
||||
return parent::getValues();
|
||||
}
|
||||
return \array_merge($this->subDefinition->getValues(), parent::getValues());
|
||||
}
|
||||
public function setExtendedDefinition(Definition $definition)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition;
|
||||
|
||||
/**
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class AutowireDefinition extends ObjectDefinition
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition;
|
||||
|
||||
/**
|
||||
* Factory that decorates a sub-definition.
|
||||
*
|
||||
* @since 5.0
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class DecoratorDefinition extends FactoryDefinition implements Definition, ExtendsPreviousDefinition
|
||||
{
|
||||
/**
|
||||
* @var Definition|null
|
||||
*/
|
||||
private $decorated;
|
||||
public function setExtendedDefinition(Definition $definition)
|
||||
{
|
||||
$this->decorated = $definition;
|
||||
}
|
||||
/**
|
||||
* @return Definition|null
|
||||
*/
|
||||
public function getDecoratedDefinition()
|
||||
{
|
||||
return $this->decorated;
|
||||
}
|
||||
public function replaceNestedDefinitions(callable $replacer)
|
||||
{
|
||||
// no nested definitions
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
return 'Decorate(' . $this->getName() . ')';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition;
|
||||
|
||||
use ElementorProDeps\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
|
||||
{
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* Apply a callable that replaces the definitions nested in this definition.
|
||||
*/
|
||||
public function replaceNestedDefinitions(callable $replacer);
|
||||
/**
|
||||
* Definitions can be cast to string for debugging information.
|
||||
*/
|
||||
public function __toString();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Dumper;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
use ElementorProDeps\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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 $e) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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.
|
||||
* @var string
|
||||
*/
|
||||
private $name = '';
|
||||
/**
|
||||
* The name of the environment variable.
|
||||
* @var string
|
||||
*/
|
||||
private $variableName;
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isOptional;
|
||||
/**
|
||||
* The default value to use if the environment variable is optional and not provided.
|
||||
* @var mixed
|
||||
*/
|
||||
private $defaultValue;
|
||||
/**
|
||||
* @param string $variableName The name of the environment variable
|
||||
* @param bool $isOptional Whether or not the environment variable definition is optional
|
||||
* @param mixed $defaultValue The default value to use if the environment variable is optional and not provided
|
||||
*/
|
||||
public function __construct(string $variableName, bool $isOptional = \false, $defaultValue = null)
|
||||
{
|
||||
$this->variableName = $variableName;
|
||||
$this->isOptional = $isOptional;
|
||||
$this->defaultValue = $defaultValue;
|
||||
}
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
public function setName(string $name)
|
||||
{
|
||||
$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()
|
||||
{
|
||||
return $this->defaultValue;
|
||||
}
|
||||
public function replaceNestedDefinitions(callable $replacer)
|
||||
{
|
||||
$this->defaultValue = $replacer($this->defaultValue);
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
$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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Exception;
|
||||
|
||||
/**
|
||||
* Error in the definitions using annotations.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class InvalidAnnotation extends InvalidDefinition
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Exception;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition;
|
||||
|
||||
/**
|
||||
* Definition of a value or class with a factory.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class FactoryDefinition implements Definition
|
||||
{
|
||||
/**
|
||||
* Entry name.
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* Callable that returns the value.
|
||||
* @var callable
|
||||
*/
|
||||
private $factory;
|
||||
/**
|
||||
* Factory parameters.
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $parameters = [];
|
||||
/**
|
||||
* @param string $name Entry name
|
||||
* @param callable $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, $factory, array $parameters = [])
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->factory = $factory;
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
/**
|
||||
* @return callable Callable that returns the value associated to the entry name.
|
||||
*/
|
||||
public function getCallable()
|
||||
{
|
||||
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)
|
||||
{
|
||||
$this->parameters = \array_map($replacer, $this->parameters);
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
return 'Factory';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Helper;
|
||||
|
||||
use ElementorProDeps\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
|
||||
{
|
||||
const DEFINITION_CLASS = AutowireDefinition::class;
|
||||
/**
|
||||
* Defines a value for a specific argument of the constructor.
|
||||
*
|
||||
* This method is usually used together with annotations 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 annotations 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($parameter, $value)
|
||||
{
|
||||
$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 annotations 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 annotations 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, $parameter, $value)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Helper;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition\MethodInjection;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition\PropertyInjection;
|
||||
/**
|
||||
* Helps defining how to create an instance of a class.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class CreateDefinitionHelper implements DefinitionHelper
|
||||
{
|
||||
const DEFINITION_CLASS = ObjectDefinition::class;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $className;
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private $lazy;
|
||||
/**
|
||||
* Array of constructor parameters.
|
||||
* @var array
|
||||
*/
|
||||
protected $constructor = [];
|
||||
/**
|
||||
* Array of properties and their value.
|
||||
* @var array
|
||||
*/
|
||||
private $properties = [];
|
||||
/**
|
||||
* Array of methods and their parameters.
|
||||
* @var array
|
||||
*/
|
||||
protected $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()
|
||||
{
|
||||
$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(...$parameters)
|
||||
{
|
||||
$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, $value)
|
||||
{
|
||||
$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, ...$parameters)
|
||||
{
|
||||
if (!isset($this->methods[$method])) {
|
||||
$this->methods[$method] = [];
|
||||
}
|
||||
$this->methods[$method][] = $parameters;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* @return ObjectDefinition
|
||||
*/
|
||||
public function getDefinition(string $entryName) : Definition
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Helper;
|
||||
|
||||
use ElementorProDeps\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;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Helper;
|
||||
|
||||
use ElementorProDeps\DI\Definition\DecoratorDefinition;
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\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;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $decorate;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $parameters = [];
|
||||
/**
|
||||
* @param callable $factory
|
||||
* @param bool $decorate Is the factory decorating a previous definition?
|
||||
*/
|
||||
public function __construct($factory, bool $decorate = \false)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->decorate = $decorate;
|
||||
}
|
||||
/**
|
||||
* @param string $entryName Container entry name
|
||||
* @return FactoryDefinition
|
||||
*/
|
||||
public function getDefinition(string $entryName) : Definition
|
||||
{
|
||||
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 annotations 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, $value)
|
||||
{
|
||||
$this->parameters[$parameter] = $value;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition;
|
||||
|
||||
/**
|
||||
* Defines injections on an existing class instance.
|
||||
*
|
||||
* @since 5.0
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class InstanceDefinition implements Definition
|
||||
{
|
||||
/**
|
||||
* Instance on which to inject dependencies.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
private $instance;
|
||||
/**
|
||||
* @var ObjectDefinition
|
||||
*/
|
||||
private $objectDefinition;
|
||||
/**
|
||||
* @param object $instance
|
||||
*/
|
||||
public function __construct($instance, ObjectDefinition $objectDefinition)
|
||||
{
|
||||
$this->instance = $instance;
|
||||
$this->objectDefinition = $objectDefinition;
|
||||
}
|
||||
public function getName() : string
|
||||
{
|
||||
// Name are superfluous for instance definitions
|
||||
return '';
|
||||
}
|
||||
public function setName(string $name)
|
||||
{
|
||||
// Name are superfluous for instance definitions
|
||||
}
|
||||
/**
|
||||
* @return object
|
||||
*/
|
||||
public function getInstance()
|
||||
{
|
||||
return $this->instance;
|
||||
}
|
||||
public function getObjectDefinition() : ObjectDefinition
|
||||
{
|
||||
return $this->objectDefinition;
|
||||
}
|
||||
public function replaceNestedDefinitions(callable $replacer)
|
||||
{
|
||||
$this->objectDefinition->replaceNestedDefinitions($replacer);
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
return 'Instance';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Dumper\ObjectDefinitionDumper;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition\MethodInjection;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition\PropertyInjection;
|
||||
use ElementorProDeps\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).
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* Class name (if null, then the class name is $name).
|
||||
* @var string|null
|
||||
*/
|
||||
protected $className;
|
||||
/**
|
||||
* Constructor parameter injection.
|
||||
* @var MethodInjection|null
|
||||
*/
|
||||
protected $constructorInjection;
|
||||
/**
|
||||
* Property injections.
|
||||
* @var PropertyInjection[]
|
||||
*/
|
||||
protected $propertyInjections = [];
|
||||
/**
|
||||
* Method calls.
|
||||
* @var MethodInjection[][]
|
||||
*/
|
||||
protected $methodInjections = [];
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
protected $lazy;
|
||||
/**
|
||||
* Store if the class exists. Storing it (in cache) avoids recomputing this.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $classExists;
|
||||
/**
|
||||
* Store if the class is instantiable. Storing it (in cache) avoids recomputing this.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $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)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
public function setClassName(string $className = null)
|
||||
{
|
||||
$this->className = $className;
|
||||
$this->updateCache();
|
||||
}
|
||||
public function getClassName() : string
|
||||
{
|
||||
if ($this->className !== null) {
|
||||
return $this->className;
|
||||
}
|
||||
return $this->name;
|
||||
}
|
||||
/**
|
||||
* @return MethodInjection|null
|
||||
*/
|
||||
public function getConstructorInjection()
|
||||
{
|
||||
return $this->constructorInjection;
|
||||
}
|
||||
public function setConstructorInjection(MethodInjection $constructorInjection)
|
||||
{
|
||||
$this->constructorInjection = $constructorInjection;
|
||||
}
|
||||
public function completeConstructorInjection(MethodInjection $injection)
|
||||
{
|
||||
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)
|
||||
{
|
||||
$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)
|
||||
{
|
||||
$method = $methodInjection->getMethodName();
|
||||
if (!isset($this->methodInjections[$method])) {
|
||||
$this->methodInjections[$method] = [];
|
||||
}
|
||||
$this->methodInjections[$method][] = $methodInjection;
|
||||
}
|
||||
public function completeFirstMethodInjection(MethodInjection $injection)
|
||||
{
|
||||
$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)
|
||||
{
|
||||
$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)
|
||||
{
|
||||
\array_walk($this->propertyInjections, function (PropertyInjection $propertyInjection) use($replacer) {
|
||||
$propertyInjection->replaceNestedDefinition($replacer);
|
||||
});
|
||||
if ($this->constructorInjection) {
|
||||
$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)
|
||||
{
|
||||
$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()
|
||||
{
|
||||
return (new ObjectDefinitionDumper())->dump($this);
|
||||
}
|
||||
private function updateCache()
|
||||
{
|
||||
$className = $this->getClassName();
|
||||
$this->classExists = \class_exists($className) || \interface_exists($className);
|
||||
if (!$this->classExists) {
|
||||
$this->isInstantiable = \false;
|
||||
return;
|
||||
}
|
||||
$class = new ReflectionClass($className);
|
||||
$this->isInstantiable = $class->isInstantiable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
/**
|
||||
* Describe an injection in an object method.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class MethodInjection implements Definition
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $methodName;
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $parameters = [];
|
||||
public function __construct(string $methodName, array $parameters = [])
|
||||
{
|
||||
$this->methodName = $methodName;
|
||||
$this->parameters = $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)
|
||||
{
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
public function merge(self $definition)
|
||||
{
|
||||
// In case of conflicts, the current definition prevails.
|
||||
$this->parameters = $this->parameters + $definition->parameters;
|
||||
}
|
||||
public function getName() : string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
public function setName(string $name)
|
||||
{
|
||||
// The name does not matter for method injections
|
||||
}
|
||||
public function replaceNestedDefinitions(callable $replacer)
|
||||
{
|
||||
$this->parameters = \array_map($replacer, $this->parameters);
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return \sprintf('method(%s)', $this->methodName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
|
||||
/**
|
||||
* Describe an injection in a class property.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class PropertyInjection
|
||||
{
|
||||
/**
|
||||
* Property name.
|
||||
* @var string
|
||||
*/
|
||||
private $propertyName;
|
||||
/**
|
||||
* Value that should be injected in the property.
|
||||
* @var mixed
|
||||
*/
|
||||
private $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.
|
||||
* @var string|null
|
||||
*/
|
||||
private $className;
|
||||
/**
|
||||
* @param string $propertyName Property name
|
||||
* @param mixed $value Value that should be injected in the property
|
||||
*/
|
||||
public function __construct(string $propertyName, $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()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getClassName()
|
||||
{
|
||||
return $this->className;
|
||||
}
|
||||
public function replaceNestedDefinition(callable $replacer)
|
||||
{
|
||||
$this->value = $replacer($this->value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition;
|
||||
|
||||
use ElementorProDeps\Psr\Container\ContainerInterface;
|
||||
/**
|
||||
* Represents a reference to another entry.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class Reference implements Definition, SelfResolvingDefinition
|
||||
{
|
||||
/**
|
||||
* Entry name.
|
||||
* @var string
|
||||
*/
|
||||
private $name = '';
|
||||
/**
|
||||
* Name of the target entry.
|
||||
* @var string
|
||||
*/
|
||||
private $targetEntryName;
|
||||
/**
|
||||
* @param string $targetEntryName Name of the target entry
|
||||
*/
|
||||
public function __construct(string $targetEntryName)
|
||||
{
|
||||
$this->targetEntryName = $targetEntryName;
|
||||
}
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
public function getTargetEntryName() : string
|
||||
{
|
||||
return $this->targetEntryName;
|
||||
}
|
||||
public function resolve(ContainerInterface $container)
|
||||
{
|
||||
return $container->get($this->getTargetEntryName());
|
||||
}
|
||||
public function isResolvable(ContainerInterface $container) : bool
|
||||
{
|
||||
return $container->has($this->getTargetEntryName());
|
||||
}
|
||||
public function replaceNestedDefinitions(callable $replacer)
|
||||
{
|
||||
// no nested definitions
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
return \sprintf('get(%s)', $this->targetEntryName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Resolver;
|
||||
|
||||
use ElementorProDeps\DI\Definition\ArrayDefinition;
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\DependencyException;
|
||||
use Exception;
|
||||
/**
|
||||
* Resolves an array definition to a value.
|
||||
*
|
||||
* @since 5.0
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class ArrayResolver implements DefinitionResolver
|
||||
{
|
||||
/**
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $definitionResolver;
|
||||
/**
|
||||
* @param DefinitionResolver $definitionResolver Used to resolve nested definitions.
|
||||
*/
|
||||
public function __construct(DefinitionResolver $definitionResolver)
|
||||
{
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
private function resolveDefinition(Definition $value, ArrayDefinition $definition, $key)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Resolver;
|
||||
|
||||
use ElementorProDeps\DI\Definition\DecoratorDefinition;
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\Psr\Container\ContainerInterface;
|
||||
/**
|
||||
* Resolves a decorator definition to a value.
|
||||
*
|
||||
* @since 5.0
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class DecoratorResolver implements DefinitionResolver
|
||||
{
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
/**
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $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(ContainerInterface $container, DefinitionResolver $definitionResolver)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->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 = [])
|
||||
{
|
||||
$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 \call_user_func($callable, $decorated, $this->container);
|
||||
}
|
||||
public function isResolvable(Definition $definition, array $parameters = []) : bool
|
||||
{
|
||||
return \true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Resolver;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
/**
|
||||
* Resolves a definition to a value.
|
||||
*
|
||||
* @since 4.0
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
interface DefinitionResolver
|
||||
{
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @throws InvalidDefinition If the definition cannot be resolved.
|
||||
*
|
||||
* @return mixed Value obtained from the definition.
|
||||
*/
|
||||
public function resolve(Definition $definition, array $parameters = []);
|
||||
/**
|
||||
* Check if a definition can be resolved.
|
||||
*
|
||||
* @param Definition $definition Object that defines how the value should be obtained.
|
||||
* @param array $parameters Optional parameters to use to build the entry.
|
||||
*/
|
||||
public function isResolvable(Definition $definition, array $parameters = []) : bool;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Resolver;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\EnvironmentVariableDefinition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
/**
|
||||
* Resolves a environment variable definition to a value.
|
||||
*
|
||||
* @author James Harris <james.harris@icecave.com.au>
|
||||
*/
|
||||
class EnvironmentVariableResolver implements DefinitionResolver
|
||||
{
|
||||
/**
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $definitionResolver;
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $variableReader;
|
||||
public function __construct(DefinitionResolver $definitionResolver, $variableReader = null)
|
||||
{
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
$this->variableReader = $variableReader ?? [$this, 'getEnvVariable'];
|
||||
}
|
||||
/**
|
||||
* Resolve an environment variable definition to a value.
|
||||
*
|
||||
* @param EnvironmentVariableDefinition $definition
|
||||
*/
|
||||
public function resolve(Definition $definition, array $parameters = [])
|
||||
{
|
||||
$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)
|
||||
{
|
||||
if (isset($_ENV[$variableName])) {
|
||||
return $_ENV[$variableName];
|
||||
} elseif (isset($_SERVER[$variableName])) {
|
||||
return $_SERVER[$variableName];
|
||||
}
|
||||
return \getenv($variableName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Resolver;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\DI\Definition\FactoryDefinition;
|
||||
use ElementorProDeps\DI\Invoker\FactoryParameterResolver;
|
||||
use ElementorProDeps\Invoker\Exception\NotCallableException;
|
||||
use ElementorProDeps\Invoker\Exception\NotEnoughParametersException;
|
||||
use ElementorProDeps\Invoker\Invoker;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\AssociativeArrayResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\DefaultValueResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\NumericArrayResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\ResolverChain;
|
||||
use ElementorProDeps\Psr\Container\ContainerInterface;
|
||||
/**
|
||||
* Resolves a factory definition to a value.
|
||||
*
|
||||
* @since 4.0
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class FactoryResolver implements DefinitionResolver
|
||||
{
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
/**
|
||||
* @var Invoker|null
|
||||
*/
|
||||
private $invoker;
|
||||
/**
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $resolver;
|
||||
/**
|
||||
* 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(ContainerInterface $container, DefinitionResolver $resolver)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->resolver = $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 = [])
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Resolver;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\InstanceDefinition;
|
||||
use ElementorProDeps\DI\DependencyException;
|
||||
use ElementorProDeps\Psr\Container\NotFoundExceptionInterface;
|
||||
/**
|
||||
* Injects dependencies on an existing instance.
|
||||
*
|
||||
* @since 5.0
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class InstanceInjector extends ObjectCreator
|
||||
{
|
||||
/**
|
||||
* Injects dependencies on an existing instance.
|
||||
*
|
||||
* @param InstanceDefinition $definition
|
||||
*/
|
||||
public function resolve(Definition $definition, array $parameters = [])
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Resolver;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition\PropertyInjection;
|
||||
use ElementorProDeps\DI\DependencyException;
|
||||
use ElementorProDeps\DI\Proxy\ProxyFactory;
|
||||
use Exception;
|
||||
use ElementorProDeps\ProxyManager\Proxy\LazyLoadingInterface;
|
||||
use ElementorProDeps\Psr\Container\NotFoundExceptionInterface;
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
/**
|
||||
* Create objects based on an object definition.
|
||||
*
|
||||
* @since 4.0
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class ObjectCreator implements DefinitionResolver
|
||||
{
|
||||
/**
|
||||
* @var ProxyFactory
|
||||
*/
|
||||
private $proxyFactory;
|
||||
/**
|
||||
* @var ParameterResolver
|
||||
*/
|
||||
private $parameterResolver;
|
||||
/**
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $definitionResolver;
|
||||
/**
|
||||
* @param DefinitionResolver $definitionResolver Used to resolve nested definitions.
|
||||
* @param ProxyFactory $proxyFactory Used to create proxies for lazy injections.
|
||||
*/
|
||||
public function __construct(DefinitionResolver $definitionResolver, ProxyFactory $proxyFactory)
|
||||
{
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
$this->proxyFactory = $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
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
public function resolve(Definition $definition, array $parameters = [])
|
||||
{
|
||||
// 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) : LazyLoadingInterface
|
||||
{
|
||||
/** @noinspection PhpUnusedParameterInspection */
|
||||
$proxy = $this->proxyFactory->createProxy($definition->getClassName(), function (&$wrappedObject, $proxy, $method, $params, &$initializer) use($definition, $parameters) {
|
||||
$wrappedObject = $this->createInstance($definition, $parameters);
|
||||
$initializer = null;
|
||||
// turning off further lazy initialization
|
||||
return \true;
|
||||
});
|
||||
return $proxy;
|
||||
}
|
||||
/**
|
||||
* Creates an instance of the class and injects dependencies..
|
||||
*
|
||||
* @param array $parameters Optional parameters to use to create the instance.
|
||||
*
|
||||
* @throws InvalidDefinition
|
||||
* @throws DependencyException
|
||||
* @return object
|
||||
*/
|
||||
private function createInstance(ObjectDefinition $definition, array $parameters)
|
||||
{
|
||||
// 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()));
|
||||
}
|
||||
$classname = $definition->getClassName();
|
||||
$classReflection = new ReflectionClass($classname);
|
||||
$constructorInjection = $definition->getConstructorInjection();
|
||||
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, ObjectDefinition $objectDefinition)
|
||||
{
|
||||
// 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
|
||||
* @throws InvalidDefinition
|
||||
*/
|
||||
private function injectProperty($object, PropertyInjection $propertyInjection)
|
||||
{
|
||||
$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', \get_class($object), $propertyName, $e->getMessage()), 0, $e);
|
||||
}
|
||||
}
|
||||
self::setPrivatePropertyValue($propertyInjection->getClassName(), $object, $propertyName, $value);
|
||||
}
|
||||
public static function setPrivatePropertyValue(string $className = null, $object, string $propertyName, $propertyValue)
|
||||
{
|
||||
$className = $className ?: \get_class($object);
|
||||
$property = new ReflectionProperty($className, $propertyName);
|
||||
if (!$property->isPublic()) {
|
||||
$property->setAccessible(\true);
|
||||
}
|
||||
$property->setValue($object, $propertyValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Resolver;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\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
|
||||
{
|
||||
/**
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $definitionResolver;
|
||||
/**
|
||||
* @param DefinitionResolver $definitionResolver Will be used to resolve nested definitions.
|
||||
*/
|
||||
public function __construct(DefinitionResolver $definitionResolver)
|
||||
{
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
}
|
||||
/**
|
||||
* @throws InvalidDefinition A parameter has no value defined or guessable.
|
||||
* @return array Parameters to use to call the function.
|
||||
*/
|
||||
public function resolveParameters(MethodInjection $definition = null, ReflectionMethod $method = null, array $parameters = [])
|
||||
{
|
||||
$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
|
||||
* @return mixed
|
||||
*/
|
||||
private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function)
|
||||
{
|
||||
try {
|
||||
return $parameter->getDefaultValue();
|
||||
} catch (\ReflectionException $e) {
|
||||
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() . '()';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Resolver;
|
||||
|
||||
use ElementorProDeps\DI\Definition\ArrayDefinition;
|
||||
use ElementorProDeps\DI\Definition\DecoratorDefinition;
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\EnvironmentVariableDefinition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\DI\Definition\FactoryDefinition;
|
||||
use ElementorProDeps\DI\Definition\InstanceDefinition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
use ElementorProDeps\DI\Definition\SelfResolvingDefinition;
|
||||
use ElementorProDeps\DI\Proxy\ProxyFactory;
|
||||
use ElementorProDeps\Psr\Container\ContainerInterface;
|
||||
/**
|
||||
* Dispatches to more specific resolvers.
|
||||
*
|
||||
* Dynamic dispatch pattern.
|
||||
*
|
||||
* @since 5.0
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class ResolverDispatcher implements DefinitionResolver
|
||||
{
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
/**
|
||||
* @var ProxyFactory
|
||||
*/
|
||||
private $proxyFactory;
|
||||
private $arrayResolver;
|
||||
private $factoryResolver;
|
||||
private $decoratorResolver;
|
||||
private $objectResolver;
|
||||
private $instanceResolver;
|
||||
private $envVariableResolver;
|
||||
public function __construct(ContainerInterface $container, ProxyFactory $proxyFactory)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->proxyFactory = $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.
|
||||
*
|
||||
* @throws InvalidDefinition If the definition cannot be resolved.
|
||||
*
|
||||
* @return mixed Value obtained from the definition.
|
||||
*/
|
||||
public function resolve(Definition $definition, array $parameters = [])
|
||||
{
|
||||
// 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 ' . \get_class($definition));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition;
|
||||
|
||||
use ElementorProDeps\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.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function resolve(ContainerInterface $container);
|
||||
/**
|
||||
* Check if a definition can be resolved.
|
||||
*/
|
||||
public function isResolvable(ContainerInterface $container) : bool;
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Source;
|
||||
|
||||
use ElementorProDeps\DI\Annotation\Inject;
|
||||
use ElementorProDeps\DI\Annotation\Injectable;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidAnnotation;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition\MethodInjection;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition\PropertyInjection;
|
||||
use ElementorProDeps\DI\Definition\Reference;
|
||||
use ElementorProDeps\Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use ElementorProDeps\Doctrine\Common\Annotations\Reader;
|
||||
use ElementorProDeps\Doctrine\Common\Annotations\SimpleAnnotationReader;
|
||||
use InvalidArgumentException;
|
||||
use ElementorProDeps\PhpDocReader\PhpDocReader;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
use ReflectionProperty;
|
||||
use UnexpectedValueException;
|
||||
/**
|
||||
* Provides DI definitions by reading annotations such as @ Inject and @ var annotations.
|
||||
*
|
||||
* Uses Autowiring, Doctrine's Annotations and regex docblock parsing.
|
||||
* This source automatically includes the reflection source.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class AnnotationBasedAutowiring implements DefinitionSource, Autowiring
|
||||
{
|
||||
/**
|
||||
* @var Reader
|
||||
*/
|
||||
private $annotationReader;
|
||||
/**
|
||||
* @var PhpDocReader
|
||||
*/
|
||||
private $phpDocReader;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $ignorePhpDocErrors;
|
||||
public function __construct($ignorePhpDocErrors = \false)
|
||||
{
|
||||
$this->ignorePhpDocErrors = (bool) $ignorePhpDocErrors;
|
||||
}
|
||||
public function autowire(string $name, ObjectDefinition $definition = null)
|
||||
{
|
||||
$className = $definition ? $definition->getClassName() : $name;
|
||||
if (!\class_exists($className) && !\interface_exists($className)) {
|
||||
return $definition;
|
||||
}
|
||||
$definition = $definition ?: new ObjectDefinition($name);
|
||||
$class = new ReflectionClass($className);
|
||||
$this->readInjectableAnnotation($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;
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws InvalidAnnotation
|
||||
* @throws InvalidArgumentException The class doesn't exist
|
||||
*/
|
||||
public function getDefinition(string $name)
|
||||
{
|
||||
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)
|
||||
{
|
||||
foreach ($class->getProperties() as $property) {
|
||||
if ($property->isStatic()) {
|
||||
continue;
|
||||
}
|
||||
$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) {
|
||||
if ($property->isStatic()) {
|
||||
continue;
|
||||
}
|
||||
$this->readProperty($property, $definition, $class->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
private function readProperty(ReflectionProperty $property, ObjectDefinition $definition, $classname = null)
|
||||
{
|
||||
// Look for @Inject annotation
|
||||
$annotation = $this->getAnnotationReader()->getPropertyAnnotation($property, 'ElementorProDeps\\DI\\Annotation\\Inject');
|
||||
if (!$annotation instanceof Inject) {
|
||||
return;
|
||||
}
|
||||
// Try to @Inject("name") or look for @var content
|
||||
$entryName = $annotation->getName() ?: $this->getPhpDocReader()->getPropertyClass($property);
|
||||
// Try using PHP7.4 typed properties
|
||||
if (\PHP_VERSION_ID > 70400 && $entryName === null && $property->getType() instanceof ReflectionNamedType && (\class_exists($property->getType()->getName()) || \interface_exists($property->getType()->getName()))) {
|
||||
$entryName = $property->getType()->getName();
|
||||
}
|
||||
if ($entryName === null) {
|
||||
throw new InvalidAnnotation(\sprintf('@Inject found on property %s::%s but unable to guess what to inject, use a @var annotation', $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)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return MethodInjection|null
|
||||
*/
|
||||
private function getMethodInjection(ReflectionMethod $method)
|
||||
{
|
||||
// Look for @Inject annotation
|
||||
try {
|
||||
$annotation = $this->getAnnotationReader()->getMethodAnnotation($method, 'ElementorProDeps\\DI\\Annotation\\Inject');
|
||||
} catch (InvalidAnnotation $e) {
|
||||
throw new InvalidAnnotation(\sprintf('@Inject annotation on %s::%s is malformed. %s', $method->getDeclaringClass()->getName(), $method->getName(), $e->getMessage()), 0, $e);
|
||||
}
|
||||
// @Inject on constructor is implicit
|
||||
if (!($annotation || $method->isConstructor())) {
|
||||
return null;
|
||||
}
|
||||
$annotationParameters = $annotation instanceof Inject ? $annotation->getParameters() : [];
|
||||
$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);
|
||||
}
|
||||
/**
|
||||
* @param int $parameterIndex
|
||||
*
|
||||
* @return string|null Entry name or null if not found.
|
||||
*/
|
||||
private function getMethodParameter($parameterIndex, ReflectionParameter $parameter, array $annotationParameters)
|
||||
{
|
||||
// @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;
|
||||
}
|
||||
// Try to use the type-hinting
|
||||
$parameterType = $parameter->getType();
|
||||
if ($parameterType && $parameterType instanceof ReflectionNamedType && !$parameterType->isBuiltin()) {
|
||||
return $parameterType->getName();
|
||||
}
|
||||
// Last resort, look for @param tag
|
||||
return $this->getPhpDocReader()->getParameterClass($parameter);
|
||||
}
|
||||
/**
|
||||
* @return Reader The annotation reader
|
||||
*/
|
||||
public function getAnnotationReader()
|
||||
{
|
||||
if ($this->annotationReader === null) {
|
||||
AnnotationRegistry::registerLoader('class_exists');
|
||||
$this->annotationReader = new SimpleAnnotationReader();
|
||||
$this->annotationReader->addNamespace('ElementorProDeps\\DI\\Annotation');
|
||||
}
|
||||
return $this->annotationReader;
|
||||
}
|
||||
/**
|
||||
* @return PhpDocReader
|
||||
*/
|
||||
private function getPhpDocReader()
|
||||
{
|
||||
if ($this->phpDocReader === null) {
|
||||
$this->phpDocReader = new PhpDocReader($this->ignorePhpDocErrors);
|
||||
}
|
||||
return $this->phpDocReader;
|
||||
}
|
||||
private function readInjectableAnnotation(ReflectionClass $class, ObjectDefinition $definition)
|
||||
{
|
||||
try {
|
||||
/** @var Injectable|null $annotation */
|
||||
$annotation = $this->getAnnotationReader()->getClassAnnotation($class, 'ElementorProDeps\\DI\\Annotation\\Injectable');
|
||||
} catch (UnexpectedValueException $e) {
|
||||
throw new InvalidAnnotation(\sprintf('Error while reading @Injectable on %s: %s', $class->getName(), $e->getMessage()), 0, $e);
|
||||
}
|
||||
if (!$annotation) {
|
||||
return;
|
||||
}
|
||||
if ($annotation->isLazy() !== null) {
|
||||
$definition->setLazy($annotation->isLazy());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Source;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\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.
|
||||
* @return ObjectDefinition|null
|
||||
*/
|
||||
public function autowire(string $name, ObjectDefinition $definition = null);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Source;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
/**
|
||||
* Reads DI definitions from a PHP array.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class DefinitionArray implements DefinitionSource, MutableDefinitionSource
|
||||
{
|
||||
const WILDCARD = '*';
|
||||
/**
|
||||
* Matches anything except "\".
|
||||
*/
|
||||
const WILDCARD_PATTERN = '([^\\\\]+)';
|
||||
/**
|
||||
* DI definitions in a PHP array.
|
||||
* @var array
|
||||
*/
|
||||
private $definitions = [];
|
||||
/**
|
||||
* Cache of wildcard definitions.
|
||||
* @var array|null
|
||||
*/
|
||||
private $wildcardDefinitions;
|
||||
/**
|
||||
* @var DefinitionNormalizer
|
||||
*/
|
||||
private $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;
|
||||
$autowiring = $autowiring ?: new NoAutowiring();
|
||||
$this->normalizer = new DefinitionNormalizer($autowiring);
|
||||
}
|
||||
/**
|
||||
* @param array $definitions DI definitions in a PHP array indexed by the definition name.
|
||||
*/
|
||||
public function addDefinitions(array $definitions)
|
||||
{
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addDefinition(Definition $definition)
|
||||
{
|
||||
$this->definitions[$definition->getName()] = $definition;
|
||||
// Clear cache
|
||||
$this->wildcardDefinitions = null;
|
||||
}
|
||||
public function getDefinition(string $name)
|
||||
{
|
||||
// Look for the definition by name
|
||||
if (\array_key_exists($name, $this->definitions)) {
|
||||
$definition = $this->definitions[$name];
|
||||
$definition = $this->normalizer->normalizeRootDefinition($definition, $name);
|
||||
return $definition;
|
||||
}
|
||||
// Build the cache of wildcard definitions
|
||||
if ($this->wildcardDefinitions === null) {
|
||||
$this->wildcardDefinitions = [];
|
||||
foreach ($this->definitions as $key => $definition) {
|
||||
if (\strpos($key, self::WILDCARD) !== \false) {
|
||||
$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);
|
||||
$definition = $this->normalizer->normalizeRootDefinition($definition, $name, $matches);
|
||||
return $definition;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function getDefinitions() : array
|
||||
{
|
||||
// Return all definitions except wildcard definitions
|
||||
$definitions = [];
|
||||
foreach ($this->definitions as $key => $definition) {
|
||||
if (\strpos($key, self::WILDCARD) === \false) {
|
||||
$definitions[$key] = $definition;
|
||||
}
|
||||
}
|
||||
return $definitions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Source;
|
||||
|
||||
/**
|
||||
* Reads DI definitions from a file returning a PHP array.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class DefinitionFile extends DefinitionArray
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $initialized = \false;
|
||||
/**
|
||||
* File containing definitions, or null if the definitions are given as a PHP array.
|
||||
* @var string|null
|
||||
*/
|
||||
private $file;
|
||||
/**
|
||||
* @param string $file File in which the definitions are returned as an array.
|
||||
*/
|
||||
public function __construct($file, Autowiring $autowiring = null)
|
||||
{
|
||||
// Lazy-loading to improve performances
|
||||
$this->file = $file;
|
||||
parent::__construct([], $autowiring);
|
||||
}
|
||||
public function getDefinition(string $name)
|
||||
{
|
||||
$this->initialize();
|
||||
return parent::getDefinition($name);
|
||||
}
|
||||
public function getDefinitions() : array
|
||||
{
|
||||
$this->initialize();
|
||||
return parent::getDefinitions();
|
||||
}
|
||||
/**
|
||||
* Lazy-loading of the definitions.
|
||||
*/
|
||||
private function initialize()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Source;
|
||||
|
||||
use ElementorProDeps\DI\Definition\ArrayDefinition;
|
||||
use ElementorProDeps\DI\Definition\AutowireDefinition;
|
||||
use ElementorProDeps\DI\Definition\DecoratorDefinition;
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\DI\Definition\FactoryDefinition;
|
||||
use ElementorProDeps\DI\Definition\Helper\DefinitionHelper;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
use ElementorProDeps\DI\Definition\ValueDefinition;
|
||||
/**
|
||||
* Turns raw definitions/definition helpers into definitions ready
|
||||
* to be resolved or compiled.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class DefinitionNormalizer
|
||||
{
|
||||
/**
|
||||
* @var Autowiring
|
||||
*/
|
||||
private $autowiring;
|
||||
public function __construct(Autowiring $autowiring)
|
||||
{
|
||||
$this->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 mixed $definition
|
||||
* @param string $name The definition name.
|
||||
* @param string[] $wildcardsReplacements Replacements for wildcard definitions.
|
||||
*
|
||||
* @throws InvalidDefinition
|
||||
*/
|
||||
public function normalizeRootDefinition($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) {
|
||||
$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.
|
||||
*
|
||||
* @param mixed $definition
|
||||
* @return mixed
|
||||
*
|
||||
* @throws InvalidDefinition
|
||||
*/
|
||||
public function normalizeNestedDefinition($definition)
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Source;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\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.
|
||||
* @return Definition|null
|
||||
*/
|
||||
public function getDefinition(string $name);
|
||||
/**
|
||||
* @return Definition[] Definitions indexed by their name.
|
||||
*/
|
||||
public function getDefinitions() : array;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Source;
|
||||
|
||||
use ElementorProDeps\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);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Source;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Exception\InvalidDefinition;
|
||||
use ElementorProDeps\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)
|
||||
{
|
||||
throw new InvalidDefinition(\sprintf('Cannot autowire entry "%s" because autowiring is disabled', $name));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Source;
|
||||
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition\MethodInjection;
|
||||
use ElementorProDeps\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)
|
||||
{
|
||||
$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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorProDeps\DI\Definition\Source;
|
||||
|
||||
use ElementorProDeps\DI\Definition\AutowireDefinition;
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\ObjectDefinition;
|
||||
/**
|
||||
* Decorator that caches another definition source.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class SourceCache implements DefinitionSource, MutableDefinitionSource
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const CACHE_KEY = 'php-di.definitions.';
|
||||
/**
|
||||
* @var DefinitionSource
|
||||
*/
|
||||
private $cachedSource;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $cacheNamespace;
|
||||
public function __construct(DefinitionSource $cachedSource, string $cacheNamespace = '')
|
||||
{
|
||||
$this->cachedSource = $cachedSource;
|
||||
$this->cacheNamespace = $cacheNamespace;
|
||||
}
|
||||
public function getDefinition(string $name)
|
||||
{
|
||||
$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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition\Source;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\ExtendsPreviousDefinition;
|
||||
/**
|
||||
* Manages a chain of other definition sources.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class SourceChain implements DefinitionSource, MutableDefinitionSource
|
||||
{
|
||||
/**
|
||||
* @var DefinitionSource[]
|
||||
*/
|
||||
private $sources;
|
||||
/**
|
||||
* @var DefinitionSource
|
||||
*/
|
||||
private $rootSource;
|
||||
/**
|
||||
* @var MutableDefinitionSource|null
|
||||
*/
|
||||
private $mutableSource;
|
||||
/**
|
||||
* @param DefinitionSource[] $sources
|
||||
*/
|
||||
public function __construct(array $sources)
|
||||
{
|
||||
// We want a numerically indexed array to ease the traversal later
|
||||
$this->sources = \array_values($sources);
|
||||
$this->rootSource = $this;
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
$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
|
||||
{
|
||||
$names = [];
|
||||
foreach ($this->sources as $source) {
|
||||
$names = \array_merge($names, $source->getDefinitions());
|
||||
}
|
||||
$names = \array_keys($names);
|
||||
$definitions = \array_combine($names, \array_map(function (string $name) {
|
||||
return $this->getDefinition($name);
|
||||
}, $names));
|
||||
return $definitions;
|
||||
}
|
||||
public function addDefinition(Definition $definition)
|
||||
{
|
||||
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)
|
||||
{
|
||||
$this->mutableSource = $mutableSource;
|
||||
\array_unshift($this->sources, $mutableSource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition;
|
||||
|
||||
use ElementorProDeps\DI\DependencyException;
|
||||
use ElementorProDeps\Psr\Container\ContainerInterface;
|
||||
use ElementorProDeps\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.
|
||||
* @var string
|
||||
*/
|
||||
private $name = '';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $expression;
|
||||
public function __construct(string $expression)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
}
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
public function setName(string $name)
|
||||
{
|
||||
$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)
|
||||
{
|
||||
// no nested definitions
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Definition;
|
||||
|
||||
use ElementorProDeps\Psr\Container\ContainerInterface;
|
||||
/**
|
||||
* Definition of a value for dependency injection.
|
||||
*
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class ValueDefinition implements Definition, SelfResolvingDefinition
|
||||
{
|
||||
/**
|
||||
* Entry name.
|
||||
* @var string
|
||||
*/
|
||||
private $name = '';
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $value;
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
public function resolve(ContainerInterface $container)
|
||||
{
|
||||
return $this->getValue();
|
||||
}
|
||||
public function isResolvable(ContainerInterface $container) : bool
|
||||
{
|
||||
return \true;
|
||||
}
|
||||
public function replaceNestedDefinitions(callable $replacer)
|
||||
{
|
||||
// no nested definitions
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
return \sprintf('Value (%s)', \var_export($this->value, \true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI;
|
||||
|
||||
use ElementorProDeps\Psr\Container\ContainerExceptionInterface;
|
||||
/**
|
||||
* Exception for the Container.
|
||||
*/
|
||||
class DependencyException extends \Exception implements ContainerExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\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.
|
||||
* @return mixed
|
||||
*/
|
||||
public function make($name, array $parameters = []);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Invoker;
|
||||
|
||||
use ElementorProDeps\DI\Definition\Definition;
|
||||
use ElementorProDeps\DI\Definition\Helper\DefinitionHelper;
|
||||
use ElementorProDeps\DI\Definition\Resolver\DefinitionResolver;
|
||||
use ElementorProDeps\Invoker\ParameterResolver\ParameterResolver;
|
||||
use ReflectionFunctionAbstract;
|
||||
/**
|
||||
* Resolves callable parameters using definitions.
|
||||
*
|
||||
* @since 5.0
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class DefinitionParameterResolver implements ParameterResolver
|
||||
{
|
||||
/**
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $definitionResolver;
|
||||
public function __construct(DefinitionResolver $definitionResolver)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Invoker;
|
||||
|
||||
use ElementorProDeps\Invoker\ParameterResolver\ParameterResolver;
|
||||
use ElementorProDeps\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
|
||||
{
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
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 === '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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI;
|
||||
|
||||
use ElementorProDeps\Psr\Container\NotFoundExceptionInterface;
|
||||
/**
|
||||
* Exception thrown when a class or a value is not found in the container.
|
||||
*/
|
||||
class NotFoundException extends \Exception implements NotFoundExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI\Proxy;
|
||||
|
||||
use ElementorProDeps\ProxyManager\Configuration;
|
||||
use ElementorProDeps\ProxyManager\Factory\LazyLoadingValueHolderFactory;
|
||||
use ElementorProDeps\ProxyManager\FileLocator\FileLocator;
|
||||
use ElementorProDeps\ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
|
||||
use ElementorProDeps\ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy;
|
||||
use ElementorProDeps\ProxyManager\Proxy\LazyLoadingInterface;
|
||||
/**
|
||||
* Creates proxy classes.
|
||||
*
|
||||
* Wraps Ocramius/ProxyManager LazyLoadingValueHolderFactory.
|
||||
*
|
||||
* @see \ProxyManager\Factory\LazyLoadingValueHolderFactory
|
||||
*
|
||||
* @since 5.0
|
||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||
*/
|
||||
class ProxyFactory
|
||||
{
|
||||
/**
|
||||
* If true, write the proxies to disk to improve performances.
|
||||
* @var bool
|
||||
*/
|
||||
private $writeProxiesToFile;
|
||||
/**
|
||||
* Directory where to write the proxies (if $writeProxiesToFile is enabled).
|
||||
* @var string|null
|
||||
*/
|
||||
private $proxyDirectory;
|
||||
/**
|
||||
* @var LazyLoadingValueHolderFactory|null
|
||||
*/
|
||||
private $proxyManager;
|
||||
public function __construct(bool $writeProxiesToFile = \false, string $proxyDirectory = null)
|
||||
{
|
||||
$this->writeProxiesToFile = $writeProxiesToFile;
|
||||
$this->proxyDirectory = $proxyDirectory;
|
||||
}
|
||||
/**
|
||||
* Creates a new lazy proxy instance of the given class with
|
||||
* the given initializer.
|
||||
*
|
||||
* @param string $className name of the class to be proxied
|
||||
* @param \Closure $initializer initializer to be passed to the proxy
|
||||
*/
|
||||
public function createProxy(string $className, \Closure $initializer) : LazyLoadingInterface
|
||||
{
|
||||
$this->createProxyManager();
|
||||
return $this->proxyManager->createProxy($className, $initializer);
|
||||
}
|
||||
/**
|
||||
* Generates and writes the proxy class to file.
|
||||
*
|
||||
* @param string $className name of the class to be proxied
|
||||
*/
|
||||
public function generateProxyClass(string $className)
|
||||
{
|
||||
// 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->writeProxiesToFile) {
|
||||
$this->createProxyManager();
|
||||
$this->createProxy($className, function () {
|
||||
});
|
||||
}
|
||||
}
|
||||
private function createProxyManager()
|
||||
{
|
||||
if ($this->proxyManager !== null) {
|
||||
return;
|
||||
}
|
||||
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->writeProxiesToFile) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\DI;
|
||||
|
||||
use ElementorProDeps\DI\Definition\ArrayDefinitionExtension;
|
||||
use ElementorProDeps\DI\Definition\EnvironmentVariableDefinition;
|
||||
use ElementorProDeps\DI\Definition\Helper\AutowireDefinitionHelper;
|
||||
use ElementorProDeps\DI\Definition\Helper\CreateDefinitionHelper;
|
||||
use ElementorProDeps\DI\Definition\Helper\FactoryDefinitionHelper;
|
||||
use ElementorProDeps\DI\Definition\Reference;
|
||||
use ElementorProDeps\DI\Definition\StringDefinition;
|
||||
use ElementorProDeps\DI\Definition\ValueDefinition;
|
||||
if (!\function_exists('ElementorProDeps\\DI\\value')) {
|
||||
/**
|
||||
* Helper for defining a value.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
function value($value) : ValueDefinition
|
||||
{
|
||||
return new ValueDefinition($value);
|
||||
}
|
||||
}
|
||||
if (!\function_exists('ElementorProDeps\\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('ElementorProDeps\\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('ElementorProDeps\\DI\\factory')) {
|
||||
/**
|
||||
* Helper for defining a container entry using a factory function/callable.
|
||||
*
|
||||
* @param callable $factory The factory is a callable that takes the container as parameter
|
||||
* and returns the value to register in the container.
|
||||
*/
|
||||
function factory($factory) : FactoryDefinitionHelper
|
||||
{
|
||||
return new FactoryDefinitionHelper($factory);
|
||||
}
|
||||
}
|
||||
if (!\function_exists('ElementorProDeps\\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) : FactoryDefinitionHelper
|
||||
{
|
||||
return new FactoryDefinitionHelper($callable, \true);
|
||||
}
|
||||
}
|
||||
if (!\function_exists('ElementorProDeps\\DI\\get')) {
|
||||
/**
|
||||
* Helper for referencing another container entry in an object definition.
|
||||
*/
|
||||
function get(string $entryName) : Reference
|
||||
{
|
||||
return new Reference($entryName);
|
||||
}
|
||||
}
|
||||
if (!\function_exists('ElementorProDeps\\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, $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('ElementorProDeps\\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('ElementorProDeps\\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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
Copyright (C) 2019 Matthieu Napoli
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "php-di\/phpdoc-reader",
|
||||
"type": "library",
|
||||
"description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)",
|
||||
"keywords": [
|
||||
"phpdoc",
|
||||
"reflection"
|
||||
],
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ElementorProDeps\\PhpDocReader\\": "src\/PhpDocReader"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"ElementorProDeps\\UnitTest\\PhpDocReader\\": "tests\/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit\/phpunit": "^8.5|^9.0",
|
||||
"mnapoli\/hard-mode": "~0.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\PhpDocReader;
|
||||
|
||||
/**
|
||||
* We stumbled upon an invalid class/property/method annotation.
|
||||
*/
|
||||
class AnnotationException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\PhpDocReader;
|
||||
|
||||
use ElementorProDeps\PhpDocReader\PhpParser\UseStatementParser;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use ReflectionParameter;
|
||||
use ReflectionProperty;
|
||||
use Reflector;
|
||||
/**
|
||||
* PhpDoc reader
|
||||
*/
|
||||
class PhpDocReader
|
||||
{
|
||||
/** @var UseStatementParser */
|
||||
private $parser;
|
||||
private const PRIMITIVE_TYPES = ['bool' => 'bool', 'boolean' => 'bool', 'string' => 'string', 'int' => 'int', 'integer' => 'int', 'float' => 'float', 'double' => 'float', 'array' => 'array', 'object' => 'object', 'callable' => 'callable', 'resource' => 'resource', 'mixed' => 'mixed', 'iterable' => 'iterable'];
|
||||
/** @var bool */
|
||||
private $ignorePhpDocErrors;
|
||||
/**
|
||||
* @param bool $ignorePhpDocErrors Enable or disable throwing errors when PhpDoc errors occur (when parsing annotations).
|
||||
*/
|
||||
public function __construct(bool $ignorePhpDocErrors = \false)
|
||||
{
|
||||
$this->parser = new UseStatementParser();
|
||||
$this->ignorePhpDocErrors = $ignorePhpDocErrors;
|
||||
}
|
||||
/**
|
||||
* Parse the docblock of the property to get the type (class or primitive type) of the var annotation.
|
||||
*
|
||||
* @return string|null Type of the property (content of var annotation)
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public function getPropertyType(ReflectionProperty $property) : ?string
|
||||
{
|
||||
return $this->readPropertyType($property, \true);
|
||||
}
|
||||
/**
|
||||
* Parse the docblock of the property to get the class of the var annotation.
|
||||
*
|
||||
* @return string|null Type of the property (content of var annotation)
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public function getPropertyClass(ReflectionProperty $property) : ?string
|
||||
{
|
||||
return $this->readPropertyType($property, \false);
|
||||
}
|
||||
private function readPropertyType(ReflectionProperty $property, bool $allowPrimitiveTypes) : ?string
|
||||
{
|
||||
// Get the content of the @var annotation
|
||||
$docComment = $property->getDocComment();
|
||||
if (!$docComment) {
|
||||
return null;
|
||||
}
|
||||
if (\preg_match('/@var\\s+([^\\s]+)/', $docComment, $matches)) {
|
||||
[, $type] = $matches;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
// Ignore primitive types
|
||||
if (isset(self::PRIMITIVE_TYPES[$type])) {
|
||||
if ($allowPrimitiveTypes) {
|
||||
return self::PRIMITIVE_TYPES[$type];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Ignore types containing special characters ([], <> ...)
|
||||
if (!\preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
|
||||
return null;
|
||||
}
|
||||
$class = $property->getDeclaringClass();
|
||||
// If the class name is not fully qualified (i.e. doesn't start with a \)
|
||||
if ($type[0] !== '\\') {
|
||||
// Try to resolve the FQN using the class context
|
||||
$resolvedType = $this->tryResolveFqn($type, $class, $property);
|
||||
if (!$resolvedType && !$this->ignorePhpDocErrors) {
|
||||
throw new AnnotationException(\sprintf('The @var annotation on %s::%s contains a non existent class "%s". ' . 'Did you maybe forget to add a "use" statement for this annotation?', $class->name, $property->getName(), $type));
|
||||
}
|
||||
$type = $resolvedType;
|
||||
}
|
||||
if (!$this->ignorePhpDocErrors && !$this->classExists($type)) {
|
||||
throw new AnnotationException(\sprintf('The @var annotation on %s::%s contains a non existent class "%s"', $class->name, $property->getName(), $type));
|
||||
}
|
||||
// Remove the leading \ (FQN shouldn't contain it)
|
||||
$type = \is_string($type) ? \ltrim($type, '\\') : null;
|
||||
return $type;
|
||||
}
|
||||
/**
|
||||
* Parse the docblock of the property to get the type (class or primitive type) of the param annotation.
|
||||
*
|
||||
* @return string|null Type of the property (content of var annotation)
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public function getParameterType(ReflectionParameter $parameter) : ?string
|
||||
{
|
||||
return $this->readParameterClass($parameter, \true);
|
||||
}
|
||||
/**
|
||||
* Parse the docblock of the property to get the class of the param annotation.
|
||||
*
|
||||
* @return string|null Type of the property (content of var annotation)
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public function getParameterClass(ReflectionParameter $parameter) : ?string
|
||||
{
|
||||
return $this->readParameterClass($parameter, \false);
|
||||
}
|
||||
private function readParameterClass(ReflectionParameter $parameter, bool $allowPrimitiveTypes) : ?string
|
||||
{
|
||||
// Use reflection
|
||||
$parameterType = $parameter->getType();
|
||||
if ($parameterType && $parameterType instanceof \ReflectionNamedType && !$parameterType->isBuiltin()) {
|
||||
return $parameterType->getName();
|
||||
}
|
||||
$parameterName = $parameter->name;
|
||||
// Get the content of the @param annotation
|
||||
$method = $parameter->getDeclaringFunction();
|
||||
$docComment = $method->getDocComment();
|
||||
if (!$docComment) {
|
||||
return null;
|
||||
}
|
||||
if (\preg_match('/@param\\s+([^\\s]+)\\s+\\$' . $parameterName . '/', $docComment, $matches)) {
|
||||
[, $type] = $matches;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
// Ignore primitive types
|
||||
if (isset(self::PRIMITIVE_TYPES[$type])) {
|
||||
if ($allowPrimitiveTypes) {
|
||||
return self::PRIMITIVE_TYPES[$type];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Ignore types containing special characters ([], <> ...)
|
||||
if (!\preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
|
||||
return null;
|
||||
}
|
||||
$class = $parameter->getDeclaringClass();
|
||||
// If the class name is not fully qualified (i.e. doesn't start with a \)
|
||||
if ($type[0] !== '\\') {
|
||||
// Try to resolve the FQN using the class context
|
||||
$resolvedType = $this->tryResolveFqn($type, $class, $parameter);
|
||||
if (!$resolvedType && !$this->ignorePhpDocErrors) {
|
||||
throw new AnnotationException(\sprintf('The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s". ' . 'Did you maybe forget to add a "use" statement for this annotation?', $parameterName, $class->name, $method->name, $type));
|
||||
}
|
||||
$type = $resolvedType;
|
||||
}
|
||||
if (!$this->ignorePhpDocErrors && !$this->classExists($type)) {
|
||||
throw new AnnotationException(\sprintf('The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s"', $parameterName, $class->name, $method->name, $type));
|
||||
}
|
||||
// Remove the leading \ (FQN shouldn't contain it)
|
||||
$type = \is_string($type) ? \ltrim($type, '\\') : null;
|
||||
return $type;
|
||||
}
|
||||
/**
|
||||
* Attempts to resolve the FQN of the provided $type based on the $class and $member context.
|
||||
*
|
||||
* @return string|null Fully qualified name of the type, or null if it could not be resolved
|
||||
*/
|
||||
private function tryResolveFqn(string $type, ReflectionClass $class, Reflector $member) : ?string
|
||||
{
|
||||
$alias = ($pos = \strpos($type, '\\')) === \false ? $type : \substr($type, 0, $pos);
|
||||
$loweredAlias = \strtolower($alias);
|
||||
// Retrieve "use" statements
|
||||
$uses = $this->parser->parseUseStatements($class);
|
||||
if (isset($uses[$loweredAlias])) {
|
||||
// Imported classes
|
||||
if ($pos !== \false) {
|
||||
return $uses[$loweredAlias] . \substr($type, $pos);
|
||||
}
|
||||
return $uses[$loweredAlias];
|
||||
}
|
||||
if ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
|
||||
return $class->getNamespaceName() . '\\' . $type;
|
||||
}
|
||||
if (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
|
||||
// Class namespace
|
||||
return $uses['__NAMESPACE__'] . '\\' . $type;
|
||||
}
|
||||
if ($this->classExists($type)) {
|
||||
// No namespace
|
||||
return $type;
|
||||
}
|
||||
// If all fail, try resolving through related traits
|
||||
return $this->tryResolveFqnInTraits($type, $class, $member);
|
||||
}
|
||||
/**
|
||||
* Attempts to resolve the FQN of the provided $type based on the $class and $member context, specifically searching
|
||||
* through the traits that are used by the provided $class.
|
||||
*
|
||||
* @return string|null Fully qualified name of the type, or null if it could not be resolved
|
||||
*/
|
||||
private function tryResolveFqnInTraits(string $type, ReflectionClass $class, Reflector $member) : ?string
|
||||
{
|
||||
/** @var ReflectionClass[] $traits */
|
||||
$traits = [];
|
||||
// Get traits for the class and its parents
|
||||
while ($class) {
|
||||
$traits = \array_merge($traits, $class->getTraits());
|
||||
$class = $class->getParentClass();
|
||||
}
|
||||
foreach ($traits as $trait) {
|
||||
// Eliminate traits that don't have the property/method/parameter
|
||||
if ($member instanceof ReflectionProperty && !$trait->hasProperty($member->name)) {
|
||||
continue;
|
||||
}
|
||||
if ($member instanceof ReflectionMethod && !$trait->hasMethod($member->name)) {
|
||||
continue;
|
||||
}
|
||||
if ($member instanceof ReflectionParameter && !$trait->hasMethod($member->getDeclaringFunction()->name)) {
|
||||
continue;
|
||||
}
|
||||
// Run the resolver again with the ReflectionClass instance for the trait
|
||||
$resolvedType = $this->tryResolveFqn($type, $trait, $member);
|
||||
if ($resolvedType) {
|
||||
return $resolvedType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private function classExists(string $class) : bool
|
||||
{
|
||||
return \class_exists($class) || \interface_exists($class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\PhpDocReader\PhpParser;
|
||||
|
||||
/**
|
||||
* Parses a file for namespaces/use/class declarations.
|
||||
*
|
||||
* Class taken and adapted from doctrine/annotations to avoid pulling the whole package.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Christian Kaps <christian.kaps@mohiva.com>
|
||||
*/
|
||||
class TokenParser
|
||||
{
|
||||
/**
|
||||
* The token list.
|
||||
*
|
||||
* @var list<mixed[]>
|
||||
*/
|
||||
private $tokens;
|
||||
/**
|
||||
* The number of tokens.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $numTokens;
|
||||
/**
|
||||
* The current array pointer.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $pointer = 0;
|
||||
/**
|
||||
* @param string $contents
|
||||
*/
|
||||
public function __construct($contents)
|
||||
{
|
||||
$this->tokens = \token_get_all($contents);
|
||||
// The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it
|
||||
// saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored
|
||||
// doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a
|
||||
// docblock. If the first thing in the file is a class without a doc block this would cause calls to
|
||||
// getDocBlock() on said class to return our long lost doc_comment. Argh.
|
||||
// To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least
|
||||
// it's harmless to us.
|
||||
\token_get_all("<?php\n/**\n *\n */");
|
||||
$this->numTokens = \count($this->tokens);
|
||||
}
|
||||
/**
|
||||
* Gets all use statements.
|
||||
*
|
||||
* @param string $namespaceName The namespace name of the reflected class.
|
||||
*
|
||||
* @return array<string, string> A list with all found use statements.
|
||||
*/
|
||||
public function parseUseStatements($namespaceName)
|
||||
{
|
||||
$statements = [];
|
||||
while ($token = $this->next()) {
|
||||
if ($token[0] === \T_USE) {
|
||||
$statements = \array_merge($statements, $this->parseUseStatement());
|
||||
continue;
|
||||
}
|
||||
if ($token[0] !== \T_NAMESPACE || $this->parseNamespace() !== $namespaceName) {
|
||||
continue;
|
||||
}
|
||||
// Get fresh array for new namespace. This is to prevent the parser to collect the use statements
|
||||
// for a previous namespace with the same name. This is the case if a namespace is defined twice
|
||||
// or if a namespace with the same name is commented out.
|
||||
$statements = [];
|
||||
}
|
||||
return $statements;
|
||||
}
|
||||
/**
|
||||
* Gets the next non whitespace and non comment token.
|
||||
*
|
||||
* @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
|
||||
* If FALSE then only whitespace and normal comments are skipped.
|
||||
*
|
||||
* @return mixed[]|string|null The token if exists, null otherwise.
|
||||
*/
|
||||
private function next($docCommentIsComment = \true)
|
||||
{
|
||||
for ($i = $this->pointer; $i < $this->numTokens; $i++) {
|
||||
$this->pointer++;
|
||||
if ($this->tokens[$i][0] === \T_WHITESPACE || $this->tokens[$i][0] === \T_COMMENT || $docCommentIsComment && $this->tokens[$i][0] === \T_DOC_COMMENT) {
|
||||
continue;
|
||||
}
|
||||
return $this->tokens[$i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Parses a single use statement.
|
||||
*
|
||||
* @return array<string, string> A list with all found class names for a use statement.
|
||||
*/
|
||||
private function parseUseStatement()
|
||||
{
|
||||
$groupRoot = '';
|
||||
$class = '';
|
||||
$alias = '';
|
||||
$statements = [];
|
||||
$explicitAlias = \false;
|
||||
while ($token = $this->next()) {
|
||||
if (!$explicitAlias && $token[0] === \T_STRING) {
|
||||
$class .= $token[1];
|
||||
$alias = $token[1];
|
||||
} elseif ($explicitAlias && $token[0] === \T_STRING) {
|
||||
$alias = $token[1];
|
||||
} elseif (\PHP_VERSION_ID >= 80000 && ($token[0] === \T_NAME_QUALIFIED || $token[0] === \T_NAME_FULLY_QUALIFIED)) {
|
||||
$class .= $token[1];
|
||||
$classSplit = \explode('\\', $token[1]);
|
||||
$alias = $classSplit[\count($classSplit) - 1];
|
||||
} elseif ($token[0] === \T_NS_SEPARATOR) {
|
||||
$class .= '\\';
|
||||
$alias = '';
|
||||
} elseif ($token[0] === \T_AS) {
|
||||
$explicitAlias = \true;
|
||||
$alias = '';
|
||||
} elseif ($token === ',') {
|
||||
$statements[\strtolower($alias)] = $groupRoot . $class;
|
||||
$class = '';
|
||||
$alias = '';
|
||||
$explicitAlias = \false;
|
||||
} elseif ($token === ';') {
|
||||
$statements[\strtolower($alias)] = $groupRoot . $class;
|
||||
break;
|
||||
} elseif ($token === '{') {
|
||||
$groupRoot = $class;
|
||||
$class = '';
|
||||
} elseif ($token === '}') {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $statements;
|
||||
}
|
||||
/**
|
||||
* Gets the namespace.
|
||||
*
|
||||
* @return string The found namespace.
|
||||
*/
|
||||
private function parseNamespace()
|
||||
{
|
||||
$name = '';
|
||||
while (($token = $this->next()) && ($token[0] === \T_STRING || $token[0] === \T_NS_SEPARATOR || \PHP_VERSION_ID >= 80000 && ($token[0] === \T_NAME_QUALIFIED || $token[0] === \T_NAME_FULLY_QUALIFIED))) {
|
||||
$name .= $token[1];
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace ElementorProDeps\PhpDocReader\PhpParser;
|
||||
|
||||
use SplFileObject;
|
||||
/**
|
||||
* Parses a file for "use" declarations.
|
||||
*
|
||||
* Class taken and adapted from doctrine/annotations to avoid pulling the whole package.
|
||||
*
|
||||
* Authors: Fabien Potencier <fabien@symfony.com> and Christian Kaps <christian.kaps@mohiva.com>
|
||||
*/
|
||||
class UseStatementParser
|
||||
{
|
||||
/**
|
||||
* @return array A list with use statements in the form (Alias => FQN).
|
||||
*/
|
||||
public function parseUseStatements(\ReflectionClass $class) : array
|
||||
{
|
||||
$filename = $class->getFilename();
|
||||
if ($filename === \false) {
|
||||
return [];
|
||||
}
|
||||
$content = $this->getFileContent($filename, $class->getStartLine());
|
||||
if ($content === null) {
|
||||
return [];
|
||||
}
|
||||
$namespace = \preg_quote($class->getNamespaceName(), '/');
|
||||
$content = \preg_replace('/^.*?(\\bnamespace\\s+' . $namespace . '\\s*[;{].*)$/s', '\\1', $content);
|
||||
$tokenizer = new TokenParser('<?php ' . $content);
|
||||
return $tokenizer->parseUseStatements($class->getNamespaceName());
|
||||
}
|
||||
/**
|
||||
* Gets the content of the file right up to the given line number.
|
||||
*
|
||||
* @param string $filename The name of the file to load.
|
||||
* @param int $lineNumber The number of lines to read from file.
|
||||
*/
|
||||
private function getFileContent(string $filename, int $lineNumber) : string
|
||||
{
|
||||
if (!\is_file($filename)) {
|
||||
throw new \RuntimeException("Unable to read file {$filename}");
|
||||
}
|
||||
$content = '';
|
||||
$lineCnt = 0;
|
||||
$file = new SplFileObject($filename);
|
||||
while (!$file->eof()) {
|
||||
if ($lineCnt++ === $lineNumber) {
|
||||
break;
|
||||
}
|
||||
$content .= $file->fgets();
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user