Vendor lock

This commit is contained in:
2023-06-16 02:08:47 +00:00
parent 7933e70e90
commit 3351b92dd6
4099 changed files with 345789 additions and 0 deletions

15
vendor/php-di/invoker/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,15 @@
# Contributing
First of all, **thank you** for contributing!
Here are a few rules to follow in order to ease code reviews and merging:
- follow [PSR-1](http://www.php-fig.org/psr/1/) and [PSR-2](http://www.php-fig.org/psr/2/)
- run the test suite
- write (or update) unit tests when applicable
- write documentation for new features
- use [commit messages that make sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
One may ask you to [squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) too. This is used to "clean" your pull request before merging it (we don't want commits such as `fix tests`, `fix 2`, `fix 3`, etc.).
When creating your pull request on GitHub, please write a description which gives the context and/or explains why you are creating it.

21
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
vendor/php-di/invoker/README.md vendored Normal file
View File

@ -0,0 +1,234 @@
# Invoker
Generic and extensible callable invoker.
[![Build Status](https://img.shields.io/travis/PHP-DI/Invoker.svg?style=flat-square)](https://travis-ci.org/PHP-DI/Invoker)
[![Coverage Status](https://img.shields.io/coveralls/PHP-DI/Invoker/master.svg?style=flat-square)](https://coveralls.io/r/PHP-DI/Invoker?branch=master)
[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/PHP-DI/Invoker.svg?style=flat-square)](https://scrutinizer-ci.com/g/PHP-DI/Invoker/?branch=master)
[![Latest Version](https://img.shields.io/github/release/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](http://silex.sensiolabs.org) 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](http://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](http://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](http://www.php-fig.org/psr/psr-11/) compliant container can be provided.

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

@ -0,0 +1,25 @@
{
"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": {
"psr/container": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.5",
"athletic/athletic": "~0.1.8"
}
}

View File

@ -0,0 +1,109 @@
# 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):
```php
interface ParameterResolver
{
public function getParameters(
ReflectionFunctionAbstract $reflection,
array $providedParameters,
array $resolvedParameters
);
}
```
- `$providedParameters` contains the parameters provided by the user when calling `$invoker->call($callable, $parameters)`
- `$resolvedParameters` contains parameters that have already been resolved by other parameter resolvers
An `Invoker` can chain multiple parameter resolvers to mix behaviors, e.g. you can mix "named parameters" support with "dependency injection" support. This is why a `ParameterResolver` should skip parameters that are already resolved in `$resolvedParameters`.
Here is an implementation example for dumb dependency injection that creates a new instance of the classes type-hinted:
```php
class MyParameterResolver implements ParameterResolver
{
public function getParameters(
ReflectionFunctionAbstract $reflection,
array $providedParameters,
array $resolvedParameters
) {
foreach ($reflection->getParameters() as $index => $parameter) {
if (array_key_exists($index, $resolvedParameters)) {
// Skip already resolved parameters
continue;
}
$class = $parameter->getClass();
if ($class) {
$resolvedParameters[$index] = $class->newInstance();
}
}
return $resolvedParameters;
}
}
```
To use it:
```php
$invoker = new Invoker\Invoker(new MyParameterResolver);
$invoker->call(function (ArticleManager $articleManager) {
$articleManager->publishArticle('Hello world', 'This is the article content.');
});
```
A new instance of `ArticleManager` will be created by our parameter resolver.
## Chaining parameter resolvers
The fun starts to happen when we want to add support for many things:
- named parameters
- dependency injection for type-hinted parameters
- ...
This is where we should use the [`ResolverChain`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/ResolverChain.php). This resolver implements the [Chain of responsibility](http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern) design pattern.
For example the default chain is:
```php
$parameterResolver = new ResolverChain([
new NumericArrayResolver,
new AssociativeArrayResolver,
new DefaultValueResolver,
]);
```
It allows to support even the weirdest use cases like:
```php
$parameters = [];
// First parameter will receive "Welcome"
$parameters[] = 'Welcome';
// Parameter named "content" will receive "Hello world!"
$parameters['content'] = 'Hello world!';
// $published is not defined so it will use its default value
$invoker->call(function ($title, $content, $published = true) {
// ...
}, $parameters);
```
We can put our custom parameter resolver in the list and created a super-duper invoker that also supports basic dependency injection:
```php
$parameterResolver = new ResolverChain([
new MyParameterResolver, // Our resolver is at the top for highest priority
new NumericArrayResolver,
new AssociativeArrayResolver,
new DefaultValueResolver,
]);
$invoker = new Invoker\Invoker($parameterResolver);
```

View File

@ -0,0 +1,133 @@
<?php
namespace Invoker;
use Invoker\Exception\NotCallableException;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* Resolves a callable from a container.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
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
*/
public function resolve($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
* @throws NotCallableException
*/
private function resolveFromContainer($callable)
{
// Shortcut for a very common use case
if ($callable instanceof \Closure) {
return $callable;
}
$isStaticCallToNonStaticMethod = false;
// If it's already a callable there is nothing to do
if (is_callable($callable)) {
$isStaticCallToNonStaticMethod = $this->isStaticCallToNonStaticMethod($callable);
if (! $isStaticCallToNonStaticMethod) {
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;
}
if ($isStaticCallToNonStaticMethod) {
throw new NotCallableException(sprintf(
'Cannot call %s::%s() because %s() is not a static method and "%s" is not a container entry',
$callable[0],
$callable[1],
$callable[1],
$callable[0]
));
}
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
* @return bool
*/
private function isStaticCallToNonStaticMethod($callable)
{
if (is_array($callable) && is_string($callable[0])) {
list($class, $method) = $callable;
$reflection = new \ReflectionMethod($class, $method);
return ! $reflection->isStatic();
}
return false;
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Invoker\Exception;
/**
* Impossible to invoke the callable.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class InvocationException extends \Exception
{
}

View File

@ -0,0 +1,35 @@
<?php
namespace Invoker\Exception;
/**
* The given callable is not actually callable.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class NotCallableException extends InvocationException
{
/**
* @param string $value
* @param bool $containerEntry
* @return self
*/
public static function fromInvalidCallable($value, $containerEntry = false)
{
if (is_object($value)) {
$message = sprintf('Instance of %s is not a callable', get_class($value));
} elseif (is_array($value) && isset($value[0]) && isset($value[1])) {
$class = is_object($value[0]) ? get_class($value[0]) : $value[0];
$extra = method_exists($class, '__call') ? ' A __call() method exists but magic methods are not supported.' : '';
$message = sprintf('%s::%s() is not a callable.%s', $class, $value[1], $extra);
} else {
if ($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,12 @@
<?php
namespace Invoker\Exception;
/**
* Not enough parameters could be resolved to invoke the callable.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class NotEnoughParametersException extends InvocationException
{
}

122
vendor/php-di/invoker/src/Invoker.php vendored Normal file
View File

@ -0,0 +1,122 @@
<?php
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;
/**
* Invoke a callable.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
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 = array())
{
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, array());
// 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);
if (! empty($diff)) {
/** @var \ReflectionParameter $parameter */
$parameter = reset($diff);
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.
*
* @return ParameterResolver
*/
private function createParameterResolver()
{
return new ResolverChain(array(
new NumericArrayResolver,
new AssociativeArrayResolver,
new DefaultValueResolver,
));
}
/**
* @return ParameterResolver By default it's a ResolverChain
*/
public function getParameterResolver()
{
return $this->parameterResolver;
}
/**
* @return ContainerInterface|null
*/
public function getContainer()
{
return $this->container;
}
/**
* @return CallableResolver|null Returns null if no container was given in the constructor.
*/
public function getCallableResolver()
{
return $this->callableResolver;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Invoker;
use Invoker\Exception\InvocationException;
use Invoker\Exception\NotCallableException;
use Invoker\Exception\NotEnoughParametersException;
/**
* Invoke a callable.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface InvokerInterface
{
/**
* Call the given function using the given parameters.
*
* @param callable $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 = array());
}

View File

@ -0,0 +1,39 @@
<?php
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.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AssociativeArrayResolver implements ParameterResolver
{
public function getParameters(
ReflectionFunctionAbstract $reflection,
array $providedParameters,
array $resolvedParameters
) {
$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,51 @@
<?php
namespace Invoker\ParameterResolver\Container;
use Invoker\ParameterResolver\ParameterResolver;
use Psr\Container\ContainerInterface;
use ReflectionFunctionAbstract;
/**
* Inject entries from a DI container using the parameter names.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
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
) {
$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,51 @@
<?php
namespace Invoker\ParameterResolver\Container;
use Invoker\ParameterResolver\ParameterResolver;
use Psr\Container\ContainerInterface;
use ReflectionFunctionAbstract;
/**
* Inject entries from a DI container using the type-hints.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
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
) {
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (! empty($resolvedParameters)) {
$parameters = array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
$parameterClass = $parameter->getClass();
if ($parameterClass && $this->container->has($parameterClass->name)) {
$resolvedParameters[$index] = $this->container->get($parameterClass->name);
}
}
return $resolvedParameters;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Invoker\ParameterResolver;
use ReflectionException;
use ReflectionFunctionAbstract;
/**
* Finds the default value for a parameter, *if it exists*.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DefaultValueResolver implements ParameterResolver
{
public function getParameters(
ReflectionFunctionAbstract $reflection,
array $providedParameters,
array $resolvedParameters
) {
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (! empty($resolvedParameters)) {
$parameters = array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
/** @var \ReflectionParameter $parameter */
if ($parameter->isOptional()) {
try {
$resolvedParameters[$index] = $parameter->getDefaultValue();
} catch (ReflectionException $e) {
// Can't get default values from PHP internal classes and functions
}
}
}
return $resolvedParameters;
}
}

View File

@ -0,0 +1,39 @@
<?php
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.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class NumericArrayResolver implements ParameterResolver
{
public function getParameters(
ReflectionFunctionAbstract $reflection,
array $providedParameters,
array $resolvedParameters
) {
// 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,33 @@
<?php
namespace Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Resolves the parameters to use to call the callable.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
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,69 @@
<?php
namespace Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Dispatches the call to other resolvers until all parameters are resolved.
*
* Chain of responsibility pattern.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ResolverChain implements ParameterResolver
{
/**
* @var ParameterResolver[]
*/
private $resolvers = array();
public function __construct(array $resolvers = array())
{
$this->resolvers = $resolvers;
}
public function getParameters(
ReflectionFunctionAbstract $reflection,
array $providedParameters,
array $resolvedParameters
) {
$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.
*
* @param ParameterResolver $resolver
*/
public function appendResolver(ParameterResolver $resolver)
{
$this->resolvers[] = $resolver;
}
/**
* Insert a parameter resolver before the ones already registered.
*
* @param ParameterResolver $resolver
*/
public function prependResolver(ParameterResolver $resolver)
{
array_unshift($this->resolvers, $resolver);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Invoker\ParameterResolver;
use Invoker\ParameterResolver\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Inject entries using type-hints.
*
* Tries to match type-hints with the parameters provided.
*
* @author Felix Becker <f.becker@outlook.com>
*/
class TypeHintResolver implements ParameterResolver
{
public function getParameters(
ReflectionFunctionAbstract $reflection,
array $providedParameters,
array $resolvedParameters
) {
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (! empty($resolvedParameters)) {
$parameters = array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
$parameterClass = $parameter->getClass();
if ($parameterClass && array_key_exists($parameterClass->name, $providedParameters)) {
$resolvedParameters[$index] = $providedParameters[$parameterClass->name];
}
}
return $resolvedParameters;
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Invoker\Reflection;
use Invoker\Exception\NotCallableException;
/**
* Create a reflection object from a callable.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class CallableReflection
{
/**
* @param callable $callable
*
* @return \ReflectionFunctionAbstract
*
* @throws NotCallableException
*
* TODO Use the `callable` type-hint once support for PHP 5.4 and up.
*/
public static function create($callable)
{
// Closure
if ($callable instanceof \Closure) {
return new \ReflectionFunction($callable);
}
// Array callable
if (is_array($callable)) {
list($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');
}
// Callable class (i.e. implementing __invoke())
if (is_string($callable) && class_exists($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)
));
}
}