This commit is contained in:
2025-09-03 15:57:56 +02:00
parent a3f5b5a0cf
commit bd32f43c9e
441 changed files with 54487 additions and 256 deletions

21
api/vendor/php-di/invoker/LICENSE vendored Normal file
View File

@ -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.

234
api/vendor/php-di/invoker/README.md vendored Normal file
View File

@ -0,0 +1,234 @@
# Invoker
Generic and extensible callable invoker.
[![CI](https://github.com/PHP-DI/Invoker/actions/workflows/ci.yml/badge.svg)](https://github.com/PHP-DI/Invoker/actions/workflows/ci.yml)
[![Latest Version](https://img.shields.io/github/release/PHP-DI/invoker.svg?style=flat-square)](https://packagist.org/packages/PHP-DI/invoker)
[![Total Downloads](https://img.shields.io/packagist/dt/php-di/invoker.svg?style=flat-square)](https://packagist.org/packages/php-di/invoker)
## Why?
Who doesn't need an over-engineered `call_user_func()`?
### Named parameters
Does this [Silex](https://github.com/silexphp/Silex#readme) example look familiar:
```php
$app->get('/project/{project}/issue/{issue}', function ($project, $issue) {
// ...
});
```
Or this command defined with [Silly](https://github.com/mnapoli/silly#usage):
```php
$app->command('greet [name] [--yell]', function ($name, $yell) {
// ...
});
```
Same pattern in [Slim](https://www.slimframework.com):
```php
$app->get('/hello/:name', function ($name) {
// ...
});
```
You get the point. These frameworks invoke the controller/command/handler using something akin to named parameters: whatever the order of the parameters, they are matched by their name.
**This library allows to invoke callables with named parameters in a generic and extensible way.**
### Dependency injection
Anyone familiar with AngularJS is familiar with how dependency injection is performed:
```js
angular.controller('MyController', ['dep1', 'dep2', function(dep1, dep2) {
// ...
}]);
```
In PHP we find this pattern again in some frameworks and DI containers with partial to full support. For example in Silex you can type-hint the application to get it injected, but it only works with `Silex\Application`:
```php
$app->get('/hello/{name}', function (Silex\Application $app, $name) {
// ...
});
```
In Silly, it only works with `OutputInterface` to inject the application output:
```php
$app->command('greet [name]', function ($name, OutputInterface $output) {
// ...
});
```
[PHP-DI](https://php-di.org/doc/container.html) provides a way to invoke a callable and resolve all dependencies from the container using type-hints:
```php
$container->call(function (Logger $logger, EntityManager $em) {
// ...
});
```
**This library provides clear extension points to let frameworks implement any kind of dependency injection support they want.**
### TL/DR
In short, this library is meant to be a base building block for calling a function with named parameters and/or dependency injection.
## Installation
```sh
$ composer require PHP-DI/invoker
```
## Usage
### Default behavior
By default the `Invoker` can call using named parameters:
```php
$invoker = new Invoker\Invoker;
$invoker->call(function () {
echo 'Hello world!';
});
// Simple parameter array
$invoker->call(function ($name) {
echo 'Hello ' . $name;
}, ['John']);
// Named parameters
$invoker->call(function ($name) {
echo 'Hello ' . $name;
}, [
'name' => 'John'
]);
// Use the default value
$invoker->call(function ($name = 'world') {
echo 'Hello ' . $name;
});
// Invoke any PHP callable
$invoker->call(['MyClass', 'myStaticMethod']);
// Using Class::method syntax
$invoker->call('MyClass::myStaticMethod');
```
Dependency injection in parameters is supported but needs to be configured with your container. Read on or jump to [*Built-in support for dependency injection*](#built-in-support-for-dependency-injection) if you are impatient.
Additionally, callables can also be resolved from your container. Read on or jump to [*Resolving callables from a container*](#resolving-callables-from-a-container) if you are impatient.
### Parameter resolvers
Extending the behavior of the `Invoker` is easy and is done by implementing a [`ParameterResolver`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/ParameterResolver.php).
This is explained in details the [Parameter resolvers documentation](doc/parameter-resolvers.md).
#### Built-in support for dependency injection
Rather than have you re-implement support for dependency injection with different containers every time, this package ships with 2 optional resolvers:
- [`TypeHintContainerResolver`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/Container/TypeHintContainerResolver.php)
This resolver will inject container entries by searching for the class name using the type-hint:
```php
$invoker->call(function (Psr\Logger\LoggerInterface $logger) {
// ...
});
```
In this example it will `->get('Psr\Logger\LoggerInterface')` from the container and inject it.
This resolver is only useful if you store objects in your container using the class (or interface) name. Silex or Symfony for example store services under a custom name (e.g. `twig`, `db`, etc.) instead of the class name: in that case use the resolver shown below.
- [`ParameterNameContainerResolver`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/Container/ParameterNameContainerResolver.php)
This resolver will inject container entries by searching for the name of the parameter:
```php
$invoker->call(function ($twig) {
// ...
});
```
In this example it will `->get('twig')` from the container and inject it.
These resolvers can work with any dependency injection container compliant with [PSR-11](http://www.php-fig.org/psr/psr-11/).
Setting up those resolvers is simple:
```php
// $container must be an instance of Psr\Container\ContainerInterface
$container = ...
$containerResolver = new TypeHintContainerResolver($container);
// or
$containerResolver = new ParameterNameContainerResolver($container);
$invoker = new Invoker\Invoker;
// Register it before all the other parameter resolvers
$invoker->getParameterResolver()->prependResolver($containerResolver);
```
You can also register both resolvers at the same time if you wish by prepending both. Implementing support for more tricky things is easy and up to you!
### Resolving callables from a container
The `Invoker` can be wired to your DI container to resolve the callables.
For example with an invokable class:
```php
class MyHandler
{
public function __invoke()
{
// ...
}
}
// By default this doesn't work: an instance of the class should be provided
$invoker->call('MyHandler');
// If we set up the container to use
$invoker = new Invoker\Invoker(null, $container);
// Now 'MyHandler' is resolved using the container!
$invoker->call('MyHandler');
```
The same works for a class method:
```php
class WelcomeController
{
public function home()
{
// ...
}
}
// By default this doesn't work: home() is not a static method
$invoker->call(['WelcomeController', 'home']);
// If we set up the container to use
$invoker = new Invoker\Invoker(null, $container);
// Now 'WelcomeController' is resolved using the container!
$invoker->call(['WelcomeController', 'home']);
// Alternatively we can use the Class::method syntax
$invoker->call('WelcomeController::home');
```
That feature can be used as the base building block for a framework's dispatcher.
Again, any [PSR-11](https://www.php-fig.org/psr/psr-11/) compliant container can be provided.

32
api/vendor/php-di/invoker/composer.json vendored Normal file
View File

@ -0,0 +1,32 @@
{
"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": {
"Invoker\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Invoker\\Test\\": "tests/"
}
},
"require": {
"php": ">=7.3",
"psr/container": "^1.0|^2.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0 || ^10 || ^11 || ^12",
"athletic/athletic": "~0.1.8",
"mnapoli/hard-mode": "~0.3.0"
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,56 @@
<?php declare(strict_types=1);
namespace Invoker\Reflection;
use Closure;
use 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)
));
}
}