PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

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.

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.

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 [container-interop](https://github.com/container-interop/container-interop). If you container is not compliant you can use the [Acclimate](https://github.com/jeremeamia/acclimate-container) package.
Setting up those resolvers is simple:
```php
// $container must be an instance of Interop\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 [container-interop](https://github.com/container-interop/container-interop) compliant container can be provided, and [Acclimate](https://github.com/jeremeamia/acclimate-container) can be used for incompatible containers.

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": {
"container-interop/container-interop": "~1.1"
},
"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,127 @@
<?php
namespace Invoker;
use Interop\Container\ContainerInterface;
use Interop\Container\Exception\NotFoundException;
use Invoker\Exception\NotCallableException;
/**
* 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 (NotFoundException $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 (NotFoundException $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
{
}

View File

@ -0,0 +1,122 @@
<?php
namespace Invoker;
use Interop\Container\ContainerInterface;
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;
/**
* 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 Interop\Container\ContainerInterface;
use Invoker\ParameterResolver\ParameterResolver;
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 Interop\Container\ContainerInterface;
use Invoker\ParameterResolver\ParameterResolver;
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)
));
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace PHPSTORM_META {
$STATIC_METHOD_TYPES = [
\Psr\Container\ContainerInterface::get('') => [
"" == "@",
],
\Interop\Container\ContainerInterface::get('') => [
"" == "@",
],
\DI\Container::get('') => [
"" == "@",
],
\EasyMock\EasyMock::easyMock('') => [
"" == "@",
],
\EasyMock\EasyMock::easySpy('') => [
"" == "@",
],
];
}

View File

@ -0,0 +1,3 @@
---
layout: 404
---

View File

@ -0,0 +1,52 @@
# Contributing
[![Build Status](https://travis-ci.org/PHP-DI/PHP-DI.png?branch=master)](https://travis-ci.org/PHP-DI/PHP-DI) [![Coverage Status](https://coveralls.io/repos/PHP-DI/PHP-DI/badge.png?branch=master)](https://coveralls.io/r/PHP-DI/PHP-DI?branch=master)
PHP-DI is licensed under the MIT License.
## Set up
* Check out the sources using git or download them
```bash
$ git clone https://github.com/PHP-DI/PHP-DI.git
```
* Install the libraries using composer:
```bash
$ curl -s http://getcomposer.org/installer | php
$ php composer.phar install
```
If you are running Windows or are having trouble, read [the official documentation](http://getcomposer.org/doc/00-intro.md#installation).
## Run the tests
The tests are run with [PHPUnit](http://www.phpunit.de/manual/current/en/installation.html):
```bash
$ phpunit
```
## Learning the internals
Read the [How it works](doc/how-it-works.md) documentation.
## What to do?
- Add tests: pick up uncovered situations in the [code coverage report](https://coveralls.io/r/PHP-DI/PHP-DI)
- Resolve issues: [issue list](https://github.com/PHP-DI/PHP-DI/issues)
- Improve the documentation
-
## Coding style
The code follows PSR0, PSR1 and [PSR2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md).
Also, do not hesitate to add your name to the author list of a class in the docblock if you improve it.

View File

@ -0,0 +1,18 @@
PHP-DI - PHP Dependency Injection
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.

View File

@ -0,0 +1,20 @@
---
layout: home
---
[![](doc/img.png)](http://php-di.org/)
[![Build Status](https://img.shields.io/travis/PHP-DI/PHP-DI.svg?style=flat-square)](https://travis-ci.org/PHP-DI/PHP-DI)
[![Coverage Status](https://img.shields.io/coveralls/PHP-DI/PHP-DI/master.svg?style=flat-square)](https://coveralls.io/r/PHP-DI/PHP-DI?branch=master)
[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/PHP-DI/PHP-DI.svg?style=flat-square)](https://scrutinizer-ci.com/g/PHP-DI/PHP-DI/?branch=master)
[![Downloads per months](https://img.shields.io/packagist/dm/PHP-DI/PHP-DI.svg?style=flat-square)](https://packagist.org/packages/PHP-DI/PHP-DI)
[![Total downloads](https://img.shields.io/packagist/dt/PHP-DI/PHP-DI.svg?style=flat-square)](https://packagist.org/packages/PHP-DI/PHP-DI)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/PHP-DI/PHP-DI.svg)](http://isitmaintained.com/project/PHP-DI/PHP-DI "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/PHP-DI/PHP-DI.svg)](http://isitmaintained.com/project/PHP-DI/PHP-DI "Percentage of issues still open")
PHP-DI is a dependency injection container meant to be practical, powerful, and framework-agnostic.
Read more on the website: **[php-di.org](http://php-di.org)**
Join us in the Gitter chat room: [![Gitter chat](https://badges.gitter.im/PHP-DI/PHP-DI.png)](https://gitter.im/PHP-DI/PHP-DI)

View File

@ -0,0 +1,384 @@
# Change log
## 5.4.6
- Fix [#554](https://github.com/PHP-DI/PHP-DI/issues/554): `Container::make()` fails when combined with `decorate()`.
## 5.4.5
Fixup of 5.4.4.
- [#531](https://github.com/PHP-DI/PHP-DI/issues/531): performance improvement.
## 5.4.4
This release was broken because it was tagged against the wrong branch.
- [#531](https://github.com/PHP-DI/PHP-DI/issues/531): performance improvement.
## 5.4.3
- [#467](https://github.com/PHP-DI/PHP-DI/issues/467): register the container against the PSR ContainerInterface
## 5.4.2
- Minor patch to add the `provide: psr/container-implementation` to `composer.json`.
## 5.4.1
- [PSR-11](http://www.php-fig.org/psr/) compliance
Note that PHP-DI was already compliant with PSR-11 because it was implementing container-interop, and container-interop 1.2 extends PSR-11. This new version just makes it more explicit and will allow to drop container-interop support in the next major versions.
## 5.4
Read the [news entry](news/20-php-di-5-4-released.md).
New features:
- [#362](https://github.com/PHP-DI/PHP-DI/issues/362) implemented in [#428](https://github.com/PHP-DI/PHP-DI/pull/428), [#430](https://github.com/PHP-DI/PHP-DI/pull/430), [#431](https://github.com/PHP-DI/PHP-DI/pull/431) and [#432](https://github.com/PHP-DI/PHP-DI/pull/432): factory parameters can now be configured, for example:
```php
return [
'Database' => DI\factory(function ($host) {...})
->parameter('host', DI\get('db.host')),
];
```
Read the [factories documentation](http://php-di.org/doc/php-definitions.html#factories) to learn more. Feature implemented by [@predakanga](https://github.com/predakanga).
Improvements:
- [#429](https://github.com/PHP-DI/PHP-DI/pull/429): performance improvements in definition resolution (by [@mnapoli](https://github.com/mnapoli))
- [#421](https://github.com/PHP-DI/PHP-DI/issues/421): once a `ContainerBuilder` has built a container, it is locked to prevent confusion when adding new definitions to it (by [@mnapoli](https://github.com/mnapoli))
- [#423](https://github.com/PHP-DI/PHP-DI/pull/423): improved exception messages (by [@mnapoli](https://github.com/mnapoli))
## 5.3
Read the [news entry](news/19-php-di-5-3-released.md).
- release of the [2.0 version](https://github.com/PHP-DI/Symfony-Bridge/releases/tag/2.0.0) of the Symfony bridge (by [@mnapoli](https://github.com/mnapoli))
- PHP 5.5 or above is now required
- a lot of documentation improvements by 9 different contributors
- [#389](https://github.com/PHP-DI/PHP-DI/pull/389): exception message improvement by [@mopahle](https://github.com/mopahle)
- [#359](https://github.com/PHP-DI/PHP-DI/issues/359), [#411](https://github.com/PHP-DI/PHP-DI/issues/411), [#414](https://github.com/PHP-DI/PHP-DI/pull/414), [#412](https://github.com/PHP-DI/PHP-DI/pull/412): compatibility with ProxyManager 1.* and 2.* (by [@holtkamp](https://github.com/holtkamp) and [@mnapoli](https://github.com/mnapoli))
- [#416](https://github.com/PHP-DI/PHP-DI/pull/416): dumping definitions was refactored into a more lightweight and simple solution; definition "dumpers" have been removed (internal classes), definitions can now be cast to string directly (by [@mnapoli](https://github.com/mnapoli))
## 5.2
Read the [news entry](news/17-php-di-5-2-released.md).
Improvements:
- [#347](https://github.com/PHP-DI/PHP-DI/pull/347) (includes [#333](https://github.com/PHP-DI/PHP-DI/pull/333) and [#345](https://github.com/PHP-DI/PHP-DI/pull/345)): by [@jdreesen](https://github.com/jdreesen), [@quimcalpe](https://github.com/quimcalpe) and [@mnapoli](https://github.com/mnapoli)
- Allow injection of any container object as factory parameter via type hinting
- Allow injection of a `DI\Factory\RequestedEntry` object to get the requested entry name
- [#272](https://github.com/PHP-DI/PHP-DI/issues/272): Support `"Class::method""` syntax for callables (by [@jdreesen](https://github.com/jdreesen))
- [#332](https://github.com/PHP-DI/PHP-DI/issues/332): IDE support (plugin and documentation) (by [@pulyaevskiy](https://github.com/pulyaevskiy), [@avant1](https://github.com/avant1) and [@mnapoli](https://github.com/mnapoli))
- [#326](https://github.com/PHP-DI/PHP-DI/pull/326): Exception messages are simpler and more consistent (by [@mnapoli](https://github.com/mnapoli))
- [#325](https://github.com/PHP-DI/PHP-DI/pull/325): Add a "Edit this page" button in the website to encourage users to improve the documentation (by [@jdreesen](https://github.com/jdreesen))
Bugfixes:
- [#321](https://github.com/PHP-DI/PHP-DI/pull/321): Allow factory definitions to reference arbitrary container entries as callables (by [@jdreesen](https://github.com/jdreesen))
- [#335](https://github.com/PHP-DI/PHP-DI/issues/335): Class imports in traits are now considered when parsing annotations (by [@thebigb](https://github.com/thebigb))
## 5.1
Read the [news entry](news/16-php-di-5-1-released.md).
Improvements:
- [Zend Framework 2 integration](https://github.com/PHP-DI/ZF2-Bridge) (by @Rastusik)
- [#308](https://github.com/PHP-DI/PHP-DI/pull/308): Instantiate factories using the container (`DI\factory(['FooFactory', 'create'])`)
- Many performances improvements - some benchmarks show up to 35% performance improvements, real results may vary of course
- Many documentation improvements (@jdreesen, @mindplay-dk, @mnapoli, @holtkamp, @Rastusik)
- [#296](https://github.com/PHP-DI/PHP-DI/issues/296): Provide a faster `ArrayCache` implementation, mostly useful in micro-benchmarks
Bugfixes:
- [#257](https://github.com/PHP-DI/PHP-DI/issues/257) & [#274](https://github.com/PHP-DI/PHP-DI/issues/274): Private properties of parent classes are not injected when using annotations
- [#300](https://github.com/PHP-DI/PHP-DI/pull/300): Exception if object definition extends an incompatible definition
- [#306](https://github.com/PHP-DI/PHP-DI/issues/306): Errors when using parameters passed by reference (fixed by @bradynpoulsen)
- [#318](https://github.com/PHP-DI/PHP-DI/issues/318): `Container::call()` ignores parameter's default value
Internal changes:
- [#276](https://github.com/PHP-DI/PHP-DI/pull/276): Tests now pass on Windows (@bgaillard)
## 5.0
This is the complete change log. You can also read the [migration guide](doc/migration/5.0.md) for upgrading, or [the news article](news/15-php-di-5-0-released.md) for a nicer introduction to this new version.
Improvements:
- Moved to an organization on GitHub: [github.com/PHP-DI/PHP-DI](https://github.com/PHP-DI/PHP-DI)
- The package has been renamed to: from `mnapoli/php-di` to [`php-di/php-di`](https://packagist.org/packages/php-di/php-di)
- New [Silex integration](doc/frameworks/silex.md)
- Lighter package: from 10 to 3 Composer dependencies!
- [#235](https://github.com/PHP-DI/PHP-DI/issues/235): `DI\link()` is now deprecated in favor of `DI\get()`. There is no BC break as `DI\link()` still works.
- [#207](https://github.com/PHP-DI/PHP-DI/issues/207): Support for `DI\link()` in arrays
- [#203](https://github.com/PHP-DI/PHP-DI/issues/203): New `DI\string()` helper ([documentation](doc/php-definitions.md))
- [#208](https://github.com/PHP-DI/PHP-DI/issues/208): Support for nested definitions
- [#226](https://github.com/PHP-DI/PHP-DI/pull/226): `DI\factory()` can now be omitted with closures:
```php
// before
'My\Class' => DI\factory(function () { ... })
// now (optional shortcut)
'My\Class' => function () { ... }
```
- [#193](https://github.com/PHP-DI/PHP-DI/issues/193): `DI\object()->method()` now supports calling the same method twice (or more).
- [#248](https://github.com/PHP-DI/PHP-DI/issues/248): New `DI\decorate()` helper to decorate a previously defined entry ([documentation](doc/definition-overriding.md))
- [#215](https://github.com/PHP-DI/PHP-DI/pull/215): New `DI\add()` helper to add entries to an existing array ([documentation](doc/definition-overriding.md))
- [#218](https://github.com/PHP-DI/PHP-DI/issues/218): `ContainerBuilder::addDefinitions()` can now take an array of definitions
- [#211](https://github.com/PHP-DI/PHP-DI/pull/211): `ContainerBuilder::addDefinitions()` is now fluent (return `$this`)
- [#250](https://github.com/PHP-DI/PHP-DI/issues/250): `Container::call()` now also accepts parameters not indexed by name as well as embedded definitions ([documentation](doc/container.md))
- Various performance improvements, e.g. lower the number of files loaded, simpler architecture, …
BC breaks:
- PHP-DI now requires a version of PHP >= 5.4.0
- The package is lighter by default:
- [#251](https://github.com/PHP-DI/PHP-DI/issues/251): Annotations are disabled by default, if you use annotations enable them with `$containerBuilder->useAnnotations(true)`. Additionally the `doctrine/annotations` package isn't required by default anymore, so you also need to run `composer require doctrine/annotations`.
- `doctrine/cache` is not installed by default anymore, you need to require it in `composer.json` (`~1.0`) if you want to configure a cache for PHP-DI
- [#198](https://github.com/PHP-DI/PHP-DI/issues/198): `ocramius/proxy-manager` is not installed by default anymore, you need to require it in `composer.json` (`~1.0`) if you want to use **lazy injection**
- Closures are now converted into factory definitions automatically. If you ever defined a closure as a value (e.g. to have the closure injected in a class), you need to wrap the closure with the new `DI\value()` helper.
- [#223](https://github.com/PHP-DI/PHP-DI/issues/223): `DI\ContainerInterface` was deprecated since v4.1 and has been removed
Internal changes in case you were replacing/extending some parts:
- the definition sources architecture has been refactored, if you defined custom definition sources you will need to update your code (it should be much easier now)
- [#252](https://github.com/PHP-DI/PHP-DI/pull/252): `DI\Scope` internal implementation has changed. You are encouraged to use the constants (`DI\Scope::SINGLETON` and `DI\Scope::PROTOTYPE`) instead of the static methods, but backward compatibility is kept (static methods still work).
- [#241](https://github.com/PHP-DI/PHP-DI/issues/241): `Container::call()` now uses the *Invoker* external library
## 4.4
Read the [news entry](news/13-php-di-4-4-released.md).
- [#185](https://github.com/PHP-DI/PHP-DI/issues/185) Support for invokable objects in `Container::call()`
- [#192](https://github.com/PHP-DI/PHP-DI/pull/192) Support for invokable classes in `Container::call()` (will instantiate the class)
- [#184](https://github.com/PHP-DI/PHP-DI/pull/184) Option to ignore phpdoc errors
## 4.3
Read the [news entry](news/11-php-di-4-3-released.md).
- [#176](https://github.com/PHP-DI/PHP-DI/pull/176) New definition type for reading environment variables: `DI\env()`
- [#181](https://github.com/PHP-DI/PHP-DI/pull/181) `DI\FactoryInterface` and `DI\InvokerInterface` are now auto-registered inside the container so that you can inject them without any configuration needed
- [#173](https://github.com/PHP-DI/PHP-DI/pull/173) `$container->call(['MyClass', 'method]);` will get `MyClass` from the container if `method()` is not a static method
## 4.2.2
- Fixed [#180](https://github.com/PHP-DI/PHP-DI/pull/180): `Container::call()` with object methods (`[$object, 'method']`) is now supported
## 4.2.1
- Support for PHP 5.3.3, which was previously incomplete because of a bug in the reflection (there is now a workaround for this bug)
But if you can, seriously avoid this (really old) PHP version and upgrade.
## 4.2
Read the [news entry](news/10-php-di-4-2-released.md).
**Minor BC-break**: Optional parameters (that were not configured) were injected, they are now ignored, which is what naturally makes sense since they are optional.
Example:
```php
public function __construct(Bar $bar = null)
{
$this->bar = $bar ?: $this->createDefaultBar();
}
```
Before 4.2, PHP-DI would try to inject a `Bar` instance. From 4.2 and onwards, it will inject `null`.
Of course, you can still explicitly define an injection for the optional parameters and that will work.
All changes:
* [#162](https://github.com/PHP-DI/PHP-DI/pull/162) Added `Container::call()` to call functions with dependency injection
* [#156](https://github.com/PHP-DI/PHP-DI/issues/156) Wildcards (`*`) in definitions
* [#164](https://github.com/PHP-DI/PHP-DI/issues/164) Prototype scope is now available for `factory()` definitions too
* FIXED [#168](https://github.com/PHP-DI/PHP-DI/pull/168) `Container::has()` now returns false for interfaces and abstract classes that are not mapped in the definitions
* FIXED [#171](https://github.com/PHP-DI/PHP-DI/issues/171) Optional parameters are now ignored (not injected) if not set in the definitions (see the BC-break warning above)
## 4.1
Read the [news entry](news/09-php-di-4-1-released.md).
BC-breaks: None.
* [#138](https://github.com/PHP-DI/PHP-DI/issues/138) [Container-interop](https://github.com/container-interop/container-interop) compliance
* [#143](https://github.com/PHP-DI/PHP-DI/issues/143) Much more explicit exception messages
* [#157](https://github.com/PHP-DI/PHP-DI/issues/157) HHVM support
* [#158](https://github.com/PHP-DI/PHP-DI/issues/158) Improved the documentation for [Symfony 2 integration](http://php-di.org/doc/frameworks/symfony2.html)
## 4.0
Major changes:
* The configuration format has changed ([read more here to understand why](news/06-php-di-4-0-new-definitions.md))
Read the migration guide if you are using 3.x: [Migration guide from 3.x to 4.0](doc/migration/4.0.md).
BC-breaks:
* YAML, XML and JSON definitions have been removed, and the PHP definition format has changed (see above)
* `ContainerSingleton` has been removed
* You cannot configure an injection as lazy anymore, you can only configure a container entry as lazy
* The Container constructor now takes mandatory parameters. Use the ContainerBuilder to create a Container.
* Removed `ContainerBuilder::setDefinitionsValidation()` (no definition validation anymore)
* `ContainerBuilder::useReflection()` is now named: `ContainerBuilder::useAutowiring()`
* `ContainerBuilder::addDefinitionsFromFile()` is now named: `ContainerBuilder::addDefinitions()`
* The `$proxy` parameter in `Container::get($name, $proxy = true)` hase been removed. To get a proxy, you now need to define an entry as "lazy".
Other changes:
* Added `ContainerInterface` and `FactoryInterface`, both implemented by the container.
* [#115](https://github.com/PHP-DI/PHP-DI/issues/115) Added `Container::has()`
* [#142](https://github.com/PHP-DI/PHP-DI/issues/142) Added `Container::make()` to resolve an entry
* [#127](https://github.com/PHP-DI/PHP-DI/issues/127) Added support for cases where PHP-DI is wrapped by another container (like Acclimate): PHP-DI can now use the wrapping container to perform injections
* [#128](https://github.com/PHP-DI/PHP-DI/issues/128) Configure entry aliases
* [#110](https://github.com/PHP-DI/PHP-DI/issues/110) XML definitions are not supported anymore
* [#122](https://github.com/PHP-DI/PHP-DI/issues/122) JSON definitions are not supported anymore
* `ContainerSingleton` has finally been removed
* Added `ContainerBuilder::buildDevContainer()` to get started with a default container very easily.
* [#99](https://github.com/PHP-DI/PHP-DI/issues/99) Fixed "`@param` with PHP internal type throws exception"
## 3.5.1
* FIXED [#126](https://github.com/PHP-DI/PHP-DI/issues/126): `Container::set` without effect if a value has already been set and retrieved
## 3.5
Read the [news entry](news/05-php-di-3-5.md).
* Importing `@Inject` and `@Injectable` annotations is now optional! It means that you don't have to write `use DI\Annotation\Inject` anymore
* FIXED [#124](https://github.com/PHP-DI/PHP-DI/issues/124): `@Injects` annotation conflicts with other annotations
## 3.4
Read the [news entry](news/04-php-di-3-4.md).
* [#106](https://github.com/PHP-DI/PHP-DI/pull/106) You can now define arrays of values (in YAML, PHP, …) thanks to [@unkind](https://github.com/unkind)
* [#98](https://github.com/PHP-DI/PHP-DI/issues/98) `ContainerBuilder` is now fluent thanks to [@drdamour](https://github.com/drdamour)
* [#101](https://github.com/PHP-DI/PHP-DI/pull/101) Optional parameters are now supported: if you don't define a value to inject, their default value will be used
* XML definitions have been deprecated, there weren't even documented and were not maintained. They will be removed in 4.0.
* FIXED [#100](https://github.com/PHP-DI/PHP-DI/issues/100): bug for lazy injection in constructors
## 3.3
Read the [news entry](news/03-php-di-3-3.md).
* Inject dependencies on an existing instance with `Container::injectOn` (work from [Jeff Flitton](https://github.com/jflitton): [#89](https://github.com/PHP-DI/PHP-DI/pull/89)).
* [#86](https://github.com/PHP-DI/PHP-DI/issues/86): Optimized definition lookup (faster)
* FIXED [#87](https://github.com/PHP-DI/PHP-DI/issues/87): Rare bug in the `PhpDocParser`, fixed by [drdamour](https://github.com/drdamour)
## 3.2
Read the [news entry](news/02-php-di-3-2.md).
Small BC-break: PHP-DI 3.0 and 3.1 injected properties before calling the constructor. This was confusing and [not supported for internal classes](https://github.com/PHP-DI/PHP-DI/issues/74).
From 3.2 and on, properties are injected after calling the constructor.
* **[Lazy injection](doc/lazy-injection.md)**: it is now possible to use lazy injection on properties and methods (setters and constructors).
* Lazy dependencies are now proxies that extend the class they proxy, so type-hinting works.
* Addition of the **`ContainerBuilder`** object, that helps to [create and configure a `Container`](doc/container-configuration.md).
* Some methods for configuring the Container have gone **deprecated** in favor of the `ContainerBuilder`. Fear not, these deprecated methods will remain until next major version (4.0).
* `Container::useReflection`, use ContainerBuilder::useReflection instead
* `Container::useAnnotations`, use ContainerBuilder::useAnnotations instead
* `Container::setDefinitionCache`, use ContainerBuilder::setDefinitionCache instead
* `Container::setDefinitionsValidation`, use ContainerBuilder::setDefinitionsValidation instead
* The container is now auto-registered (as 'DI\Container'). You can now inject the container without registering it.
## 3.1.1
* Value definitions (`$container->set('foo', 80)`) are not cached anymore
* FIXED [#82](https://github.com/PHP-DI/PHP-DI/issues/82): Serialization error when using a cache
## 3.1
Read the [news entry](news/01-php-di-3-1.md).
* Zend Framework 1 integration through the [PHP-DI-ZF1 project](https://github.com/PHP-DI/PHP-DI-ZF1)
* Fixed the order of priorities when you mix different definition sources (reflection, annotations, files, …). See [Definition overriding](doc/definition-overriding.md)
* Now possible to define null values with `$container->set('foo', null)` (see [#79](https://github.com/PHP-DI/PHP-DI/issues/79)).
* Deprecated usage of `ContainerSingleton`, will be removed in next major version (4.0)
## 3.0.6
* FIXED [#76](https://github.com/PHP-DI/PHP-DI/issues/76): Definition conflict when setting a closure for a class name
## 3.0.5
* FIXED [#70](https://github.com/PHP-DI/PHP-DI/issues/70): Definition conflict when setting a value for a class name
## 3.0.4
* FIXED [#69](https://github.com/PHP-DI/PHP-DI/issues/69): YamlDefinitionFileLoader crashes if YAML file is empty
## 3.0.3
* Fixed over-restrictive dependencies in composer.json
## 3.0.2
* [#64](https://github.com/PHP-DI/PHP-DI/issues/64): Non PHP-DI exceptions are not captured-rethrown anymore when injecting dependencies (cleaner stack trace)
## 3.0.1
* [#62](https://github.com/PHP-DI/PHP-DI/issues/62): When using aliases, definitions are now merged
## 3.0
Major compatibility breaks with 2.x.
* The container is no longer a Singleton (but `ContainerSingleton::getInstance()` is available for fools who like it)
* Setter injection
* Constructor injection
* Scopes: singleton (share the same instance of the class) or prototype (create a new instance each time it is fetched). Defined at class level.
* Configuration is reworked from scratch. Now every configuration backend can do 100% of the job.
* Provided configuration backends:
* Reflection
* Annotations: @Inject, @Injectable
* PHP code (`Container::set()`)
* PHP array
* YAML file
* As a consequence, annotations are not mandatory anymore, all functionalities can be used with or without annotations.
* Renamed `DI\Annotations\` to `DI\Annotation\`
* `Container` no longer implements ArrayAccess, use only `$container->get($key)` now
* ZF1 integration broken and removed (work in progress for next releases)
* Code now follows PSR1 and PSR2 coding styles
* FIXED: [#58](https://github.com/PHP-DI/PHP-DI/issues/58) Getting a proxy of an alias didn't work
## 2.1
* `use` statements to import classes from other namespaces are now taken into account with the `@var` annotation
* Updated and lightened the dependencies : `doctrine/common` has been replaced with more specific `doctrine/annotations` and `doctrine/cache`
## 2.0
Major compatibility breaks with 1.x.
* `Container::resolveDependencies()` has been renamed to `Container::injectAll()`
* Dependencies are now injected **before** the constructor is called, and thus are available in the constructor
* Merged `@Value` annotation with `@Inject`: no difference between value and bean injection anymore
* Container implements ArrayAccess for get() and set() (`$container['db.host'] = 'localhost';`)
* Ini configuration files removed: configuration is done in PHP
* Allow to define beans within closures for lazy-loading
* Switched to MIT License
Warning:
* If you use PHP 5.3 and __wakeup() methods, they will be called when PHP-DI creates new instances of those classes.
## 1.1
* Caching of annotations based on Doctrine caches
## 1.0
* DependencyManager renamed to Container
* Refactored basic Container usage with `get` and `set`
* Allow named injection `@Inject(name="")`
* Zend Framework integration

View File

@ -0,0 +1,52 @@
{
"name": "php-di/php-di",
"type": "library",
"description": "The dependency injection container for humans",
"keywords": ["di", "dependency injection", "container"],
"homepage": "http://php-di.org/",
"license": "MIT",
"autoload": {
"psr-4": {
"DI\\": "src/DI/"
},
"files": [
"src/DI/functions.php"
]
},
"autoload-dev": {
"psr-4": {
"DI\\Test\\IntegrationTest\\": "tests/IntegrationTest/",
"DI\\Test\\UnitTest\\": "tests/UnitTest/"
}
},
"scripts": {
"test": "phpunit"
},
"require": {
"php": ">=5.5.0",
"container-interop/container-interop": "~1.2",
"psr/container": "~1.0",
"php-di/invoker": "^1.3.2",
"php-di/phpdoc-reader": "^2.0.1"
},
"require-dev": {
"phpunit/phpunit": "~4.5",
"mnapoli/phpunit-easymock": "~0.2.0",
"doctrine/cache": "~1.4",
"doctrine/annotations": "~1.2",
"phpbench/phpbench": "@dev",
"ocramius/proxy-manager": "~1.0|~2.0"
},
"replace": {
"mnapoli/php-di": "*"
},
"provide": {
"container-interop/container-interop-implementation": "^1.0",
"psr/container-implementation": "^1.0"
},
"suggest": {
"doctrine/cache": "Install it if you want to use the cache (version ~1.4)",
"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 ~1.0 or ~2.0)"
}
}

View File

@ -0,0 +1,134 @@
baseUrl: http://php-di.org
scripts:
before:
- lessc --clean-css website/less/main.less website/css/all.min.css
exclude:
- vendor
- website
- src
- tests
- logo
menu:
items:
introduction:
section: Introduction
items:
getting-started:
text: Getting started
url: doc/getting-started.html
understanding-di:
text: Understanding dependency injection
url: doc/understanding-di.html
best-practices:
text: "\"Best practices\" guide"
url: doc/best-practices.html
usage:
section: Usage
items:
container-configuration:
text: Configuring the container
url: doc/container-configuration.html
container:
text: Using the container
url: doc/container.html
definition:
section: Definitions
items:
definition-introduction:
text: Introduction
url: doc/definition.html
autowiring:
text: Autowiring
url: doc/autowiring.html
php-definitions:
text: PHP definitions
url: doc/php-definitions.html
annotations:
text: Annotations
url: doc/annotations.html
definition-overriding:
text: Definition extensions and overriding
url: doc/definition-overriding.html
frameworks:
section: Frameworks
items:
symfony:
text: Symfony
url: doc/frameworks/symfony2.html
silex:
text: Silex
url: doc/frameworks/silex.html
zf2:
text: Zend Framework 2
url: doc/frameworks/zf2.html
zf1:
text: Zend Framework 1
url: doc/frameworks/zf1.html
slim:
text: Slim
url: doc/frameworks/slim.html
silly:
text: Silly
url: doc/frameworks/silly.html
demo:
text: Demo application
absoluteUrl: https://github.com/PHP-DI/demo
advanced:
section: Advanced topics
items:
performances:
text: Performances
url: doc/performances.html
scopes:
text: Scopes
url: doc/scopes.html
lazy-injection:
text: Lazy injection
url: doc/lazy-injection.html
inject-on-instance:
text: Inject on an existing instance
url: doc/inject-on-instance.html
environments:
text: Injections depending on the environment
url: doc/environments.html
ide-integration:
text: IDE integration
url: doc/ide-integration.html
migration:
section: Migration guides
items:
v4:
text: From PHP-DI 3.x to 4.0
url: doc/migration/4.0.html
v5:
text: From PHP-DI 4.x to 5.0
url: doc/migration/5.0.html
internals:
section: Internals
items:
contributing:
text: Contributing
url: contributing.html
how-it-works:
text: How PHP-DI works
url: doc/how-it-works.html
versions:
section: Old documentation
items:
v3:
text: PHP-DI 3.x
absoluteUrl: https://github.com/PHP-DI/PHP-DI/tree/3.x/doc
v4:
text: PHP-DI 4.x
absoluteUrl: https://github.com/PHP-DI/PHP-DI/tree/4.x/doc
frameworks:
zf1: Zend Framework 1
zf2: Zend Framework 2
symfony2: Symfony
silex: Silex
slim: Slim
silly: Silly

View File

@ -0,0 +1,3 @@
{
"bootstrap": "vendor/autoload.php"
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
phpunit -c phpunit.xml
-->
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
syntaxCheck="true"
forceCoversAnnotation="true"
bootstrap="./vendor/autoload.php">
<testsuites>
<testsuite name="unit">
<directory>./tests/UnitTest/</directory>
</testsuite>
<testsuite name="integration">
<directory>./tests/IntegrationTest/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,90 @@
<?php
namespace DI\Annotation;
use DI\Definition\Exception\AnnotationException;
/**
* "Inject" annotation.
*
* Marks a property or method as an injection point
*
* @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 = [];
/**
* @param array $values
* @throws AnnotationException
*/
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 AnnotationException(sprintf(
'@Inject({"param" = "value"}) expects "value" to be a string, %s given.',
json_encode($value)
));
}
$this->parameters[$key] = $value;
}
}
}
/**
* @return string 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()
{
return $this->parameters;
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace DI\Annotation;
use DI\Scope;
use UnexpectedValueException;
/**
* "Injectable" annotation.
*
* Marks a class as injectable
*
* @Annotation
* @Target("CLASS")
*
* @author Domenic Muskulus <domenic@muskulus.eu>
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
final class Injectable
{
/**
* The scope of an class: prototype, singleton.
* @var string|null
*/
private $scope;
/**
* Should the object be lazy-loaded.
* @var bool|null
*/
private $lazy;
/**
* @param array $values
*/
public function __construct(array $values)
{
if (isset($values['scope'])) {
if ($values['scope'] === 'prototype') {
$this->scope = Scope::PROTOTYPE;
} elseif ($values['scope'] === 'singleton') {
$this->scope = Scope::SINGLETON;
} else {
throw new UnexpectedValueException(sprintf("Value '%s' is not a valid scope", $values['scope']));
}
}
if (isset($values['lazy'])) {
$this->lazy = (bool) $values['lazy'];
}
}
/**
* @return string|null
*/
public function getScope()
{
return $this->scope;
}
/**
* @return bool|null
*/
public function isLazy()
{
return $this->lazy;
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace DI\Cache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\ClearableCache;
use Doctrine\Common\Cache\FlushableCache;
/**
* Simple implementation of a cache based on an array.
*
* This implementation can be used instead of Doctrine's ArrayCache for
* better performances (because simpler implementation).
*
* The code is based on Doctrine's ArrayCache provider:
* @see \Doctrine\Common\Cache\ArrayCache
* @link www.doctrine-project.org
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class ArrayCache implements Cache, FlushableCache, ClearableCache
{
/**
* @var array
*/
private $data = [];
public function fetch($id)
{
return $this->contains($id) ? $this->data[$id] : false;
}
public function contains($id)
{
// isset() is required for performance optimizations, to avoid unnecessary function calls to array_key_exists.
return isset($this->data[$id]) || array_key_exists($id, $this->data);
}
public function save($id, $data, $lifeTime = 0)
{
$this->data[$id] = $data;
return true;
}
public function delete($id)
{
unset($this->data[$id]);
return true;
}
public function getStats()
{
return null;
}
public function flushAll()
{
$this->data = [];
return true;
}
public function deleteAll()
{
return $this->flushAll();
}
}

View File

@ -0,0 +1,354 @@
<?php
namespace DI;
use DI\Definition\Definition;
use DI\Definition\FactoryDefinition;
use DI\Definition\Helper\DefinitionHelper;
use DI\Definition\InstanceDefinition;
use DI\Definition\ObjectDefinition;
use DI\Definition\Resolver\DefinitionResolver;
use DI\Definition\Resolver\ResolverDispatcher;
use DI\Definition\Source\CachedDefinitionSource;
use DI\Definition\Source\DefinitionSource;
use DI\Definition\Source\MutableDefinitionSource;
use DI\Invoker\DefinitionParameterResolver;
use DI\Proxy\ProxyFactory;
use Exception;
use Interop\Container\ContainerInterface as InteropContainerInterface;
use InvalidArgumentException;
use Invoker\Invoker;
use Invoker\ParameterResolver\AssociativeArrayResolver;
use Invoker\ParameterResolver\Container\TypeHintContainerResolver;
use Invoker\ParameterResolver\DefaultValueResolver;
use Invoker\ParameterResolver\NumericArrayResolver;
use Invoker\ParameterResolver\ResolverChain;
use Psr\Container\ContainerInterface;
/**
* Dependency Injection Container.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Container implements ContainerInterface, InteropContainerInterface, FactoryInterface, \DI\InvokerInterface
{
/**
* Map of entries with Singleton scope that are already resolved.
* @var array
*/
private $singletonEntries = [];
/**
* @var DefinitionSource
*/
private $definitionSource;
/**
* @var DefinitionResolver
*/
private $definitionResolver;
/**
* Map of definitions that are already looked up.
*
* @var array
*/
private $definitionCache = [];
/**
* Array of entries being resolved. Used to avoid circular dependencies and infinite loops.
* @var array
*/
private $entriesBeingResolved = [];
/**
* @var \Invoker\InvokerInterface|null
*/
private $invoker;
/**
* Container that wraps this container. If none, points to $this.
*
* @var ContainerInterface
*/
private $wrapperContainer;
/**
* Use the ContainerBuilder to ease constructing the Container.
*
* @see ContainerBuilder
*
* @param DefinitionSource $definitionSource
* @param ProxyFactory $proxyFactory
* @param ContainerInterface $wrapperContainer If the container is wrapped by another container.
*/
public function __construct(
DefinitionSource $definitionSource,
ProxyFactory $proxyFactory,
ContainerInterface $wrapperContainer = null
) {
$this->wrapperContainer = $wrapperContainer ?: $this;
$this->definitionSource = $definitionSource;
$this->definitionResolver = new ResolverDispatcher($this->wrapperContainer, $proxyFactory);
// Auto-register the container
$this->singletonEntries[self::class] = $this;
$this->singletonEntries[FactoryInterface::class] = $this;
$this->singletonEntries[InvokerInterface::class] = $this;
$this->singletonEntries[ContainerInterface::class] = $this;
}
/**
* Returns an entry of the container by its name.
*
* @param string $name Entry name or a class name.
*
* @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
*/
public function get($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)
));
}
// Try to find the entry in the singleton map
if (array_key_exists($name, $this->singletonEntries)) {
return $this->singletonEntries[$name];
}
$definition = $this->getDefinition($name);
if (! $definition) {
throw new NotFoundException("No entry or class found for '$name'");
}
$value = $this->resolveDefinition($definition);
// If the entry is singleton, we store it to always return it without recomputing it
if ($definition->getScope() === Scope::SINGLETON) {
$this->singletonEntries[$name] = $value;
}
return $value;
}
private function getDefinition($name)
{
if(!array_key_exists($name, $this->definitionCache)){
$this->definitionCache[$name] = $this->definitionSource->getDefinition($name);
}
return $this->definitionCache[$name];
}
/**
* Build an entry of the container by its name.
*
* This method behave like get() except it forces the scope to "prototype",
* which means the definition of the entry will be re-evaluated each 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.
*
* @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 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
*/
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) {
// Try to find the entry in the singleton map
if (array_key_exists($name, $this->singletonEntries)) {
return $this->singletonEntries[$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->singletonEntries)) {
return true;
}
$definition = $this->getDefinition($name);
if ($definition === null) {
return false;
}
return $this->definitionResolver->isResolvable($definition);
}
/**
* Inject all dependencies on an existing instance.
*
* @param object $instance Object to perform injection upon
* @throws InvalidArgumentException
* @throws DependencyException Error while injecting dependencies
* @return object $instance Returns the same instance
*/
public function injectOn($instance)
{
$objectDefinition = $this->definitionSource->getDefinition(get_class($instance));
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($name, $value)
{
if ($value instanceof DefinitionHelper) {
$value = $value->getDefinition($name);
} elseif ($value instanceof \Closure) {
$value = new FactoryDefinition($name, $value);
}
if ($value instanceof Definition) {
$this->setDefinition($name, $value);
} else {
$this->singletonEntries[$name] = $value;
}
}
/**
* Resolves a definition.
*
* Checks for circular dependencies while resolving the definition.
*
* @param Definition $definition
* @param array $parameters
*
* @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);
} catch (Exception $exception) {
unset($this->entriesBeingResolved[$entryName]);
throw $exception;
}
unset($this->entriesBeingResolved[$entryName]);
return $value;
}
private function setDefinition($name, Definition $definition)
{
if ($this->definitionSource instanceof CachedDefinitionSource) {
throw new \LogicException('You cannot set a definition at runtime on a container that has a cache configured. 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.');
}
if (! $this->definitionSource instanceof MutableDefinitionSource) {
// This can happen if you instantiate the container yourself
throw new \LogicException('The container has not been initialized correctly');
}
$this->definitionCache = []; //Completely clear definitionCache
// Clear existing entries if it exists
if (array_key_exists($name, $this->singletonEntries)) {
unset($this->singletonEntries[$name]);
}
$this->definitionSource->addDefinition($definition);
}
/**
* @return \Invoker\InvokerInterface
*/
private function getInvoker()
{
if (! $this->invoker) {
$parameterResolver = new ResolverChain([
new DefinitionParameterResolver($this->definitionResolver),
new NumericArrayResolver,
new AssociativeArrayResolver,
new DefaultValueResolver,
new TypeHintContainerResolver($this->wrapperContainer),
]);
$this->invoker = new Invoker($parameterResolver, $this);
}
return $this->invoker;
}
}

View File

@ -0,0 +1,284 @@
<?php
namespace DI;
use DI\Definition\Source\AnnotationReader;
use DI\Definition\Source\Autowiring;
use DI\Definition\Source\CachedDefinitionSource;
use DI\Definition\Source\DefinitionArray;
use DI\Definition\Source\DefinitionFile;
use DI\Definition\Source\DefinitionSource;
use DI\Definition\Source\SourceChain;
use DI\Proxy\ProxyFactory;
use Doctrine\Common\Cache\Cache;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
/**
* Helper to create and configure a Container.
*
* With the default options, the container created is appropriate for the development environment.
*
* Example:
*
* $builder = new ContainerBuilder();
* $container = $builder->build();
*
* @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;
/**
* @var bool
*/
private $useAutowiring = true;
/**
* @var bool
*/
private $useAnnotations = false;
/**
* @var bool
*/
private $ignorePhpDocErrors = false;
/**
* @var Cache
*/
private $cache;
/**
* 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
*/
private $proxyDirectory;
/**
* If PHP-DI is wrapped in another container, this references the wrapper.
* @var ContainerInterface
*/
private $wrapperContainer;
/**
* @var DefinitionSource[]
*/
private $definitionSources = [];
/**
* Whether the container has already been built.
* @var bool
*/
private $locked = false;
/**
* Build a container configured for the dev environment.
*
* @return Container
*/
public static function buildDevContainer()
{
$builder = new self();
return $builder->build();
}
/**
* @param string $containerClass Name of the container class, used to create the container.
*/
public function __construct($containerClass = 'DI\Container')
{
$this->containerClass = $containerClass;
}
/**
* Build and return a container.
*
* @return Container
*/
public function build()
{
$sources = array_reverse($this->definitionSources);
if ($this->useAnnotations) {
$sources[] = new AnnotationReader($this->ignorePhpDocErrors);
} elseif ($this->useAutowiring) {
$sources[] = new Autowiring();
}
$chain = new SourceChain($sources);
if ($this->cache) {
$source = new CachedDefinitionSource($chain, $this->cache);
$chain->setRootDefinitionSource($source);
} else {
$source = $chain;
// Mutable definition source
$source->setMutableDefinitionSource(new DefinitionArray());
}
$proxyFactory = new ProxyFactory($this->writeProxiesToFile, $this->proxyDirectory);
$this->locked = true;
$containerClass = $this->containerClass;
return new $containerClass($source, $proxyFactory, $this->wrapperContainer);
}
/**
* Enable or disable the use of autowiring to guess injections.
*
* Enabled by default.
*
* @param bool $bool
* @return ContainerBuilder
*/
public function useAutowiring($bool)
{
$this->ensureNotLocked();
$this->useAutowiring = $bool;
return $this;
}
/**
* Enable or disable the use of annotations to guess injections.
*
* Disabled by default.
*
* @param bool $bool
* @return ContainerBuilder
*/
public function useAnnotations($bool)
{
$this->ensureNotLocked();
$this->useAnnotations = $bool;
return $this;
}
/**
* Enable or disable ignoring phpdoc errors (non-existent classes in `@param` or `@var`).
*
* @param bool $bool
* @return ContainerBuilder
*/
public function ignorePhpDocErrors($bool)
{
$this->ensureNotLocked();
$this->ignorePhpDocErrors = $bool;
return $this;
}
/**
* Enables the use of a cache for the definitions.
*
* @param Cache $cache Cache backend to use
* @return ContainerBuilder
*/
public function setDefinitionCache(Cache $cache)
{
$this->ensureNotLocked();
$this->cache = $cache;
return $this;
}
/**
* Configure the proxy generation.
*
* For dev environment, use writeProxiesToFile(false) (default configuration)
* For production environment, use writeProxiesToFile(true, 'tmp/proxies')
*
* @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 ContainerBuilder
*/
public function writeProxiesToFile($writeToFile, $proxyDirectory = null)
{
$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.
*
* @param ContainerInterface $otherContainer
* @return $this
*/
public function wrapContainer(ContainerInterface $otherContainer)
{
$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)
{
$this->ensureNotLocked();
if (is_string($definitions)) {
// File
$definitions = new DefinitionFile($definitions);
} elseif (is_array($definitions)) {
$definitions = new DefinitionArray($definitions);
} elseif (! $definitions instanceof DefinitionSource) {
throw new InvalidArgumentException(sprintf(
'%s parameter must be a string, an array or a DefinitionSource object, %s given',
'ContainerBuilder::addDefinitions()',
is_object($definitions) ? get_class($definitions) : gettype($definitions)
));
}
$this->definitionSources[] = $definitions;
return $this;
}
private function ensureNotLocked()
{
if ($this->locked) {
throw new \LogicException('The ContainerBuilder cannot be modified after the container has been built');
}
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace DI;
use DI\Definition\Definition;
/**
* Debug utilities.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Debug
{
/**
* Dump the definition to a string.
*
* @return string
*
* @deprecated You should cast the definition to string instead.
* This feature was simplified: definitions can be cast to string directly.
*/
public static function dumpDefinition(Definition $definition)
{
return (string) $definition;
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace DI\Definition;
use DI\Scope;
use Psr\Container\ContainerInterface;
/**
* Defines an alias from an entry to another.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AliasDefinition implements CacheableDefinition, SelfResolvingDefinition
{
/**
* Entry name.
* @var string
*/
private $name;
/**
* Name of the target entry.
* @var string
*/
private $targetEntryName;
/**
* @param string $name Entry name
* @param string $targetEntryName Name of the target entry
*/
public function __construct($name, $targetEntryName)
{
$this->name = $name;
$this->targetEntryName = $targetEntryName;
}
/**
* @return string Entry name
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getScope()
{
return Scope::PROTOTYPE;
}
/**
* @return string
*/
public function getTargetEntryName()
{
return $this->targetEntryName;
}
public function resolve(ContainerInterface $container)
{
return $container->get($this->getTargetEntryName());
}
public function isResolvable(ContainerInterface $container)
{
return $container->has($this->getTargetEntryName());
}
public function __toString()
{
return sprintf(
'get(%s)',
$this->targetEntryName
);
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace DI\Definition;
use DI\Definition\Helper\DefinitionHelper;
use DI\Scope;
/**
* 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;
/**
* @param string $name Entry name
* @param array $values
*/
public function __construct($name, array $values)
{
$this->name = $name;
$this->values = $values;
}
/**
* @return string Entry name
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getScope()
{
return Scope::SINGLETON;
}
/**
* @return array
*/
public function getValues()
{
return $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 DefinitionHelper) {
$str .= str_replace(PHP_EOL, PHP_EOL . ' ', $value->getDefinition(''));
} else {
$str .= var_export($value, true);
}
$str .= ',' . PHP_EOL;
}
return $str . ']';
}
}

View File

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

View File

@ -0,0 +1,12 @@
<?php
namespace DI\Definition;
/**
* Cacheable definition.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface CacheableDefinition extends Definition
{
}

View File

@ -0,0 +1,46 @@
<?php
namespace DI\Definition;
/**
* Factory that decorates a sub-definition.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DecoratorDefinition extends FactoryDefinition implements Definition, HasSubDefinition
{
/**
* @var Definition
*/
private $decorated;
/**
* @return string
*/
public function getSubDefinitionName()
{
return $this->getName();
}
/**
* @param Definition $definition
*/
public function setSubDefinition(Definition $definition)
{
$this->decorated = $definition;
}
/**
* @return Definition
*/
public function getDecoratedDefinition()
{
return $this->decorated;
}
public function __toString()
{
return 'Decorate(' . $this->getSubDefinitionName() . ')';
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace DI\Definition;
use DI\Factory\RequestedEntry;
/**
* Definition.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface Definition extends RequestedEntry
{
/**
* Returns the name of the entry in the container.
*
* @return string
*/
public function getName();
/**
* Returns the scope of the entry.
*
* @return string
*/
public function getScope();
/**
* Definitions can be cast to string for debugging information.
*
* This method is not enforced by the interface yet for backward
* compatibility.
*/
// public function __toString();
}

View File

@ -0,0 +1,144 @@
<?php
namespace DI\Definition\Dumper;
use DI\Definition\EntryReference;
use DI\Definition\ObjectDefinition;
use 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.
*
* @return string
*/
public function dump(ObjectDefinition $definition)
{
$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);
// Scope
$str .= PHP_EOL . ' scope = ' . $definition->getScope();
// 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($className, ObjectDefinition $definition)
{
$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)
{
$str = '';
foreach ($definition->getPropertyInjections() as $propertyInjection) {
$value = $propertyInjection->getValue();
if ($value instanceof EntryReference) {
$valueStr = sprintf('get(%s)', $value->getName());
} else {
$valueStr = var_export($value, true);
}
$str .= sprintf(PHP_EOL . ' $%s = %s', $propertyInjection->getPropertyName(), $valueStr);
}
return $str;
}
private function dumpMethods($className, ObjectDefinition $definition)
{
$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($className, MethodInjection $methodInjection)
{
$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];
if ($value instanceof EntryReference) {
$args[] = sprintf('$%s = get(%s)', $parameter->getName(), $value->getName());
} else {
$args[] = sprintf('$%s = %s', $parameter->getName(), var_export($value, true));
}
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);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace DI\Definition;
use DI\Definition\Helper\DefinitionHelper;
/**
* Represents a reference to a container entry.
*
* TODO should EntryReference and AliasDefinition be merged into a ReferenceDefinition?
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class EntryReference implements DefinitionHelper
{
/**
* Entry name.
* @var string
*/
private $name;
/**
* @param string $entryName Entry name
*/
public function __construct($entryName)
{
$this->name = $entryName;
}
/**
* @return string Entry name
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getDefinition($entryName)
{
return new AliasDefinition($entryName, $this->name);
}
}

View File

@ -0,0 +1,129 @@
<?php
namespace DI\Definition;
use DI\Definition\Helper\DefinitionHelper;
use DI\Scope;
/**
* 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 CacheableDefinition
{
/**
* 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;
/**
* @var string|null
*/
private $scope;
/**
* @param string $name Entry name
* @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($name, $variableName, $isOptional = false, $defaultValue = null)
{
$this->name = $name;
$this->variableName = $variableName;
$this->isOptional = $isOptional;
$this->defaultValue = $defaultValue;
}
/**
* @return string Entry name
*/
public function getName()
{
return $this->name;
}
/**
* @return string The name of the environment variable
*/
public function getVariableName()
{
return $this->variableName;
}
/**
* @return bool Whether or not the environment variable definition is optional
*/
public function isOptional()
{
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;
}
/**
* @param string $scope
*/
public function setScope($scope)
{
$this->scope = $scope;
}
/**
* {@inheritdoc}
*/
public function getScope()
{
return $this->scope ?: Scope::SINGLETON;
}
public function __toString()
{
$str = ' variable = ' . $this->variableName . PHP_EOL
. ' optional = ' . ($this->isOptional ? 'yes' : 'no');
if ($this->isOptional) {
if ($this->defaultValue instanceof DefinitionHelper) {
$nestedDefinition = (string) $this->defaultValue->getDefinition('');
$defaultValueStr = str_replace(PHP_EOL, PHP_EOL . ' ', $nestedDefinition);
} else {
$defaultValueStr = var_export($this->defaultValue, true);
}
$str .= PHP_EOL . ' default = ' . $defaultValueStr;
}
return sprintf('Environment variable (' . PHP_EOL . '%s' . PHP_EOL . ')', $str);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace DI\Definition\Exception;
/**
* Exception in the definitions using annotations.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AnnotationException extends DefinitionException
{
}

View File

@ -0,0 +1,22 @@
<?php
namespace DI\Definition\Exception;
use DI\Definition\Definition;
/**
* Invalid DI definitions.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DefinitionException extends \Exception
{
public static function create(Definition $definition, $message)
{
return new self(sprintf(
'%s' . PHP_EOL . 'Full definition:' . PHP_EOL . '%s',
$message,
(string) $definition
));
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace DI\Definition;
use DI\Scope;
/**
* 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;
/**
* @var string
*/
private $scope;
/**
* Callable that returns the value.
* @var callable
*/
private $factory;
/**
* Factory parameters.
* @var array
*/
private $parameters = [];
/**
* @param string $name Entry name
* @param callable $factory Callable that returns the value associated to the entry name.
* @param string|null $scope
* @param array $parameters Parameters to be passed to the callable
*/
public function __construct($name, $factory, $scope = null, $parameters = [])
{
$this->name = $name;
$this->factory = $factory;
$this->scope = $scope;
$this->parameters = $parameters;
}
/**
* @return string Entry name.
*/
public function getName()
{
return $this->name;
}
/**
* Default scope is singleton: the callable is called once and the result is shared.
*
* {@inheritdoc}
*/
public function getScope()
{
return $this->scope ?: Scope::SINGLETON;
}
/**
* @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()
{
return $this->parameters;
}
public function __toString()
{
return 'Factory';
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace DI\Definition;
/**
* A definition that has a sub-definition.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface HasSubDefinition extends Definition
{
/**
* @return string
*/
public function getSubDefinitionName();
/**
* @param Definition $definition
*/
public function setSubDefinition(Definition $definition);
}

View File

@ -0,0 +1,39 @@
<?php
namespace DI\Definition\Helper;
use DI\Definition\ArrayDefinitionExtension;
/**
* Helps extending the definition of an array.
*
* For example you can add new entries to the array.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ArrayDefinitionExtensionHelper implements DefinitionHelper
{
/**
* @var array
*/
private $values = [];
/**
* @param array $values Values to add to the array.
*/
public function __construct(array $values)
{
$this->values = $values;
}
/**
* @param string $entryName Container entry name
*
* @return ArrayDefinitionExtension
*/
public function getDefinition($entryName)
{
return new ArrayDefinitionExtension($entryName, $this->values);
}
}

View File

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

View File

@ -0,0 +1,57 @@
<?php
namespace DI\Definition\Helper;
use DI\Definition\EnvironmentVariableDefinition;
/**
* Helps defining how to create an instance of an environment variable definition.
*
* @author James Harris <james.harris@icecave.com.au>
*/
class EnvironmentVariableDefinitionHelper implements DefinitionHelper
{
/**
* 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($variableName, $isOptional, $defaultValue = null)
{
$this->variableName = $variableName;
$this->isOptional = $isOptional;
$this->defaultValue = $defaultValue;
}
/**
* @param string $entryName Container entry name
*
* @return EnvironmentVariableDefinition
*/
public function getDefinition($entryName)
{
return new EnvironmentVariableDefinition($entryName, $this->variableName, $this->isOptional, $this->defaultValue);
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace DI\Definition\Helper;
use DI\Definition\DecoratorDefinition;
use 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 string|null
*/
private $scope;
/**
* @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, $decorate = false)
{
$this->factory = $factory;
$this->decorate = $decorate;
}
/**
* Defines the scope of the entry.
*
* @param string $scope
*
* @return FactoryDefinitionHelper
*/
public function scope($scope)
{
$this->scope = $scope;
return $this;
}
/**
* @param string $entryName Container entry name
* @return FactoryDefinition
*/
public function getDefinition($entryName)
{
if ($this->decorate) {
return new DecoratorDefinition($entryName, $this->factory, $this->scope, $this->parameters);
}
return new FactoryDefinition($entryName, $this->factory, $this->scope, $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 FactoryDefinitionHelper
*/
public function parameter($parameter, $value)
{
$this->parameters[$parameter] = $value;
return $this;
}
}

View File

@ -0,0 +1,278 @@
<?php
namespace DI\Definition\Helper;
use DI\Definition\Exception\DefinitionException;
use DI\Definition\ObjectDefinition;
use DI\Definition\ObjectDefinition\MethodInjection;
use DI\Definition\ObjectDefinition\PropertyInjection;
/**
* Helps defining how to create an instance of a class.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ObjectDefinitionHelper implements DefinitionHelper
{
/**
* @var string|null
*/
private $className;
/**
* @var bool|null
*/
private $lazy;
/**
* @var string|null
*/
private $scope;
/**
* Array of constructor parameters.
* @var array
*/
private $constructor = [];
/**
* Array of properties and their value.
* @var array
*/
private $properties = [];
/**
* Array of methods and their parameters.
* @var array
*/
private $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($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 ObjectDefinitionHelper
*/
public function lazy()
{
$this->lazy = true;
return $this;
}
/**
* Defines the scope of the entry.
*
* @param string $scope
*
* @return ObjectDefinitionHelper
*/
public function scope($scope)
{
$this->scope = $scope;
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 to use for calling the constructor of the class.
*
* @return ObjectDefinitionHelper
*/
public function constructor()
{
$this->constructor = func_get_args();
return $this;
}
/**
* 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 $parameter Parameter for which the value will be given.
* @param mixed $value Value to give to this parameter.
*
* @return ObjectDefinitionHelper
*/
public function constructorParameter($parameter, $value)
{
$this->constructor[$parameter] = $value;
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 ObjectDefinitionHelper
*/
public function property($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 to use for calling the method.
*
* @return ObjectDefinitionHelper
*/
public function method($method)
{
$args = func_get_args();
array_shift($args);
if (! isset($this->methods[$method])) {
$this->methods[$method] = [];
}
$this->methods[$method][] = $args;
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 $parameter Name or index of the parameter for which the value will be given.
* @param mixed $value Value to give to this parameter.
*
* @return ObjectDefinitionHelper
*/
public function methodParameter($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;
}
/**
* {@inheritdoc}
*/
public function getDefinition($entryName)
{
$definition = new ObjectDefinition($entryName, $this->className);
if ($this->lazy !== null) {
$definition->setLazy($this->lazy);
}
if ($this->scope !== null) {
$definition->setScope($this->scope);
}
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.
*
* @param ObjectDefinition $definition
* @param string $method
* @param array $parameters
* @throws DefinitionException
* @return array
*/
private function fixParameters(ObjectDefinition $definition, $method, $parameters)
{
$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 DefinitionException::create($definition, sprintf("Parameter with name '%s' could not be found. %s.", $index, $e->getMessage()));
}
$index = $reflectionParameter->getPosition();
}
$fixedParameters[$index] = $parameter;
}
return $fixedParameters;
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace DI\Definition\Helper;
use DI\Definition\StringDefinition;
/**
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class StringDefinitionHelper implements DefinitionHelper
{
/**
* @var string
*/
private $expression;
public function __construct($expression)
{
$this->expression = $expression;
}
/**
* @param string $entryName Container entry name
*
* @return StringDefinition
*/
public function getDefinition($entryName)
{
return new StringDefinition($entryName, $this->expression);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace DI\Definition\Helper;
use DI\Definition\ValueDefinition;
/**
* Helps defining a value.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ValueDefinitionHelper implements DefinitionHelper
{
/**
* @var mixed
*/
private $value;
/**
* @param mixed $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* @param string $entryName Container entry name
* @return ValueDefinition
*/
public function getDefinition($entryName)
{
return new ValueDefinition($entryName, $this->value);
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace DI\Definition;
use DI\Scope;
/**
* 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
* @param ObjectDefinition $objectDefinition
*/
public function __construct($instance, ObjectDefinition $objectDefinition)
{
$this->instance = $instance;
$this->objectDefinition = $objectDefinition;
}
/**
* {@inheritdoc}
*/
public function getName()
{
// Name are superfluous for instance definitions
return '';
}
/**
* {@inheritdoc}
*/
public function getScope()
{
return Scope::PROTOTYPE;
}
/**
* @return object
*/
public function getInstance()
{
return $this->instance;
}
/**
* @return ObjectDefinition
*/
public function getObjectDefinition()
{
return $this->objectDefinition;
}
}

View File

@ -0,0 +1,336 @@
<?php
namespace DI\Definition;
use DI\Definition\Dumper\ObjectDefinitionDumper;
use DI\Definition\ObjectDefinition\MethodInjection;
use DI\Definition\ObjectDefinition\PropertyInjection;
use DI\Scope;
use ReflectionClass;
/**
* Defines how an object can be instantiated.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ObjectDefinition implements Definition, CacheableDefinition, HasSubDefinition
{
/**
* 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
*/
private $className;
/**
* Constructor parameter injection.
* @var MethodInjection|null
*/
private $constructorInjection;
/**
* Property injections.
* @var PropertyInjection[]
*/
private $propertyInjections = [];
/**
* Method calls.
* @var MethodInjection[][]
*/
private $methodInjections = [];
/**
* @var string|null
*/
private $scope;
/**
* @var bool|null
*/
private $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 Class name
* @param string $className
*/
public function __construct($name, $className = null)
{
$this->name = (string) $name;
$this->setClassName($className);
}
/**
* @return string Entry name
*/
public function getName()
{
return $this->name;
}
/**
* @param string|null $className
*/
public function setClassName($className)
{
$this->className = $className;
$this->updateCache();
}
/**
* @return string Class name
*/
public function getClassName()
{
if ($this->className !== null) {
return $this->className;
}
return $this->name;
}
/**
* @return MethodInjection|null
*/
public function getConstructorInjection()
{
return $this->constructorInjection;
}
/**
* @param MethodInjection $constructorInjection
*/
public function setConstructorInjection(MethodInjection $constructorInjection)
{
$this->constructorInjection = $constructorInjection;
}
/**
* @return PropertyInjection[] Property injections
*/
public function getPropertyInjections()
{
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()
{
// Return array leafs
$injections = [];
array_walk_recursive($this->methodInjections, function ($injection) use (&$injections) {
$injections[] = $injection;
});
return $injections;
}
/**
* @param MethodInjection $methodInjection
*/
public function addMethodInjection(MethodInjection $methodInjection)
{
$method = $methodInjection->getMethodName();
if (! isset($this->methodInjections[$method])) {
$this->methodInjections[$method] = [];
}
$this->methodInjections[$method][] = $methodInjection;
}
/**
* @param string $scope
*/
public function setScope($scope)
{
$this->scope = $scope;
}
/**
* {@inheritdoc}
*/
public function getScope()
{
return $this->scope ?: Scope::SINGLETON;
}
/**
* @param bool|null $lazy
*/
public function setLazy($lazy)
{
$this->lazy = $lazy;
}
/**
* @return bool
*/
public function isLazy()
{
if ($this->lazy !== null) {
return $this->lazy;
} else {
// Default value
return false;
}
}
/**
* @return bool
*/
public function classExists()
{
return $this->classExists;
}
/**
* @return bool
*/
public function isInstantiable()
{
return $this->isInstantiable;
}
/**
* {@inheritdoc}
*/
public function getSubDefinitionName()
{
return $this->getClassName();
}
/**
* {@inheritdoc}
*/
public function setSubDefinition(Definition $definition)
{
if (! $definition instanceof self) {
return;
}
// The current prevails
if ($this->className === null) {
$this->setClassName($definition->className);
}
if ($this->scope === null) {
$this->scope = $definition->scope;
}
if ($this->lazy === null) {
$this->lazy = $definition->lazy;
}
// Merge constructor injection
$this->mergeConstructorInjection($definition);
// Merge property injections
$this->mergePropertyInjections($definition);
// Merge method injections
$this->mergeMethodInjections($definition);
}
public function __toString()
{
return (new ObjectDefinitionDumper)->dump($this);
}
private function mergeConstructorInjection(ObjectDefinition $definition)
{
if ($definition->getConstructorInjection() !== null) {
if ($this->constructorInjection !== null) {
// Merge
$this->constructorInjection->merge($definition->getConstructorInjection());
} else {
// Set
$this->constructorInjection = $definition->getConstructorInjection();
}
}
}
private function mergePropertyInjections(ObjectDefinition $definition)
{
foreach ($definition->propertyInjections as $propertyName => $propertyInjection) {
if (! isset($this->propertyInjections[$propertyName])) {
// Add
$this->propertyInjections[$propertyName] = $propertyInjection;
}
}
}
private function mergeMethodInjections(ObjectDefinition $definition)
{
foreach ($definition->methodInjections as $methodName => $calls) {
if (array_key_exists($methodName, $this->methodInjections)) {
$this->mergeMethodCalls($calls, $methodName);
} else {
// Add
$this->methodInjections[$methodName] = $calls;
}
}
}
private function mergeMethodCalls(array $calls, $methodName)
{
foreach ($calls as $index => $methodInjection) {
// Merge
if (array_key_exists($index, $this->methodInjections[$methodName])) {
// Merge
$this->methodInjections[$methodName][$index]->merge($methodInjection);
} else {
// Add
$this->methodInjections[$methodName][$index] = $methodInjection;
}
}
}
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();
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace DI\Definition\ObjectDefinition;
use DI\Definition\Definition;
use DI\Scope;
/**
* Describe an injection in an object method.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class MethodInjection implements Definition
{
/**
* @var string
*/
private $methodName;
/**
* @var array
*/
private $parameters = [];
/**
* @param string $methodName
* @param array $parameters
*/
public function __construct($methodName, array $parameters = [])
{
$this->methodName = (string) $methodName;
$this->parameters = $parameters;
}
public static function constructor(array $parameters = [])
{
return new self('__construct', $parameters);
}
/**
* @return string Method name
*/
public function getMethodName()
{
return $this->methodName;
}
/**
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
/**
* Replace the parameters of the definition by a new array of parameters.
*
* @param array $parameters
*/
public function replaceParameters(array $parameters)
{
$this->parameters = $parameters;
}
public function merge(MethodInjection $definition)
{
// In case of conflicts, the current definition prevails.
$this->parameters = $this->parameters + $definition->parameters;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return null;
}
/**
* {@inheritdoc}
*/
public function getScope()
{
return Scope::PROTOTYPE;
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace 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
* @param string|null $className
*/
public function __construct($propertyName, $value, $className = null)
{
$this->propertyName = (string) $propertyName;
$this->value = $value;
$this->className = $className;
}
/**
* @return string Property name
*/
public function getPropertyName()
{
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;
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace DI\Definition\Resolver;
use DI\Definition\ArrayDefinition;
use DI\Definition\Definition;
use DI\Definition\Helper\DefinitionHelper;
use 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
*
* {@inheritdoc}
*/
public function resolve(Definition $definition, array $parameters = [])
{
$values = $definition->getValues();
// Resolve nested definitions
foreach ($values as $key => $value) {
if ($value instanceof DefinitionHelper) {
$values[$key] = $this->resolveDefinition($value, $definition, $key);
}
}
return $values;
}
/**
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = [])
{
return true;
}
private function resolveDefinition(DefinitionHelper $value, ArrayDefinition $definition, $key)
{
try {
return $this->definitionResolver->resolve($value->getDefinition(''));
} catch (DependencyException $e) {
throw $e;
} catch (Exception $e) {
throw new DependencyException(sprintf(
'Error while resolving %s[%s]. %s',
$definition->getName(),
$key,
$e->getMessage()
), 0, $e);
}
}
}

View File

@ -0,0 +1,86 @@
<?php
namespace DI\Definition\Resolver;
use DI\Definition\DecoratorDefinition;
use DI\Definition\Definition;
use DI\Definition\Exception\DefinitionException;
use 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 ContainerInterface $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
*
* {@inheritdoc}
*/
public function resolve(Definition $definition, array $parameters = [])
{
$callable = $definition->getCallable();
if (! is_callable($callable)) {
throw new DefinitionException(sprintf(
'The decorator "%s" is not callable',
$definition->getName()
));
}
$decoratedDefinition = $definition->getDecoratedDefinition();
if (! $decoratedDefinition instanceof Definition) {
if (! $definition->getSubDefinitionName()) {
throw new DefinitionException('Decorators cannot be nested in another definition');
}
throw new DefinitionException(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);
}
/**
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = [])
{
return true;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace DI\Definition\Resolver;
use DI\Definition\Definition;
use DI\Definition\Exception\DefinitionException;
/**
* 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 DefinitionException 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.
*
* @return bool
*/
public function isResolvable(Definition $definition, array $parameters = []);
}

View File

@ -0,0 +1,74 @@
<?php
namespace DI\Definition\Resolver;
use DI\Definition\Definition;
use DI\Definition\EnvironmentVariableDefinition;
use DI\Definition\Exception\DefinitionException;
use DI\Definition\Helper\DefinitionHelper;
/**
* 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 = 'getenv')
{
$this->definitionResolver = $definitionResolver;
$this->variableReader = $variableReader;
}
/**
* Resolve an environment variable definition to a value.
*
* @param EnvironmentVariableDefinition $definition
*
* {@inheritdoc}
*/
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 DefinitionException(sprintf(
"The environment variable '%s' has not been defined",
$definition->getVariableName()
));
}
$value = $definition->getDefaultValue();
// Nested definition
if ($value instanceof DefinitionHelper) {
return $this->definitionResolver->resolve($value->getDefinition(''));
}
return $value;
}
/**
* @param EnvironmentVariableDefinition $definition
*
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = [])
{
return true;
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace DI\Definition\Resolver;
use DI\Definition\Definition;
use DI\Definition\Exception\DefinitionException;
use DI\Definition\FactoryDefinition;
use DI\Definition\Helper\DefinitionHelper;
use DI\Invoker\FactoryParameterResolver;
use Invoker\Exception\NotCallableException;
use Invoker\Exception\NotEnoughParametersException;
use Invoker\Invoker;
use Invoker\ParameterResolver\AssociativeArrayResolver;
use Invoker\ParameterResolver\NumericArrayResolver;
use Invoker\ParameterResolver\ResolverChain;
use 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.
*
* @param ContainerInterface $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
*
* {@inheritdoc}
*/
public function resolve(Definition $definition, array $parameters = [])
{
if (! $this->invoker) {
$parameterResolver = new ResolverChain([
new AssociativeArrayResolver,
new FactoryParameterResolver($this->container),
new NumericArrayResolver,
]);
$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);
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 DefinitionException(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 DefinitionException(sprintf(
'Entry "%s" cannot be resolved: factory %s',
$definition->getName(),
$e->getMessage()
));
} catch (NotEnoughParametersException $e) {
throw new DefinitionException(sprintf(
'Entry "%s" cannot be resolved: %s',
$definition->getName(),
$e->getMessage()
));
}
}
/**
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = [])
{
return true;
}
private function resolveExtraParams(array $params)
{
$resolved = [];
foreach ($params as $key => $value) {
if ($value instanceof DefinitionHelper) {
// As per ObjectCreator::injectProperty, use '' for an anonymous sub-definition
$value = $value->getDefinition('');
}
if (!$value instanceof Definition) {
$resolved[$key] = $value;
} else {
$resolved[$key] = $this->resolver->resolve($value);
}
}
return $resolved;
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace DI\Definition\Resolver;
use DI\Definition\Definition;
use DI\Definition\InstanceDefinition;
use DI\DependencyException;
use 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
*
* {@inheritdoc}
*/
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);
}
}
/**
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = [])
{
return true;
}
}

View File

@ -0,0 +1,247 @@
<?php
namespace DI\Definition\Resolver;
use DI\Definition\Definition;
use DI\Definition\Exception\DefinitionException;
use DI\Definition\Helper\DefinitionHelper;
use DI\Definition\ObjectDefinition;
use DI\Definition\ObjectDefinition\PropertyInjection;
use DI\DependencyException;
use DI\Proxy\ProxyFactory;
use Exception;
use ProxyManager\Proxy\LazyLoadingInterface;
use 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
*
* {@inheritdoc}
*/
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
*
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = [])
{
return $definition->isInstantiable();
}
/**
* Returns a proxy instance.
*
* @param ObjectDefinition $definition
* @param array $parameters
*
* @return LazyLoadingInterface Proxy instance
*/
private function createProxy(ObjectDefinition $definition, array $parameters)
{
/** @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 ObjectDefinition $definition
* @param array $parameters Optional parameters to use to create the instance.
*
* @throws DefinitionException
* @throws DependencyException
* @return object
*/
private function createInstance(ObjectDefinition $definition, array $parameters)
{
$this->assertClassExists($definition);
$classname = $definition->getClassName();
$classReflection = new ReflectionClass($classname);
$this->assertClassIsInstantiable($definition);
$constructorInjection = $definition->getConstructorInjection();
try {
$args = $this->parameterResolver->resolveParameters(
$constructorInjection,
$classReflection->getConstructor(),
$parameters
);
if (count($args) > 0) {
$object = $classReflection->newInstanceArgs($args);
} else {
$object = new $classname;
}
$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 (DefinitionException $e) {
throw DefinitionException::create($definition, sprintf(
'Entry "%s" cannot be resolved: %s',
$definition->getName(),
$e->getMessage()
));
}
if (! $object) {
throw new DependencyException(sprintf(
'Entry "%s" cannot be resolved: %s could not be constructed',
$definition->getName(),
$classReflection->getName()
));
}
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 DefinitionException
*/
private function injectProperty($object, PropertyInjection $propertyInjection)
{
$propertyName = $propertyInjection->getPropertyName();
$className = $propertyInjection->getClassName();
$className = $className ?: get_class($object);
$property = new ReflectionProperty($className, $propertyName);
$value = $propertyInjection->getValue();
if ($value instanceof DefinitionHelper) {
/** @var Definition $nestedDefinition */
$nestedDefinition = $value->getDefinition('');
try {
$value = $this->definitionResolver->resolve($nestedDefinition);
} 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);
}
}
if (! $property->isPublic()) {
$property->setAccessible(true);
}
$property->setValue($object, $value);
}
private function assertClassExists(ObjectDefinition $definition)
{
if (! $definition->classExists()) {
throw DefinitionException::create($definition, sprintf(
'Entry "%s" cannot be resolved: the class doesn\'t exist',
$definition->getName()
));
}
}
private function assertClassIsInstantiable(ObjectDefinition $definition)
{
if (! $definition->isInstantiable()) {
throw DefinitionException::create($definition, sprintf(
'Entry "%s" cannot be resolved: the class is not instantiable',
$definition->getName()
));
}
}
}

View File

@ -0,0 +1,120 @@
<?php
namespace DI\Definition\Resolver;
use DI\Definition\Exception\DefinitionException;
use DI\Definition\Helper\DefinitionHelper;
use 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;
}
/**
* @param MethodInjection $definition
* @param ReflectionMethod $method
* @param array $parameters
*
* @throws DefinitionException 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->isOptional()) {
$args[] = $this->getParameterDefaultValue($parameter, $method);
continue;
}
throw new DefinitionException(sprintf(
'Parameter $%s of %s has no value defined or guessable',
$parameter->getName(),
$this->getFunctionName($method)
));
}
if ($value instanceof DefinitionHelper) {
$nestedDefinition = $value->getDefinition('');
// If the container cannot produce the entry, we can use the default parameter value
if ($parameter->isOptional() && ! $this->definitionResolver->isResolvable($nestedDefinition)) {
$value = $this->getParameterDefaultValue($parameter, $method);
} else {
$value = $this->definitionResolver->resolve($nestedDefinition);
}
}
$args[] = &$value;
}
return $args;
}
/**
* Returns the default value of a function parameter.
*
* @param ReflectionParameter $parameter
* @param ReflectionMethod $function
*
* @throws DefinitionException 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 DefinitionException(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)
{
return $method->getName() . '()';
}
}

View File

@ -0,0 +1,133 @@
<?php
namespace DI\Definition\Resolver;
use DI\Definition\Definition;
use DI\Definition\Exception\DefinitionException;
use DI\Proxy\ProxyFactory;
use 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 $selfResolvingResolver;
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 DefinitionException If the definition cannot be resolved.
*
* @return mixed Value obtained from the definition.
*/
public function resolve(Definition $definition, array $parameters = [])
{
$definitionResolver = $this->getDefinitionResolver($definition);
return $definitionResolver->resolve($definition, $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.
*
* @return bool
*/
public function isResolvable(Definition $definition, array $parameters = [])
{
$definitionResolver = $this->getDefinitionResolver($definition);
return $definitionResolver->isResolvable($definition, $parameters);
}
/**
* Returns a resolver capable of handling the given definition.
*
* @param Definition $definition
*
* @throws \RuntimeException No definition resolver was found for this type of definition.
* @return DefinitionResolver
*/
private function getDefinitionResolver(Definition $definition)
{
switch (true) {
case $definition instanceof \DI\Definition\SelfResolvingDefinition:
if (! $this->selfResolvingResolver) {
$this->selfResolvingResolver = new SelfResolver($this->container);
}
return $this->selfResolvingResolver;
case $definition instanceof \DI\Definition\ObjectDefinition:
if (! $this->objectResolver) {
$this->objectResolver = new ObjectCreator($this, $this->proxyFactory);
}
return $this->objectResolver;
case $definition instanceof \DI\Definition\DecoratorDefinition:
if (! $this->decoratorResolver) {
$this->decoratorResolver = new DecoratorResolver($this->container, $this);
}
return $this->decoratorResolver;
case $definition instanceof \DI\Definition\FactoryDefinition:
if (! $this->factoryResolver) {
$this->factoryResolver = new FactoryResolver($this->container, $this);
}
return $this->factoryResolver;
case $definition instanceof \DI\Definition\ArrayDefinition:
if (! $this->arrayResolver) {
$this->arrayResolver = new ArrayResolver($this);
}
return $this->arrayResolver;
case $definition instanceof \DI\Definition\EnvironmentVariableDefinition:
if (! $this->envVariableResolver) {
$this->envVariableResolver = new EnvironmentVariableResolver($this);
}
return $this->envVariableResolver;
case $definition instanceof \DI\Definition\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));
}
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace DI\Definition\Resolver;
use DI\Definition\Definition;
use DI\Definition\SelfResolvingDefinition;
use Psr\Container\ContainerInterface;
/**
* Resolves self-resolving definitions.
*
* @since 5.3
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class SelfResolver implements DefinitionResolver
{
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* @param SelfResolvingDefinition $definition
*
* {@inheritdoc}
*/
public function resolve(Definition $definition, array $parameters = [])
{
return $definition->resolve($this->container);
}
/**
* @param SelfResolvingDefinition $definition
*
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = [])
{
return $definition->isResolvable($this->container);
}
}

View File

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

View File

@ -0,0 +1,273 @@
<?php
namespace DI\Definition\Source;
use DI\Annotation\Inject;
use DI\Annotation\Injectable;
use DI\Definition\EntryReference;
use DI\Definition\Exception\AnnotationException;
use DI\Definition\Exception\DefinitionException;
use DI\Definition\ObjectDefinition;
use DI\Definition\ObjectDefinition\MethodInjection;
use DI\Definition\ObjectDefinition\PropertyInjection;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\Reader;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use InvalidArgumentException;
use PhpDocReader\PhpDocReader;
use ReflectionClass;
use ReflectionMethod;
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 AnnotationReader implements DefinitionSource
{
/**
* @var Reader
*/
private $annotationReader;
/**
* @var PhpDocReader
*/
private $phpDocReader;
/**
* @var bool
*/
private $ignorePhpDocErrors;
public function __construct($ignorePhpDocErrors = false)
{
$this->ignorePhpDocErrors = (bool) $ignorePhpDocErrors;
}
/**
* {@inheritdoc}
* @throws AnnotationException
* @throws InvalidArgumentException The class doesn't exist
*/
public function getDefinition($name)
{
if (!class_exists($name) && !interface_exists($name)) {
return null;
}
$class = new ReflectionClass($name);
$definition = new ObjectDefinition($name);
$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;
}
/**
* 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
/** @var $annotation Inject */
$annotation = $this->getAnnotationReader()->getPropertyAnnotation($property, 'DI\Annotation\Inject');
if ($annotation === null) {
return null;
}
// @Inject("name") or look for @var content
$entryName = $annotation->getName() ?: $this->getPhpDocReader()->getPropertyClass($property);
if ($entryName === null) {
throw new AnnotationException(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 EntryReference($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->setConstructorInjection($methodInjection);
} else {
$objectDefinition->addMethodInjection($methodInjection);
}
}
}
private function getMethodInjection(ReflectionMethod $method)
{
// Look for @Inject annotation
/** @var $annotation Inject|null */
try {
$annotation = $this->getAnnotationReader()->getMethodAnnotation($method, 'DI\Annotation\Inject');
} catch (AnnotationException $e) {
throw new AnnotationException(sprintf(
'@Inject annotation on %s::%s is malformed. %s',
$method->getDeclaringClass()->getName(),
$method->getName(),
$e->getMessage()
), 0, $e);
}
$annotationParameters = $annotation ? $annotation->getParameters() : [];
// @Inject on constructor is implicit
if (! ($annotation || $method->isConstructor())) {
return null;
}
$parameters = [];
foreach ($method->getParameters() as $index => $parameter) {
$entryName = $this->getMethodParameter($index, $parameter, $annotationParameters);
if ($entryName !== null) {
$parameters[$index] = new EntryReference($entryName);
}
}
if ($method->isConstructor()) {
return MethodInjection::constructor($parameters);
} else {
return new MethodInjection($method->getName(), $parameters);
}
}
/**
* @param int $parameterIndex
* @param ReflectionParameter $parameter
* @param array $annotationParameters
*
* @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
$parameterClass = $parameter->getClass();
if ($parameterClass) {
return $parameterClass->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::registerAutoloadNamespace('DI\Annotation', __DIR__ . '/../../../');
$this->annotationReader = new SimpleAnnotationReader();
$this->annotationReader->addNamespace('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 $annotation Injectable|null */
$annotation = $this->getAnnotationReader()
->getClassAnnotation($class, 'DI\Annotation\Injectable');
} catch (UnexpectedValueException $e) {
throw new DefinitionException(sprintf(
'Error while reading @Injectable on %s: %s',
$class->getName(),
$e->getMessage()
), 0, $e);
}
if (! $annotation) {
return;
}
if ($annotation->getScope()) {
$definition->setScope($annotation->getScope());
}
if ($annotation->isLazy() !== null) {
$definition->setLazy($annotation->isLazy());
}
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace DI\Definition\Source;
use DI\Definition\EntryReference;
use DI\Definition\ObjectDefinition;
use DI\Definition\ObjectDefinition\MethodInjection;
/**
* Reads DI class definitions using reflection.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Autowiring implements DefinitionSource
{
/**
* {@inheritdoc}
*/
public function getDefinition($name)
{
if (!class_exists($name) && !interface_exists($name)) {
return null;
}
$definition = new ObjectDefinition($name);
// Constructor
$class = new \ReflectionClass($name);
$constructor = $class->getConstructor();
if ($constructor && $constructor->isPublic()) {
$definition->setConstructorInjection(
MethodInjection::constructor($this->getParametersDefinition($constructor))
);
}
return $definition;
}
/**
* Read the type-hinting from the parameters of the function.
*/
private function getParametersDefinition(\ReflectionFunctionAbstract $constructor)
{
$parameters = [];
foreach ($constructor->getParameters() as $index => $parameter) {
// Skip optional parameters
if ($parameter->isOptional()) {
continue;
}
$parameterClass = $parameter->getClass();
if ($parameterClass) {
$parameters[$index] = new EntryReference($parameterClass->getName());
}
}
return $parameters;
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace DI\Definition\Source;
use DI\Definition\CacheableDefinition;
use DI\Definition\Definition;
use Doctrine\Common\Cache\Cache;
/**
* Caches another definition source.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class CachedDefinitionSource implements DefinitionSource
{
/**
* Prefix for cache key, to avoid conflicts with other systems using the same cache.
* @var string
*/
const CACHE_PREFIX = 'DI\\Definition\\';
/**
* @var DefinitionSource
*/
private $source;
/**
* @var Cache
*/
private $cache;
public function __construct(DefinitionSource $source, Cache $cache)
{
$this->source = $source;
$this->cache = $cache;
}
/**
* {@inheritdoc}
*/
public function getDefinition($name)
{
// Look in cache
$definition = $this->fetchFromCache($name);
if ($definition === false) {
$definition = $this->source->getDefinition($name);
// Save to cache
if ($definition === null || ($definition instanceof CacheableDefinition)) {
$this->saveToCache($name, $definition);
}
}
return $definition;
}
/**
* @return Cache
*/
public function getCache()
{
return $this->cache;
}
/**
* Fetches a definition from the cache.
*
* @param string $name Entry name
* @return Definition|null|bool The cached definition, null or false if the value is not already cached
*/
private function fetchFromCache($name)
{
$cacheKey = self::CACHE_PREFIX . $name;
$data = $this->cache->fetch($cacheKey);
if ($data !== false) {
return $data;
}
return false;
}
/**
* Saves a definition to the cache.
*
* @param string $name Entry name
* @param Definition|null $definition
*/
private function saveToCache($name, Definition $definition = null)
{
$cacheKey = self::CACHE_PREFIX . $name;
$this->cache->save($cacheKey, $definition);
}
}

View File

@ -0,0 +1,150 @@
<?php
namespace DI\Definition\Source;
use DI\Definition\ArrayDefinition;
use DI\Definition\Definition;
use DI\Definition\FactoryDefinition;
use DI\Definition\Helper\DefinitionHelper;
use DI\Definition\ObjectDefinition;
use DI\Definition\ValueDefinition;
/**
* 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
*/
private $wildcardDefinitions;
/**
* @param array $definitions
*/
public function __construct(array $definitions = [])
{
$this->definitions = $definitions;
}
/**
* @param array $definitions DI definitions in a PHP array indexed by the definition name.
*/
public function addDefinitions(array $definitions)
{
// 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;
}
/**
* {@inheritdoc}
*/
public function getDefinition($name)
{
// Look for the definition by name
if (array_key_exists($name, $this->definitions)) {
return $this->castDefinition($this->definitions[$name], $name);
}
// 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) {
$definition = $this->castDefinition($definition, $name);
// For a class definition, we replace * in the class name with the matches
// *Interface -> *Impl => FooInterface -> FooImpl
if ($definition instanceof ObjectDefinition) {
array_shift($matches);
$definition->setClassName(
$this->replaceWildcards($definition->getClassName(), $matches)
);
}
return $definition;
}
}
return null;
}
/**
* @param mixed $definition
* @param string $name
* @return Definition
*/
private function castDefinition($definition, $name)
{
if ($definition instanceof DefinitionHelper) {
$definition = $definition->getDefinition($name);
} elseif (is_array($definition)) {
$definition = new ArrayDefinition($name, $definition);
} elseif ($definition instanceof \Closure) {
$definition = new FactoryDefinition($name, $definition);
} elseif (! $definition instanceof Definition) {
$definition = new ValueDefinition($name, $definition);
}
return $definition;
}
/**
* Replaces all the wildcards in the string with the given replacements.
* @param string $string
* @param string[] $replacements
* @return string
*/
private function replaceWildcards($string, array $replacements)
{
foreach ($replacements as $replacement) {
$pos = strpos($string, self::WILDCARD);
if ($pos !== false) {
$string = substr_replace($string, $replacement, $pos, 1);
}
}
return $string;
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace DI\Definition\Source;
use DI\Definition\Exception\DefinitionException;
/**
* 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)
{
// Lazy-loading to improve performances
$this->file = $file;
parent::__construct([]);
}
/**
* {@inheritdoc}
*/
public function getDefinition($name)
{
$this->initialize();
return parent::getDefinition($name);
}
/**
* Lazy-loading of the definitions.
* @throws DefinitionException
*/
private function initialize()
{
if ($this->initialized === true) {
return;
}
$definitions = require $this->file;
if (! is_array($definitions)) {
throw new DefinitionException("File {$this->file} should return an array of definitions");
}
$this->addDefinitions($definitions);
$this->initialized = true;
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace DI\Definition\Source;
use DI\Definition\Definition;
use DI\Definition\Exception\DefinitionException;
/**
* Source of definitions for entries of the container.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface DefinitionSource
{
/**
* Returns the DI definition for the entry name.
*
* @param string $name
*
* @throws DefinitionException An invalid definition was found.
* @return Definition|null
*/
public function getDefinition($name);
}

View File

@ -0,0 +1,15 @@
<?php
namespace DI\Definition\Source;
use 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);
}

View File

@ -0,0 +1,102 @@
<?php
namespace DI\Definition\Source;
use DI\Definition\Definition;
use DI\Definition\HasSubDefinition;
/**
* 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($name, $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 HasSubDefinition) {
$this->resolveSubDefinition($definition, $i);
}
return $definition;
}
}
return null;
}
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);
}
public function setRootDefinitionSource(DefinitionSource $rootSource)
{
$this->rootSource = $rootSource;
}
private function resolveSubDefinition(HasSubDefinition $definition, $currentIndex)
{
$subDefinitionName = $definition->getSubDefinitionName();
if ($subDefinitionName === $definition->getName()) {
// Extending itself: look in the next sources only (else infinite recursion)
$subDefinition = $this->getDefinition($subDefinitionName, $currentIndex + 1);
} else {
// Extending another definition: look from the root
$subDefinition = $this->rootSource->getDefinition($subDefinitionName);
}
if ($subDefinition) {
$definition->setSubDefinition($subDefinition);
}
}
public function setMutableDefinitionSource(MutableDefinitionSource $mutableSource)
{
$this->mutableSource = $mutableSource;
array_unshift($this->sources, $mutableSource);
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace DI\Definition;
use DI\DependencyException;
use DI\Scope;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* Definition of a string composed of other strings.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class StringDefinition implements Definition, SelfResolvingDefinition
{
/**
* Entry name.
* @var string
*/
private $name;
/**
* @var string
*/
private $expression;
/**
* @param string $name Entry name
* @param string $expression
*/
public function __construct($name, $expression)
{
$this->name = $name;
$this->expression = $expression;
}
/**
* @return string Entry name
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getScope()
{
return Scope::SINGLETON;
}
/**
* @return string
*/
public function getExpression()
{
return $this->expression;
}
public function resolve(ContainerInterface $container)
{
$expression = $this->expression;
$result = preg_replace_callback('#\{([^\{\}]+)\}#', function (array $matches) use ($container) {
try {
return $container->get($matches[1]);
} catch (NotFoundExceptionInterface $e) {
throw new DependencyException(sprintf(
"Error while parsing string expression for entry '%s': %s",
$this->getName(),
$e->getMessage()
), 0, $e);
}
}, $expression);
if ($result === null) {
throw new \RuntimeException(sprintf('An unknown error occurred while parsing the string definition: \'%s\'', $expression));
}
return $result;
}
public function isResolvable(ContainerInterface $container)
{
return true;
}
public function __toString()
{
return $this->expression;
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace DI\Definition;
use DI\Scope;
use 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 string $name Entry name
* @param mixed $value
*/
public function __construct($name, $value)
{
$this->name = $name;
$this->value = $value;
}
/**
* @return string Entry name
*/
public function getName()
{
return $this->name;
}
/**
* A value definition is like a constant, there is nothing to compute, the value is the same for everyone.
*
* {@inheritdoc}
*/
public function getScope()
{
return Scope::SINGLETON;
}
/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}
public function resolve(ContainerInterface $container)
{
return $this->getValue();
}
public function isResolvable(ContainerInterface $container)
{
return true;
}
public function __toString()
{
return sprintf('Value (%s)', var_export($this->value, true));
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace DI;
use Interop\Container\Exception\ContainerException;
/**
* Exception for the Container.
*/
class DependencyException extends \Exception implements ContainerException
{
}

View File

@ -0,0 +1,21 @@
<?php
namespace 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.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface RequestedEntry
{
/**
* Returns the name of the entry that was requested by the container.
*
* @return string
*/
public function getName();
}

View File

@ -0,0 +1,27 @@
<?php
namespace DI;
/**
* Describes the basic interface of a factory.
*
* @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 = []);
}

View File

@ -0,0 +1,66 @@
<?php
namespace DI\Invoker;
use DI\Definition\Helper\DefinitionHelper;
use DI\Definition\Resolver\DefinitionResolver;
use 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;
}
/**
* {@inheritdoc}
*/
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 (! $value instanceof DefinitionHelper) {
continue;
}
$definition = $value->getDefinition('');
$value = $this->definitionResolver->resolve($definition);
if (is_int($key)) {
// Indexed by position
$resolvedParameters[$key] = $value;
} else {
// Indexed by parameter name
// TODO optimize?
$reflectionParameters = $reflection->getParameters();
foreach ($reflectionParameters as $reflectionParameter) {
if ($key === $reflectionParameter->name) {
$resolvedParameters[$reflectionParameter->getPosition()] = $value;
}
}
}
}
return $resolvedParameters;
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace DI\Invoker;
use Invoker\ParameterResolver\ParameterResolver;
use Psr\Container\ContainerInterface;
use ReflectionFunctionAbstract;
/**
* 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
) {
$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) {
continue;
}
if ($parameterClass->name === 'Interop\Container\ContainerInterface'
|| $parameterClass->name === 'Psr\Container\ContainerInterface') {
$resolvedParameters[$index] = $this->container;
} elseif ($parameterClass->name === 'DI\Factory\RequestedEntry') {
// By convention the second parameter is the definition
$resolvedParameters[$index] = $providedParameters[1];
} elseif ($this->container->has($parameterClass->name)) {
$resolvedParameters[$index] = $this->container->get($parameterClass->name);
}
}
return $resolvedParameters;
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace DI;
/**
* Invoke a callable.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface InvokerInterface extends \Invoker\InvokerInterface
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace DI;
use Interop\Container\Exception\NotFoundException as BaseNotFoundException;
/**
* Exception thrown when a class or a value is not found in the container.
*/
class NotFoundException extends \Exception implements BaseNotFoundException
{
}

View File

@ -0,0 +1,84 @@
<?php
namespace DI\Proxy;
use ProxyManager\Configuration;
use ProxyManager\Factory\LazyLoadingValueHolderFactory;
use ProxyManager\FileLocator\FileLocator;
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
use ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy;
/**
* 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
*/
private $proxyDirectory;
/**
* @var LazyLoadingValueHolderFactory|null
*/
private $proxyManager;
public function __construct($writeProxiesToFile, $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
*
* @return \ProxyManager\Proxy\LazyLoadingInterface
*/
public function createProxy($className, \Closure $initializer)
{
$this->createProxyManager();
return $this->proxyManager->createProxy($className, $initializer);
}
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:~1.0".');
}
$config = new Configuration();
if ($this->writeProxiesToFile) {
$config->setProxiesTargetDir($this->proxyDirectory);
$config->setGeneratorStrategy(new FileWriterGeneratorStrategy(new FileLocator($this->proxyDirectory)));
spl_autoload_register($config->getProxyAutoloader());
} else {
$config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
}
$this->proxyManager = new LazyLoadingValueHolderFactory($config);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace DI;
/**
* Scope enum.
*
* The scope defines the lifecycle of an entry.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Scope
{
/**
* A singleton entry will be computed once and shared.
*
* For a class, only a single instance of the class will be created.
*/
const SINGLETON = 'singleton';
/**
* A prototype entry will be recomputed each time it is asked.
*
* For a class, this will create a new instance each time.
*/
const PROTOTYPE = 'prototype';
/**
* Method kept for backward compatibility, use the constant instead.
*
* @return string
*/
public static function SINGLETON()
{
return self::SINGLETON;
}
/**
* Method kept for backward compatibility, use the constant instead.
*
* @return string
*/
public static function PROTOTYPE()
{
return self::PROTOTYPE;
}
}

View File

@ -0,0 +1,174 @@
<?php
namespace DI;
use DI\Definition\EntryReference;
use DI\Definition\Helper\ArrayDefinitionExtensionHelper;
use DI\Definition\Helper\EnvironmentVariableDefinitionHelper;
use DI\Definition\Helper\FactoryDefinitionHelper;
use DI\Definition\Helper\ObjectDefinitionHelper;
use DI\Definition\Helper\StringDefinitionHelper;
use DI\Definition\Helper\ValueDefinitionHelper;
if (! function_exists('DI\value')) {
/**
* Helper for defining a value.
*
* @param mixed $value
*
* @return ValueDefinitionHelper
*/
function value($value)
{
return new ValueDefinitionHelper($value);
}
}
if (! function_exists('DI\object')) {
/**
* 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.
*
* @return ObjectDefinitionHelper
*/
function object($className = null)
{
return new ObjectDefinitionHelper($className);
}
}
if (! function_exists('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.
*
* @return FactoryDefinitionHelper
*/
function factory($factory)
{
return new FactoryDefinitionHelper($factory);
}
}
if (! function_exists('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.
*
* @return FactoryDefinitionHelper
*/
function decorate($callable)
{
return new FactoryDefinitionHelper($callable, true);
}
}
if (! function_exists('DI\get')) {
/**
* Helper for referencing another container entry in an object definition.
*
* @param string $entryName
*
* @return EntryReference
*/
function get($entryName)
{
return new EntryReference($entryName);
}
}
if (! function_exists('DI\link')) {
/**
* Helper for referencing another container entry in an object definition.
*
* @deprecated \DI\link() has been replaced by \DI\get()
*
* @param string $entryName
*
* @return EntryReference
*/
function link($entryName)
{
return new EntryReference($entryName);
}
}
if (! function_exists('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.
*
* @return EnvironmentVariableDefinitionHelper
*/
function env($variableName, $defaultValue = null)
{
// Only mark as optional if the default value was *explicitly* provided.
$isOptional = 2 === func_num_args();
return new EnvironmentVariableDefinitionHelper($variableName, $isOptional, $defaultValue);
}
}
if (! function_exists('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.
*
* @return ArrayDefinitionExtensionHelper
*
* @since 5.0
*/
function add($values)
{
if (! is_array($values)) {
$values = [$values];
}
return new ArrayDefinitionExtensionHelper($values);
}
}
if (! function_exists('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.
*
* @return StringDefinitionHelper
*
* @since 5.0
*/
function string($expression)
{
return new StringDefinitionHelper((string) $expression);
}
}

View File

@ -0,0 +1,16 @@
Copyright (C) 2015 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.

View File

@ -0,0 +1,58 @@
# PhpDocReader
[![Build Status](https://img.shields.io/travis/PHP-DI/PhpDocReader.svg)](https://travis-ci.org/mnapoli/PhpDocReader)
![](https://img.shields.io/packagist/dt/PHP-DI/phpdoc-reader.svg)
This project is used by:
- [PHP-DI](http://php-di.org/)
- [phockito-unit-php-di](https://github.com/balihoo/phockito-unit-php-di)
Fork the README to add your project here.
## Features
PhpDocReader parses `@var` and `@param` values in PHP docblocks:
```php
use My\Cache\Backend;
class Cache
{
/**
* @var Backend
*/
protected $backend;
/**
* @param Backend $backend
*/
public function __construct($backend)
{
}
}
```
It supports namespaced class names with the same resolution rules as PHP:
- fully qualified name (starting with `\`)
- imported class name (eg. `use My\Cache\Backend;`)
- relative class name (from the current namespace, like `SubNamespace\MyClass`)
- aliased class name (eg. `use My\Cache\Backend as FooBar;`)
Primitive types (`@var string`) are ignored (returns null), only valid class names are returned.
## Usage
```php
$reader = new PhpDocReader();
// Read a property type (@var phpdoc)
$property = new ReflectionProperty($className, $propertyName);
$propertyClass = $reader->getPropertyClass($property);
// Read a parameter type (@param phpdoc)
$parameter = new ReflectionParameter(array($className, $methodName), $parameterName);
$parameterClass = $reader->getParameterClass($parameter);
```

View File

@ -0,0 +1,23 @@
{
"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": {
"PhpDocReader\\": "src/PhpDocReader"
}
},
"autoload-dev": {
"psr-4": {
"UnitTest\\PhpDocReader\\": "tests/"
}
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.6"
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace PhpDocReader;
/**
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AnnotationException extends \Exception
{
}

View File

@ -0,0 +1,311 @@
<?php
namespace PhpDocReader;
use PhpDocReader\PhpParser\UseStatementParser;
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
use Reflector;
/**
* PhpDoc reader
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class PhpDocReader
{
/**
* @var UseStatementParser
*/
private $parser;
private $ignoredTypes = array(
'bool',
'boolean',
'string',
'int',
'integer',
'float',
'double',
'array',
'object',
'callable',
'resource',
'mixed',
);
/**
* Enable or disable throwing errors when PhpDoc Errors occur (when parsing annotations)
*
* @var bool
*/
private $ignorePhpDocErrors;
/**
*
* @param bool $ignorePhpDocErrors
*/
public function __construct($ignorePhpDocErrors = false)
{
$this->parser = new UseStatementParser();
$this->ignorePhpDocErrors = $ignorePhpDocErrors;
}
/**
* Parse the docblock of the property to get the class of the var annotation.
*
* @param ReflectionProperty $property
*
* @throws AnnotationException
* @return string|null Type of the property (content of var annotation)
*
* @deprecated Use getPropertyClass instead.
*/
public function getPropertyType(ReflectionProperty $property)
{
return $this->getPropertyClass($property);
}
/**
* Parse the docblock of the property to get the class of the var annotation.
*
* @param ReflectionProperty $property
*
* @throws AnnotationException
* @return string|null Type of the property (content of var annotation)
*/
public function getPropertyClass(ReflectionProperty $property)
{
// Get the content of the @var annotation
if (preg_match('/@var\s+([^\s]+)/', $property->getDocComment(), $matches)) {
list(, $type) = $matches;
} else {
return null;
}
// Ignore primitive types
if (in_array($type, $this->ignoredTypes)) {
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->classExists($type) && !$this->ignorePhpDocErrors) {
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 = ltrim($type, '\\');
return $type;
}
/**
* Parse the docblock of the property to get the class of the param annotation.
*
* @param ReflectionParameter $parameter
*
* @throws AnnotationException
* @return string|null Type of the property (content of var annotation)
*
* @deprecated Use getParameterClass instead.
*/
public function getParameterType(ReflectionParameter $parameter)
{
return $this->getParameterClass($parameter);
}
/**
* Parse the docblock of the property to get the class of the param annotation.
*
* @param ReflectionParameter $parameter
*
* @throws AnnotationException
* @return string|null Type of the property (content of var annotation)
*/
public function getParameterClass(ReflectionParameter $parameter)
{
// Use reflection
$parameterClass = $parameter->getClass();
if ($parameterClass !== null) {
return $parameterClass->name;
}
$parameterName = $parameter->name;
// Get the content of the @param annotation
$method = $parameter->getDeclaringFunction();
if (preg_match('/@param\s+([^\s]+)\s+\$' . $parameterName . '/', $method->getDocComment(), $matches)) {
list(, $type) = $matches;
} else {
return null;
}
// Ignore primitive types
if (in_array($type, $this->ignoredTypes)) {
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->classExists($type) && !$this->ignorePhpDocErrors) {
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 = ltrim($type, '\\');
return $type;
}
/**
* Attempts to resolve the FQN of the provided $type based on the $class and $member context.
*
* @param string $type
* @param ReflectionClass $class
* @param Reflector $member
*
* @return string|null Fully qualified name of the type, or null if it could not be resolved
*/
private function tryResolveFqn($type, ReflectionClass $class, Reflector $member)
{
$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);
} else {
return $uses[$loweredAlias];
}
} elseif ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
return $class->getNamespaceName() . '\\' . $type;
} elseif (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
// Class namespace
return $uses['__NAMESPACE__'] . '\\' . $type;
} elseif ($this->classExists($type)) {
// No namespace
return $type;
}
if (version_compare(phpversion(), '5.4.0', '<')) {
return null;
} else {
// 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.
*
* @param string $type
* @param ReflectionClass $class
* @param Reflector $member
*
* @return string|null Fully qualified name of the type, or null if it could not be resolved
*/
private function tryResolveFqnInTraits($type, ReflectionClass $class, Reflector $member)
{
/** @var ReflectionClass[] $traits */
$traits = array();
// 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;
} elseif ($member instanceof ReflectionMethod && !$trait->hasMethod($member->name)) {
continue;
} elseif ($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;
}
/**
* @param string $class
* @return bool
*/
private function classExists($class)
{
return class_exists($class) || interface_exists($class);
}
}

View File

@ -0,0 +1,159 @@
<?php
namespace 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 array
*/
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 A list with all found use statements.
*/
public function parseUseStatements($namespaceName)
{
$statements = array();
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 = array();
}
return $statements;
}
/**
* Gets the next non whitespace and non comment token.
*
* @param boolean $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
* If FALSE then only whitespace and normal comments are skipped.
*
* @return array|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 A list with all found class names for a use statement.
*/
private function parseUseStatement()
{
$class = '';
$alias = '';
$statements = array();
$explicitAlias = false;
while (($token = $this->next())) {
$isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR;
if (!$explicitAlias && $isNameToken) {
$class .= $token[1];
$alias = $token[1];
} elseif ($explicitAlias && $isNameToken) {
$alias .= $token[1];
} elseif ($token[0] === T_AS) {
$explicitAlias = true;
$alias = '';
} elseif ($token === ',') {
$statements[strtolower($alias)] = $class;
$class = '';
$alias = '';
$explicitAlias = false;
} elseif ($token === ';') {
$statements[strtolower($alias)] = $class;
break;
} 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)) {
$name .= $token[1];
}
return $name;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace PhpDocReader\PhpParser;
use SplFileObject;
/**
* Parses a file for "use" 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 UseStatementParser
{
/**
* @return array A list with use statements in the form (Alias => FQN).
*/
public function parseUseStatements(\ReflectionClass $class)
{
if (false === $filename = $class->getFilename()) {
return array();
}
$content = $this->getFileContent($filename, $class->getStartLine());
if (null === $content) {
return array();
}
$namespace = preg_quote($class->getNamespaceName());
$content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
$tokenizer = new TokenParser('<?php ' . $content);
$statements = $tokenizer->parseUseStatements($class->getNamespaceName());
return $statements;
}
/**
* Gets the content of the file right up to the given line number.
*
* @param string $filename The name of the file to load.
* @param integer $lineNumber The number of lines to read from file.
*
* @return string The content of the file.
*/
private function getFileContent($filename, $lineNumber)
{
if ( ! is_file($filename)) {
return null;
}
$content = '';
$lineCnt = 0;
$file = new SplFileObject($filename);
while (!$file->eof()) {
if ($lineCnt++ == $lineNumber) {
break;
}
$content .= $file->fgets();
}
return $content;
}
}