update Piwik to version 2.16 (fixes #91)

This commit is contained in:
oliver 2016-04-10 18:55:57 +02:00
commit d885a4baa9
5833 changed files with 418860 additions and 226988 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,131 @@
<?php
namespace Invoker;
use Interop\Container\ContainerInterface;
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 new NotCallableException(sprintf(
'%s is not a callable',
is_object($callable) ? 'Instance of ' . get_class($callable) : var_export($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)) {
if ($this->container->has($callable)) {
return $this->container->get($callable);
} else {
throw new NotCallableException(sprintf(
'"%s" is neither a callable nor a valid container entry',
$callable
));
}
}
// 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])) {
if ($this->container->has($callable[0])) {
// Replace the container entry name by the actual object
$callable[0] = $this->container->get($callable[0]);
return $callable;
} elseif ($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]
));
} else {
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,12 @@
<?php
namespace Invoker\Exception;
/**
* The given callable is not actually callable.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class NotCallableException extends InvocationException
{
}

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,57 @@
<?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;
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,3 @@
---
layout: 404
---

View file

@ -0,0 +1,52 @@
# Contributing
[![Build Status](https://travis-ci.org/mnapoli/PHP-DI.png?branch=master)](https://travis-ci.org/mnapoli/PHP-DI) [![Coverage Status](https://coveralls.io/repos/mnapoli/PHP-DI/badge.png?branch=master)](https://coveralls.io/r/mnapoli/PHP-DI?branch=master)
PHP-DI is license under the MIT License.
## Set up
* Check out the sources using git or download them
```bash
$ git clone https://github.com/mnapoli/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/mnapoli/PHP-DI)
- Resolve issues: [issue list](https://github.com/mnapoli/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,23 @@
---
layout: home
---
PHP-DI is a Dependency Injection Container made for humans.
[![Build Status](https://img.shields.io/travis/mnapoli/PHP-DI.svg?style=flat-square)](https://travis-ci.org/mnapoli/PHP-DI)
[![HHVM Status](https://img.shields.io/hhvm/mnapoli/PHP-DI.svg?style=flat-square)](http://hhvm.h4cc.de/package/mnapoli/php-di)
[![Coverage Status](https://img.shields.io/coveralls/mnapoli/PHP-DI/master.svg?style=flat-square)](https://coveralls.io/r/mnapoli/PHP-DI?branch=master)
[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/mnapoli/PHP-DI.svg?style=flat-square)](https://scrutinizer-ci.com/g/mnapoli/PHP-DI/?branch=master)
[![Latest Version](https://img.shields.io/github/release/mnapoli/PHP-DI.svg?style=flat-square)](https://packagist.org/packages/mnapoli/php-di)
[![Total Downloads](https://img.shields.io/packagist/dt/mnapoli/PHP-DI.svg?style=flat-square)](https://packagist.org/packages/mnapoli/php-di)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/mnapoli/PHP-DI.svg)](http://isitmaintained.com/project/mnapoli/PHP-DI "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/mnapoli/PHP-DI.svg)](http://isitmaintained.com/project/mnapoli/PHP-DI "Percentage of issues still open")
It is 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/mnapoli/PHP-DI.png)](https://gitter.im/mnapoli/PHP-DI)
Support is always appreciated: [![Gratipay](https://img.shields.io/gratipay/JSFiddle.svg)]()

View file

@ -0,0 +1,279 @@
# Change log
## 5.0
Improvements:
- Lighter package: requires 4 less Composer dependencies by default
- [#207](https://github.com/mnapoli/PHP-DI/issues/207): Support for `DI\link()` in arrays
- [#208](https://github.com/mnapoli/PHP-DI/issues/208): Support for nested definitions
- [#226](https://github.com/mnapoli/PHP-DI/pull/226): `DI\factory()` can now be omitted with closures:
```php
// before
'My\Class' => DI\factory(function () {
return new Foo();
})
// now (optional shortcut)
'My\Class' => function () {
return new Foo();
}
```
- [#235](https://github.com/mnapoli/PHP-DI/issues/235) `DI\link()` is now deprecated in favor of `DI\get()`. There is no BC break as `DI\link()` still works.
- [#193](https://github.com/mnapoli/PHP-DI/issues/193) `DI\object()->method()` now supports calling the same method twice (or more).
- [#248](https://github.com/mnapoli/PHP-DI/issues/248): New `DI\decorate()` function to decorate a previously defined entry:
```php
// file A.php
ProductDaoInterface::class => DI\get(ProductDaoDatabase::class)
// file B.php
ProductDaoInterface::class => DI\decorate(function ($previous, ContainerInterface $c) {
return new ProductDaoCached($previous, $c->get('cache.backend'));
})
```
BC breaks:
- [#251](https://github.com/mnapoli/PHP-DI/issues/251) Annotations are disabled by default, if you use annotations enable them with `$containerBuilder->useAnnotations(true)`.
- [#198](https://github.com/mnapoli/PHP-DI/issues/198) `ocramius/proxy-manager` is not installed by default anymore, you need to require it in `composer.json` 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/mnapoli/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)
- `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).
- `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/mnapoli/PHP-DI/issues/185) Support for invokable objects in `Container::call()`
- [#192](https://github.com/mnapoli/PHP-DI/pull/192) Support for invokable classes in `Container::call()` (will instantiate the class)
- [#184](https://github.com/mnapoli/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/mnapoli/PHP-DI/pull/176) New definition type for reading environment variables: `DI\env()`
- [#181](https://github.com/mnapoli/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/mnapoli/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/mnapoli/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/mnapoli/PHP-DI/pull/162) Added `Container::call()` to call functions with dependency injection
* [#156](https://github.com/mnapoli/PHP-DI/issues/156) Wildcards (`*`) in definitions
* [#164](https://github.com/mnapoli/PHP-DI/issues/164) Prototype scope is now available for `factory()` definitions too
* FIXED [#168](https://github.com/mnapoli/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/mnapoli/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/mnapoli/PHP-DI/issues/138) [Container-interop](https://github.com/container-interop/container-interop) compliance
* [#143](https://github.com/mnapoli/PHP-DI/issues/143) Much more explicit exception messages
* [#157](https://github.com/mnapoli/PHP-DI/issues/157) HHVM support
* [#158](https://github.com/mnapoli/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/mnapoli/PHP-DI/issues/115) Added `Container::has()`
* [#142](https://github.com/mnapoli/PHP-DI/issues/142) Added `Container::make()` to resolve an entry
* [#127](https://github.com/mnapoli/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/mnapoli/PHP-DI/issues/128) Configure entry aliases
* [#110](https://github.com/mnapoli/PHP-DI/issues/110) XML definitions are not supported anymore
* [#122](https://github.com/mnapoli/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/mnapoli/PHP-DI/issues/99) Fixed "`@param` with PHP internal type throws exception"
## 3.5.1
* FIXED [#126](https://github.com/mnapoli/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/mnapoli/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/mnapoli/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/mnapoli/PHP-DI/issues/98) `ContainerBuilder` is now fluent thanks to [@drdamour](https://github.com/drdamour)
* [#101](https://github.com/mnapoli/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/mnapoli/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/mnapoli/PHP-DI/pull/89)).
* [#86](https://github.com/mnapoli/PHP-DI/issues/86): Optimized definition lookup (faster)
* FIXED [#87](https://github.com/mnapoli/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/mnapoli/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/mnapoli/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/mnapoli/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/mnapoli/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/mnapoli/PHP-DI/issues/76): Definition conflict when setting a closure for a class name
## 3.0.5
* FIXED [#70](https://github.com/mnapoli/PHP-DI/issues/70): Definition conflict when setting a value for a class name
## 3.0.4
* FIXED [#69](https://github.com/mnapoli/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/mnapoli/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/mnapoli/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/mnapoli/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,43 @@
{
"name": "mnapoli/php-di",
"type": "library",
"description": "PHP-DI is a Container that makes Dependency Injection as practical as possible in PHP",
"keywords": ["di", "dependency injection", "container"],
"homepage": "http://mnapoli.github.com/PHP-DI/",
"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/"
}
},
"require": {
"php": ">=5.3.3",
"doctrine/annotations": "~1.2",
"doctrine/cache": "~1.0",
"mnapoli/phpdocreader": "~1.3",
"container-interop/container-interop": "~1.0",
"php-di/invoker": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.5",
"mnapoli/phpunit-easymock": "~0.1.4",
"ocramius/proxy-manager": "~0.5"
},
"suggest": {
"ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~0.5)"
},
"extra": {
"branch-alias": {
"dev-master": "5.0.x-dev"
}
}
}

View file

@ -0,0 +1,95 @@
baseUrl: http://php-di.org
scripts:
before:
- lessc --clean-css website/less/main.less website/css/all.min.css
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:
text: Defining injections
url: doc/definition.html
annotations:
text: Annotations
url: doc/annotations.html
definition-overriding:
text: Definition overriding
url: doc/definition-overriding.html
scopes:
text: Scopes
url: doc/scopes.html
frameworks:
section: Frameworks
items:
symfony2:
text: Symfony 2
url: doc/frameworks/symfony2.html
zf1:
text: Zend Framework 1
url: doc/frameworks/zf1.html
silly:
text: Silly
url: doc/frameworks/silly.html
advanced:
section: Advanced topics
items:
performances:
text: Performances
url: doc/performances.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
migration:
section: Migration guides
items:
4:
text: From PHP-DI 3.x to 4.0
url: doc/migration/4.0.html
5:
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/mnapoli/PHP-DI/tree/3.x/doc
v4:
text: PHP-DI 4.x
absoluteUrl: https://github.com/mnapoli/PHP-DI/tree/4.x/doc

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,95 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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 = array();
/**
* @param array $values
*/
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,74 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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 boolean|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 = (boolean) $values['lazy'];
}
}
/**
* @return string|null
*/
public function getScope()
{
return $this->scope;
}
/**
* @return boolean|null
*/
public function isLazy()
{
return $this->lazy;
}
}

View file

@ -0,0 +1,330 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI;
use DI\Definition\ObjectDefinition;
use DI\Definition\Definition;
use DI\Definition\FactoryDefinition;
use DI\Definition\InstanceDefinition;
use DI\Definition\Resolver\ResolverDispatcher;
use DI\Definition\Source\CachedDefinitionSource;
use DI\Definition\Source\DefinitionSource;
use DI\Definition\Source\MutableDefinitionSource;
use DI\Definition\Helper\DefinitionHelper;
use DI\Definition\Resolver\DefinitionResolver;
use DI\Invoker\DefinitionParameterResolver;
use DI\Proxy\ProxyFactory;
use Exception;
use Interop\Container\ContainerInterface;
use InvalidArgumentException;
use Invoker\Invoker;
use Invoker\ParameterResolver\AssociativeArrayResolver;
use Invoker\ParameterResolver\Container\TypeHintContainerResolver;
use Invoker\ParameterResolver\NumericArrayResolver;
use Invoker\ParameterResolver\ResolverChain;
/**
* Dependency Injection Container.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Container implements ContainerInterface, FactoryInterface, \DI\InvokerInterface
{
/**
* Map of entries with Singleton scope that are already resolved.
* @var array
*/
private $singletonEntries = array();
/**
* @var DefinitionSource
*/
private $definitionSource;
/**
* @var DefinitionResolver
*/
private $definitionResolver;
/**
* Array of entries being resolved. Used to avoid circular dependencies and infinite loops.
* @var array
*/
private $entriesBeingResolved = array();
/**
* @var \Invoker\InvokerInterface|null
*/
private $invoker;
/**
* 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
) {
$wrapperContainer = $wrapperContainer ?: $this;
$this->definitionSource = $definitionSource;
$this->definitionResolver = new ResolverDispatcher($wrapperContainer, $proxyFactory);
// Auto-register the container
$this->singletonEntries['DI\Container'] = $this;
$this->singletonEntries['DI\ContainerInterface'] = $this;
$this->singletonEntries['DI\FactoryInterface'] = $this;
$this->singletonEntries['DI\InvokerInterface'] = $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->definitionSource->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;
}
/**
* 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 = array())
{
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->definitionSource->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->definitionSource->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 = array())
{
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 = array())
{
$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');
}
// Clear existing entry if it exists
if (array_key_exists($name, $this->singletonEntries)) {
unset($this->singletonEntries[$name]);
}
$this->definitionSource->addDefinition($definition);
}
private function getInvoker()
{
if (! $this->invoker) {
$parameterResolver = new ResolverChain(array(
new NumericArrayResolver,
new AssociativeArrayResolver,
new DefinitionParameterResolver($this->definitionResolver),
new TypeHintContainerResolver($this),
));
$this->invoker = new Invoker($parameterResolver, $this);
}
return $this->invoker;
}
}

View file

@ -0,0 +1,258 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI;
use DI\Definition\Source\AnnotationReader;
use DI\Definition\Source\DefinitionArray;
use DI\Definition\Source\CachedDefinitionSource;
use DI\Definition\Source\DefinitionSource;
use DI\Definition\Source\DefinitionFile;
use DI\Definition\Source\Autowiring;
use DI\Definition\Source\SourceChain;
use DI\Proxy\ProxyFactory;
use Doctrine\Common\Cache\Cache;
use Interop\Container\ContainerInterface;
use InvalidArgumentException;
/**
* 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 boolean
*/
private $useAutowiring = true;
/**
* @var boolean
*/
private $useAnnotations = false;
/**
* @var boolean
*/
private $ignorePhpDocErrors = false;
/**
* @var Cache
*/
private $cache;
/**
* If true, write the proxies to disk to improve performances.
* @var boolean
*/
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 = array();
/**
* 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);
$containerClass = $this->containerClass;
return new $containerClass($source, $proxyFactory, $this->wrapperContainer);
}
/**
* Enable or disable the use of autowiring to guess injections.
*
* Enabled by default.
*
* @param boolean $bool
* @return ContainerBuilder
*/
public function useAutowiring($bool)
{
$this->useAutowiring = $bool;
return $this;
}
/**
* Enable or disable the use of annotations to guess injections.
*
* Disabled by default.
*
* @param boolean $bool
* @return ContainerBuilder
*/
public function useAnnotations($bool)
{
$this->useAnnotations = $bool;
return $this;
}
/**
* Enable or disable ignoring phpdoc errors (non-existent classes in `@param` or `@var`)
*
* @param boolean $bool
* @return ContainerBuilder
*/
public function ignorePhpDocErrors($bool)
{
$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->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 boolean $writeToFile If true, write the proxies to disk to improve performances
* @param string|null $proxyDirectory Directory where to write the proxies
* @return ContainerBuilder
*
* @throws InvalidArgumentException when writeToFile is set to true and the proxy directory is null
*/
public function writeProxiesToFile($writeToFile, $proxyDirectory = null)
{
$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->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)
{
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;
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI;
use DI\Definition\Definition;
use DI\Definition\Dumper\DefinitionDumperDispatcher;
/**
* Debug utilities.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Debug
{
/**
* Dump the definition to a string.
*
* @param Definition $definition
*
* @return string
*/
public static function dumpDefinition(Definition $definition)
{
static $dumper;
if (! $dumper) {
$dumper = new DefinitionDumperDispatcher();
}
return $dumper->dump($definition);
}
}

View file

@ -0,0 +1,82 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition;
use DI\Scope;
/**
* Describe a function call.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
abstract class AbstractFunctionCallDefinition implements Definition
{
/**
* @var array
*/
protected $parameters = array();
/**
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
/**
* @param int $index Position of the parameter (starting at 0)
* @return mixed|null Value to inject, or null if no injection defined.
*/
public function hasParameter($index)
{
return array_key_exists($index, $this->parameters);
}
/**
* @param int $index Position of the parameter (starting at 0)
* @throws \InvalidArgumentException
* @return mixed Value to inject
*/
public function getParameter($index)
{
if (! array_key_exists($index, $this->parameters)) {
throw new \InvalidArgumentException('There is no parameter value for index ' . $index);
}
return $this->parameters[$index];
}
/**
* Replace the parameters of the definition by a new array of parameters.
*
* @param array $parameters
*/
public function replaceParameters(array $parameters)
{
$this->parameters = $parameters;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return null;
}
/**
* {@inheritdoc}
*/
public function getScope()
{
return Scope::PROTOTYPE;
}
}

View file

@ -0,0 +1,66 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition;
use DI\Scope;
/**
* Defines an alias from an entry to another.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AliasDefinition implements CacheableDefinition
{
/**
* 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;
}
}

View file

@ -0,0 +1,66 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition;
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;
}
}

View file

@ -0,0 +1,61 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,19 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition;
/**
* Cacheable definition
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface CacheableDefinition extends Definition
{
}

View file

@ -0,0 +1,48 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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;
}
}

View file

@ -0,0 +1,32 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition;
/**
* Definition
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface Definition
{
/**
* 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();
}

View file

@ -0,0 +1,48 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Dumper;
use DI\Definition\AliasDefinition;
use DI\Definition\Definition;
/**
* Dumps alias definitions.
*
* @since 4.1
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AliasDefinitionDumper implements DefinitionDumper
{
/**
* {@inheritdoc}
*/
public function dump(Definition $definition)
{
if (! $definition instanceof AliasDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition dumper is only compatible with AliasDefinition objects, %s given',
get_class($definition)
));
}
if ($definition->getName()) {
return sprintf(
"get(%s => %s)",
$definition->getName(),
$definition->getTargetEntryName()
);
}
return sprintf(
"get(%s)",
$definition->getTargetEntryName()
);
}
}

View file

@ -0,0 +1,65 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Dumper;
use DI\Debug;
use DI\Definition\ArrayDefinition;
use DI\Definition\Definition;
use DI\Definition\Helper\DefinitionHelper;
/**
* Dumps array definitions.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ArrayDefinitionDumper implements DefinitionDumper
{
/**
* {@inheritdoc}
*/
public function dump(Definition $definition)
{
if (! $definition instanceof ArrayDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition dumper is only compatible with ArrayDefinition objects, %s given',
get_class($definition)
));
}
$str = '[' . PHP_EOL;
foreach ($definition->getValues() as $key => $value) {
if (is_string($key)) {
$key = "'" . $key . "'";
}
$str .= ' ' . $key . ' => ';
if ($value instanceof DefinitionHelper) {
$nestedDefinition = Debug::dumpDefinition($value->getDefinition(''));
$str .= $this->indent($nestedDefinition);
} else {
$str .= var_export($value, true);
}
$str .= ',' . PHP_EOL;
}
$str .= ']';
return $str;
}
private function indent($str)
{
return str_replace("\n", "\n ", $str);
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Dumper;
use DI\Definition\DecoratorDefinition;
use DI\Definition\Definition;
/**
* Dumps decorator definitions.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DecoratorDefinitionDumper implements DefinitionDumper
{
/**
* {@inheritdoc}
*/
public function dump(Definition $definition)
{
if (! $definition instanceof DecoratorDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition dumper is only compatible with DecoratorDefinition objects, %s given',
get_class($definition)
));
}
return 'Decorate(' . $definition->getSubDefinitionName() . ')';
}
}

View file

@ -0,0 +1,30 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Dumper;
use DI\Definition\Definition;
/**
* Dumps definitions to help debugging.
*
* @since 4.1
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface DefinitionDumper
{
/**
* Returns the given definition as string representation.
*
* @param Definition $definition
*
* @return string
*/
public function dump(Definition $definition);
}

View file

@ -0,0 +1,68 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Dumper;
use DI\Definition\Definition;
/**
* Dispatch a definition to the appropriate dumper.
*
* @since 4.1
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DefinitionDumperDispatcher implements DefinitionDumper
{
/**
* Definition dumpers, indexed by the class of the definition they can dump.
*
* @var DefinitionDumper[]|null
*/
private $dumpers = array();
public function __construct($dumpers = null)
{
$this->dumpers = $dumpers;
}
/**
* {@inheritdoc}
*/
public function dump(Definition $definition)
{
$this->initialize();
$class = get_class($definition);
if (! array_key_exists($class, $this->dumpers)) {
throw new \RuntimeException(sprintf(
'There is no DefinitionDumper capable of dumping this definition of type %s',
$class
));
}
$dumper = $this->dumpers[$class];
return $dumper->dump($definition);
}
private function initialize()
{
if ($this->dumpers === null) {
$this->dumpers = array(
'DI\Definition\ValueDefinition' => new ValueDefinitionDumper(),
'DI\Definition\FactoryDefinition' => new FactoryDefinitionDumper(),
'DI\Definition\DecoratorDefinition' => new DecoratorDefinitionDumper(),
'DI\Definition\AliasDefinition' => new AliasDefinitionDumper(),
'DI\Definition\ObjectDefinition' => new ObjectDefinitionDumper(),
'DI\Definition\EnvironmentVariableDefinition' => new EnvironmentVariableDefinitionDumper(),
);
}
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Dumper;
use DI\Debug;
use DI\Definition\Definition;
use DI\Definition\EntryReference;
use DI\Definition\EnvironmentVariableDefinition;
use DI\Definition\Helper\DefinitionHelper;
/**
* Dumps environment variable definitions.
*
* @author James Harris <james.harris@icecave.com.au>
*/
class EnvironmentVariableDefinitionDumper implements DefinitionDumper
{
/**
* {@inheritdoc}
*/
public function dump(Definition $definition)
{
if (! $definition instanceof EnvironmentVariableDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition dumper is only compatible with EnvironmentVariableDefinition objects, %s given',
get_class($definition)
));
}
$str = " variable = " . $definition->getVariableName();
$str .= "\n optional = " . ($definition->isOptional() ? 'yes' : 'no');
if ($definition->isOptional()) {
$defaultValue = $definition->getDefaultValue();
if ($defaultValue instanceof DefinitionHelper) {
$nestedDefinition = Debug::dumpDefinition($defaultValue->getDefinition(''));
$defaultValueStr = $this->indent($nestedDefinition);
} else {
$defaultValueStr = var_export($defaultValue, true);
}
$str .= "\n default = " . $defaultValueStr;
}
return sprintf(
"Environment variable (\n%s\n)",
$str
);
}
private function indent($str)
{
return str_replace("\n", "\n ", $str);
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Dumper;
use DI\Definition\Definition;
use DI\Definition\FactoryDefinition;
/**
* Dumps factory definitions.
*
* @since 4.1
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class FactoryDefinitionDumper implements DefinitionDumper
{
/**
* {@inheritdoc}
*/
public function dump(Definition $definition)
{
if (! $definition instanceof FactoryDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition dumper is only compatible with FactoryDefinition objects, %s given',
get_class($definition)
));
}
return 'Factory';
}
}

View file

@ -0,0 +1,156 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Dumper;
use DI\Definition\ObjectDefinition;
use DI\Definition\ObjectDefinition\MethodInjection;
use DI\Definition\Definition;
use DI\Definition\EntryReference;
use ReflectionException;
use ReflectionMethod;
/**
* Dumps object definitions.
*
* @since 4.1
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ObjectDefinitionDumper implements DefinitionDumper
{
/**
* {@inheritdoc}
*/
public function dump(Definition $definition)
{
if (! $definition instanceof ObjectDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition dumper is only compatible with ObjectDefinition objects, %s given',
get_class($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 .= "\n scope = " . $definition->getScope();
// Lazy
$str .= "\n 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 (\n%s\n)", $str);
}
private function dumpConstructor($className, ObjectDefinition $definition)
{
$str = '';
$constructorInjection = $definition->getConstructorInjection();
if ($constructorInjection !== null) {
$parameters = $this->dumpMethodParameters($className, $constructorInjection);
$str .= sprintf("\n __construct(\n %s\n )", $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("\n $%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("\n %s(\n %s\n )", $methodInjection->getMethodName(), $parameters);
}
return $str;
}
private function dumpMethodParameters($className, MethodInjection $methodInjection)
{
$methodReflection = new \ReflectionMethod($className, $methodInjection->getMethodName());
$args = array();
foreach ($methodReflection->getParameters() as $index => $parameter) {
if ($methodInjection->hasParameter($index)) {
$value = $methodInjection->getParameter($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,37 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Dumper;
use DI\Definition\Definition;
use DI\Definition\StringDefinition;
/**
* Dumps string definitions.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class StringDefinitionDumper implements DefinitionDumper
{
/**
* {@inheritdoc}
*/
public function dump(Definition $definition)
{
if (! $definition instanceof StringDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition dumper is only compatible with StringDefinition objects, %s given',
get_class($definition)
));
}
return $definition->getExpression();
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Dumper;
use DI\Definition\Definition;
use DI\Definition\ValueDefinition;
/**
* Dumps value definitions.
*
* @since 4.1
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ValueDefinitionDumper implements DefinitionDumper
{
/**
* {@inheritdoc}
*/
public function dump(Definition $definition)
{
if (! $definition instanceof ValueDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition dumper is only compatible with ValueDefinition objects, %s given',
get_class($definition)
));
}
ob_start();
var_dump($definition->getValue());
return sprintf(
"Value (\n %s\n)",
trim(ob_get_clean())
);
}
}

View file

@ -0,0 +1,52 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,116 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition;
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 boolean
*/
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 boolean $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 boolean 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;
}
}

View file

@ -0,0 +1,19 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,30 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Exception;
use DI\Debug;
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\nFull definition:\n%s",
$message,
Debug::dumpDefinition($definition)
));
}
}

View file

@ -0,0 +1,75 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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;
/**
* @param string $name Entry name
* @param callable $factory Callable that returns the value associated to the entry name.
* @param string|null $scope
*/
public function __construct($name, $factory, $scope = null)
{
$this->name = $name;
$this->factory = $factory;
$this->scope = $scope;
}
/**
* @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;
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,46 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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 = array();
/**
* @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,24 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,64 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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 boolean
*/
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 boolean $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,72 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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;
/**
* @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);
}
return new FactoryDefinition($entryName, $this->factory, $this->scope);
}
}

View file

@ -0,0 +1,273 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Helper;
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 boolean|null
*/
private $lazy;
/**
* @var string|null
*/
private $scope;
/**
* Array of constructor parameters.
* @var array
*/
private $constructor = array();
/**
* Array of properties and their value.
* @var array
*/
private $properties = array();
/**
* Array of methods and their parameters.
* @var array
*/
private $methods = array();
/**
* 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] = array();
}
$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] = array(0 => array());
}
$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
* @return array
*/
private function fixParameters(ObjectDefinition $definition, $method, $parameters)
{
$fixedParameters = array();
foreach ($parameters as $index => $parameter) {
// Parameter indexed by the parameter name, we reindex it with its position
if (is_string($index)) {
$callable = array($definition->getClassName(), $method);
$reflectionParameter = new \ReflectionParameter($callable, $index);
$index = $reflectionParameter->getPosition();
}
$fixedParameters[$index] = $parameter;
}
return $fixedParameters;
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,42 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,76 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,294 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition;
use DI\Definition\ObjectDefinition\MethodInjection;
use DI\Definition\ObjectDefinition\PropertyInjection;
use DI\Definition\Exception\DefinitionException;
use DI\Scope;
/**
* 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 = array();
/**
* Method calls
* @var MethodInjection[][]
*/
private $methodInjections = array();
/**
* @var string|null
*/
private $scope;
/**
* @var boolean|null
*/
private $lazy;
/**
* @param string $name Class name
* @param string $className
*/
public function __construct($name, $className = null)
{
$this->name = (string) $name;
$this->className = $className;
}
/**
* @return string Entry name
*/
public function getName()
{
return $this->name;
}
/**
* @param string $className
*/
public function setClassName($className)
{
$this->className = $className;
}
/**
* @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;
}
/**
* @param string $propertyName
* @return PropertyInjection
*/
public function getPropertyInjection($propertyName)
{
return isset($this->propertyInjections[$propertyName]) ? $this->propertyInjections[$propertyName] : null;
}
/**
* @param PropertyInjection $propertyInjection
*/
public function addPropertyInjection(PropertyInjection $propertyInjection)
{
$this->propertyInjections[$propertyInjection->getPropertyName()] = $propertyInjection;
}
/**
* @return MethodInjection[] Method injections
*/
public function getMethodInjections()
{
// Return array leafs
$injections = array();
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] = array();
}
$this->methodInjections[$method][] = $methodInjection;
}
/**
* @param string $scope
*/
public function setScope($scope)
{
$this->scope = $scope;
}
/**
* {@inheritdoc}
*/
public function getScope()
{
return $this->scope ?: Scope::SINGLETON;
}
/**
* @param boolean|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;
}
}
/**
* {@inheritdoc}
*/
public function getSubDefinitionName()
{
return $this->getClassName();
}
/**
* {@inheritdoc}
*/
public function setSubDefinition(Definition $definition)
{
if (! $definition instanceof ObjectDefinition) {
throw new DefinitionException(sprintf(
"Container entry '%s' extends entry '%s' which is not an object",
$this->getName(),
$definition->getName()
));
}
// The current prevails
if ($this->className === null) {
$this->className = $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);
}
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->getPropertyInjections() as $propertyName => $propertyInjection) {
if (! array_key_exists($propertyName, $this->propertyInjections)) {
// 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;
}
}
}
}

View file

@ -0,0 +1,54 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\ObjectDefinition;
use DI\Definition\AbstractFunctionCallDefinition;
/**
* Describe an injection in an object method.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class MethodInjection extends AbstractFunctionCallDefinition
{
/**
* @var string
*/
private $methodName;
/**
* @param string $methodName
* @param array $parameters
*/
public function __construct($methodName, array $parameters = array())
{
$this->methodName = (string) $methodName;
$this->parameters = $parameters;
}
public static function constructor(array $parameters = array())
{
return new self('__construct', $parameters);
}
/**
* @return string Method name
*/
public function getMethodName()
{
return $this->methodName;
}
public function merge(MethodInjection $definition)
{
// In case of conflicts, the current definition prevails.
$this->parameters = $this->parameters + $definition->parameters;
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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;
/**
* @param string $propertyName Property name
* @param mixed $value Value that should be injected in the property
*/
public function __construct($propertyName, $value)
{
$this->propertyName = (string) $propertyName;
$this->value = $value;
}
/**
* @return string Property name
*/
public function getPropertyName()
{
return $this->propertyName;
}
/**
* @return string Value that should be injected in the property
*/
public function getValue()
{
return $this->value;
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* PHP-DI
*
* @link http://mnapoli.github.com/PHP-DI/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Resolver;
use DI\Definition\AliasDefinition;
use DI\Definition\Definition;
use Interop\Container\ContainerInterface;
/**
* Resolves an alias definition to a value.
*
* @since 4.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AliasResolver implements DefinitionResolver
{
/**
* @var ContainerInterface
*/
private $container;
/**
* The resolver needs a container.
* This container will be used to get the entry to which the alias points to.
*
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Resolve an alias definition to a value.
*
* This will return the entry the alias points to.
*
* @param AliasDefinition $definition
*
* {@inheritdoc}
*/
public function resolve(Definition $definition, array $parameters = array())
{
$this->assertIsAliasDefinition($definition);
return $this->container->get($definition->getTargetEntryName());
}
/**
* @param AliasDefinition $definition
*
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = array())
{
$this->assertIsAliasDefinition($definition);
return $this->container->has($definition->getTargetEntryName());
}
private function assertIsAliasDefinition(Definition $definition)
{
if (!$definition instanceof AliasDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition resolver is only compatible with AliasDefinition objects, %s given',
get_class($definition)
));
}
}
}

View file

@ -0,0 +1,105 @@
<?php
/**
* PHP-DI
*
* @link http://mnapoli.github.com/PHP-DI/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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 = array())
{
$this->assertIsArrayDefinition($definition);
$values = $definition->getValues();
$values = $this->resolveNestedDefinitions($definition, $values);
return $values;
}
/**
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = array())
{
$this->assertIsArrayDefinition($definition);
return true;
}
private function assertIsArrayDefinition(Definition $definition)
{
if (!$definition instanceof ArrayDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition resolver is only compatible with ArrayDefinition objects, %s given',
get_class($definition)
));
}
}
private function resolveNestedDefinitions(ArrayDefinition $definition, array $values)
{
foreach ($values as $key => $value) {
if ($value instanceof DefinitionHelper) {
$values[$key] = $this->resolveDefinition($value, $definition, $key);
}
}
return $values;
}
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,107 @@
<?php
/**
* PHP-DI
*
* @link http://mnapoli.github.com/PHP-DI/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Resolver;
use DI\Definition\DecoratorDefinition;
use DI\Definition\Exception\DefinitionException;
use DI\Definition\Definition;
use Interop\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 = array())
{
$this->assertIsDecoratorDefinition($definition);
$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);
return call_user_func($callable, $decorated, $this->container);
}
/**
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = array())
{
$this->assertIsDecoratorDefinition($definition);
return true;
}
private function assertIsDecoratorDefinition(Definition $definition)
{
if (!$definition instanceof DecoratorDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition resolver is only compatible with DecoratorDefinition objects, %s given',
get_class($definition)
));
}
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* PHP-DI
*
* @link http://mnapoli.github.com/PHP-DI/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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 = array());
/**
* 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 = array());
}

View file

@ -0,0 +1,94 @@
<?php
/**
* PHP-DI
*
* @link http://mnapoli.github.com/PHP-DI/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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 = array())
{
$this->assertIsEnvironmentVariableDefinition($definition);
$value = call_user_func($this->variableReader, $definition->getVariableName());
if (false !== $value) {
return $value;
} elseif (!$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 = array())
{
$this->assertIsEnvironmentVariableDefinition($definition);
return $definition->isOptional()
|| false !== call_user_func($this->variableReader, $definition->getVariableName());
}
private function assertIsEnvironmentVariableDefinition(Definition $definition)
{
if (!$definition instanceof EnvironmentVariableDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition resolver is only compatible with EnvironmentVariableDefinition objects, %s given',
get_class($definition)
));
}
}
}

View file

@ -0,0 +1,85 @@
<?php
/**
* PHP-DI
*
* @link http://mnapoli.github.com/PHP-DI/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Resolver;
use DI\Definition\Exception\DefinitionException;
use DI\Definition\FactoryDefinition;
use DI\Definition\Definition;
use Interop\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;
/**
* 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)
{
$this->container = $container;
}
/**
* 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 = array())
{
$this->assertIsFactoryDefinition($definition);
$callable = $definition->getCallable();
if (! is_callable($callable)) {
throw new DefinitionException(sprintf(
'The factory definition "%s" is not callable',
$definition->getName()
));
}
return call_user_func($callable, $this->container);
}
/**
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = array())
{
$this->assertIsFactoryDefinition($definition);
return true;
}
private function assertIsFactoryDefinition(Definition $definition)
{
if (!$definition instanceof FactoryDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition resolver is only compatible with FactoryDefinition objects, %s given',
get_class($definition)
));
}
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* PHP-DI
*
* @link http://mnapoli.github.com/PHP-DI/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Resolver;
use DI\Definition\Definition;
use DI\Definition\InstanceDefinition;
use DI\DependencyException;
use Interop\Container\Exception\NotFoundException;
/**
* 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 = array())
{
$this->assertIsInstanceDefinition($definition);
try {
$this->injectMethodsAndProperties($definition->getInstance(), $definition->getObjectDefinition());
} catch (NotFoundException $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 = array())
{
$this->assertIsInstanceDefinition($definition);
return true;
}
private function assertIsInstanceDefinition(Definition $definition)
{
if (!$definition instanceof InstanceDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition resolver is only compatible with InstanceDefinition objects, %s given',
get_class($definition)
));
}
}
}

View file

@ -0,0 +1,271 @@
<?php
/**
* PHP-DI
*
* @link http://mnapoli.github.com/PHP-DI/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Resolver;
use DI\Definition\ObjectDefinition;
use DI\Definition\Definition;
use DI\Definition\Exception\DefinitionException;
use DI\Definition\ObjectDefinition\PropertyInjection;
use DI\Definition\Helper\DefinitionHelper;
use DI\DependencyException;
use DI\Proxy\ProxyFactory;
use Exception;
use Interop\Container\Exception\NotFoundException;
use ProxyManager\Proxy\LazyLoadingInterface;
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 = array())
{
$this->assertIsObjectDefinition($definition);
// 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 = array())
{
$this->assertIsObjectDefinition($definition);
if (! class_exists($definition->getClassName())) {
return false;
}
$classReflection = new ReflectionClass($definition->getClassName());
return $classReflection->isInstantiable();
}
/**
* Returns a proxy instance
*
* @param ObjectDefinition $definition
* @param array $parameters
*
* @return LazyLoadingInterface Proxy instance
*/
private function createProxy(ObjectDefinition $definition, array $parameters)
{
// waiting for PHP 5.4+ support
$resolver = $this;
/** @noinspection PhpUnusedParameterInspection */
$proxy = $this->proxyFactory->createProxy(
$definition->getClassName(),
function (& $wrappedObject, $proxy, $method, $params, & $initializer) use ($resolver, $definition, $parameters) {
$wrappedObject = $resolver->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
*
* @todo Make private once PHP-DI supports PHP > 5.4 only
*/
public function createInstance(ObjectDefinition $definition, array $parameters)
{
$this->assertClassExists($definition);
$classReflection = new ReflectionClass($definition->getClassName());
$this->assertClassIsInstantiable($definition, $classReflection);
$constructorInjection = $definition->getConstructorInjection();
try {
$args = $this->parameterResolver->resolveParameters(
$constructorInjection,
$classReflection->getConstructor(),
$parameters
);
if (count($args) > 0) {
$object = $classReflection->newInstanceArgs($args);
} else {
$object = $classReflection->newInstance();
}
$this->injectMethodsAndProperties($object, $definition);
} catch (NotFoundException $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()
));
}
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();
$property = new ReflectionProperty(get_class($object), $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 assertIsObjectDefinition(Definition $definition)
{
if (!$definition instanceof ObjectDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition resolver is only compatible with ObjectDefinition objects, %s given',
get_class($definition)
));
}
}
private function assertClassExists(ObjectDefinition $definition)
{
if (!class_exists($definition->getClassName()) && !interface_exists($definition->getClassName())) {
throw DefinitionException::create($definition,
sprintf(
"Entry %s cannot be resolved: class %s doesn't exist",
$definition->getName(),
$definition->getClassName()
));
}
}
private function assertClassIsInstantiable(ObjectDefinition $definition, ReflectionClass $classReflection)
{
if (!$classReflection->isInstantiable()) {
throw DefinitionException::create($definition,
sprintf(
"Entry %s cannot be resolved: class %s is not instantiable",
$definition->getName(),
$definition->getClassName()
));
}
}
}

View file

@ -0,0 +1,138 @@
<?php
/**
* PHP-DI
*
* @link http://mnapoli.github.com/PHP-DI/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Resolver;
use DI\Definition\AbstractFunctionCallDefinition;
use DI\Definition\ObjectDefinition;
use DI\Definition\Exception\DefinitionException;
use DI\Definition\Helper\DefinitionHelper;
/**
* 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 AbstractFunctionCallDefinition $definition
* @param \ReflectionFunctionAbstract $functionReflection
* @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(
AbstractFunctionCallDefinition $definition = null,
\ReflectionFunctionAbstract $functionReflection = null,
array $parameters = array()
) {
$args = array();
if (! $functionReflection) {
return $args;
}
foreach ($functionReflection->getParameters() as $index => $parameter) {
if (array_key_exists($parameter->getName(), $parameters)) {
// Look in the $parameters array
$value = $parameters[$parameter->getName()];
} elseif ($definition && $definition->hasParameter($index)) {
// Look in the definition
$value = $definition->getParameter($index);
} else {
// If the parameter is optional and wasn't specified, we take its default value
if ($parameter->isOptional()) {
$args[] = $this->getParameterDefaultValue($parameter, $functionReflection);
continue;
}
throw new DefinitionException(sprintf(
"The parameter '%s' of %s has no value defined or guessable",
$parameter->getName(),
$this->getFunctionName($functionReflection)
));
}
if ($value instanceof DefinitionHelper) {
$nestedDefinition = $value->getDefinition('');
// If the container cannot produce the entry, we can use the default parameter value
if (!$this->definitionResolver->isResolvable($nestedDefinition) && $parameter->isOptional()) {
$value = $this->getParameterDefaultValue($parameter, $functionReflection);
} else {
$value = $this->definitionResolver->resolve($nestedDefinition);
}
}
$args[] = $value;
}
return $args;
}
/**
* Returns the default value of a function parameter.
*
* @param \ReflectionParameter $parameter
* @param \ReflectionFunctionAbstract $function
*
* @throws DefinitionException Can't get default values from PHP internal classes and functions
* @return mixed
*/
private function getParameterDefaultValue(
\ReflectionParameter $parameter,
\ReflectionFunctionAbstract $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(\ReflectionFunctionAbstract $reflectionFunction)
{
if ($reflectionFunction instanceof \ReflectionMethod) {
return sprintf(
'%s::%s',
$reflectionFunction->getDeclaringClass()->getName(),
$reflectionFunction->getName()
);
} elseif ($reflectionFunction->isClosure()) {
return sprintf(
'closure defined in %s at line %d',
$reflectionFunction->getFileName(),
$reflectionFunction->getStartLine()
);
}
return $reflectionFunction->getName();
}
}

View file

@ -0,0 +1,141 @@
<?php
namespace DI\Definition\Resolver;
use DI\Definition\Definition;
use DI\Definition\Exception\DefinitionException;
use DI\Proxy\ProxyFactory;
use Interop\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 $valueResolver;
private $arrayResolver;
private $factoryResolver;
private $decoratorResolver;
private $aliasResolver;
private $objectResolver;
private $instanceResolver;
private $envVariableResolver;
private $stringResolver;
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 = array())
{
$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 = array())
{
$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)
{
$definitionType = get_class($definition);
switch ($definitionType) {
case 'DI\Definition\ValueDefinition':
if (! $this->valueResolver) {
$this->valueResolver = new ValueResolver();
}
return $this->valueResolver;
case 'DI\Definition\ArrayDefinition':
case 'DI\Definition\ArrayDefinitionExtension':
if (! $this->arrayResolver) {
$this->arrayResolver = new ArrayResolver($this);
}
return $this->arrayResolver;
case 'DI\Definition\FactoryDefinition':
if (! $this->factoryResolver) {
$this->factoryResolver = new FactoryResolver($this->container);
}
return $this->factoryResolver;
case 'DI\Definition\DecoratorDefinition':
if (! $this->decoratorResolver) {
$this->decoratorResolver = new DecoratorResolver($this->container, $this);
}
return $this->decoratorResolver;
case 'DI\Definition\AliasDefinition':
if (! $this->aliasResolver) {
$this->aliasResolver = new AliasResolver($this->container);
}
return $this->aliasResolver;
case 'DI\Definition\ObjectDefinition':
if (! $this->objectResolver) {
$this->objectResolver = new ObjectCreator($this, $this->proxyFactory);
}
return $this->objectResolver;
case 'DI\Definition\InstanceDefinition':
if (! $this->instanceResolver) {
$this->instanceResolver = new InstanceInjector($this, $this->proxyFactory);
}
return $this->instanceResolver;
case 'DI\Definition\EnvironmentVariableDefinition':
if (! $this->envVariableResolver) {
$this->envVariableResolver = new EnvironmentVariableResolver($this);
}
return $this->envVariableResolver;
case 'DI\Definition\StringDefinition':
if (! $this->stringResolver) {
$this->stringResolver = new StringResolver($this->container);
}
return $this->stringResolver;
default:
throw new \RuntimeException("No definition resolver was configured for definition of type $definitionType");
}
}
}

View file

@ -0,0 +1,98 @@
<?php
/**
* PHP-DI
*
* @link http://mnapoli.github.com/PHP-DI/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Resolver;
use DI\Definition\Definition;
use DI\Definition\StringDefinition;
use DI\DependencyException;
use DI\NotFoundException;
use Interop\Container\ContainerInterface;
/**
* Resolves a string expression.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class StringResolver implements DefinitionResolver
{
/**
* @var ContainerInterface
*/
private $container;
/**
* The resolver needs a container.
* This container will be used to get the entry to which the alias points to.
*
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Resolve a value definition to a value.
*
* A value definition is simple, so this will just return the value of the ValueDefinition.
*
* @param StringDefinition $definition
*
* {@inheritdoc}
*/
public function resolve(Definition $definition, array $parameters = array())
{
$this->assertIsStringDefinition($definition);
$expression = $definition->getExpression();
// TODO Remove PHP 5.3 support
$container = $this->container;
$result = preg_replace_callback('#\{([^\{\}]+)\}#', function (array $matches) use ($container, $definition) {
try {
return $container->get($matches[1]);
} catch (NotFoundException $e) {
throw new DependencyException(sprintf(
"Error while parsing string expression for entry '%s': %s",
$definition->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;
}
/**
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = array())
{
$this->assertIsStringDefinition($definition);
return true;
}
private function assertIsStringDefinition(Definition $definition)
{
if (!$definition instanceof StringDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition resolver is only compatible with StringDefinition objects, %s given',
get_class($definition)
));
}
}
}

View file

@ -0,0 +1,58 @@
<?php
/**
* PHP-DI
*
* @link http://mnapoli.github.com/PHP-DI/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Resolver;
use DI\Definition\Definition;
use DI\Definition\ValueDefinition;
/**
* Resolves a value definition to a value.
*
* @since 4.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ValueResolver implements DefinitionResolver
{
/**
* Resolve a value definition to a value.
*
* A value definition is simple, so this will just return the value of the ValueDefinition.
*
* @param ValueDefinition $definition
*
* {@inheritdoc}
*/
public function resolve(Definition $definition, array $parameters = array())
{
$this->assertIsValueDefinition($definition);
return $definition->getValue();
}
/**
* {@inheritdoc}
*/
public function isResolvable(Definition $definition, array $parameters = array())
{
$this->assertIsValueDefinition($definition);
return true;
}
private function assertIsValueDefinition(Definition $definition)
{
if (!$definition instanceof ValueDefinition) {
throw new \InvalidArgumentException(sprintf(
'This definition resolver is only compatible with ValueDefinition objects, %s given',
get_class($definition)
));
}
}
}

View file

@ -0,0 +1,298 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Source;
use DI\Annotation\Inject;
use DI\Annotation\Injectable;
use DI\Definition\ObjectDefinition;
use DI\Definition\EntryReference;
use DI\Definition\Exception\AnnotationException;
use DI\Definition\Exception\DefinitionException;
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;
/**
* @param bool $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.
*
* @param ReflectionClass $reflectionClass
* @param ObjectDefinition $objectDefinition
*/
private function readProperties(ReflectionClass $reflectionClass, ObjectDefinition $objectDefinition)
{
// This will look in all the properties, including those of the parent classes
foreach ($reflectionClass->getProperties() as $property) {
// Ignore static properties
if ($property->isStatic()) {
continue;
}
$propertyInjection = $this->getPropertyInjection($property);
if ($propertyInjection) {
$objectDefinition->addPropertyInjection($propertyInjection);
}
}
}
/**
* @param ReflectionProperty $property
* @return PropertyInjection|null
*/
private function getPropertyInjection(ReflectionProperty $property)
{
// 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()
));
}
return new PropertyInjection($property->getName(), new EntryReference($entryName));
}
/**
* Browse the object's methods looking for annotated methods.
*
* @param ReflectionClass $class
* @param ObjectDefinition $objectDefinition
*/
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);
}
}
}
/**
* {@inheritdoc}
*/
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() : array();
// @Inject on constructor is implicit
if (! ($annotation || $method->isConstructor())) {
return null;
}
$parameters = array();
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;
}
/**
* @param Reader $annotationReader The annotation reader
*/
public function setAnnotationReader(Reader $annotationReader)
{
$this->annotationReader = $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,68 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Source;
use DI\Definition\ObjectDefinition;
use DI\Definition\EntryReference;
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 = array();
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,104 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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|boolean 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,142 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Source;
use DI\Definition\ArrayDefinition;
use DI\Definition\ObjectDefinition;
use DI\Definition\Definition;
use DI\Definition\FactoryDefinition;
use DI\Definition\ValueDefinition;
use DI\Definition\Helper\DefinitionHelper;
/**
* 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 = array();
/**
* @param array $definitions
*/
public function __construct(array $definitions = array())
{
$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;
}
/**
* {@inheritdoc}
*/
public function addDefinition(Definition $definition)
{
$this->definitions[$definition->getName()] = $definition;
}
/**
* {@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);
}
// Look if there are wildcards definitions
foreach ($this->definitions as $key => $definition) {
if (strpos($key, self::WILDCARD) === false) {
continue;
}
// Turn the pattern into a regex
$key = addslashes($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);
}
if (! $definition instanceof Definition && is_array($definition)) {
$definition = new ArrayDefinition($name, $definition);
}
if ($definition instanceof \Closure) {
$definition = new FactoryDefinition($name, $definition);
}
if (! $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,74 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition\Source;
use DI\Definition\Definition;
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(array());
}
/**
* {@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,31 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,108 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,66 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition;
use DI\Scope;
/**
* Definition of a string composed of other strings.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class StringDefinition implements Definition
{
/**
* 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;
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Definition;
use DI\Scope;
/**
* Definition of a value for dependency injection.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ValueDefinition implements Definition
{
/**
* 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;
}
}

View file

@ -0,0 +1,19 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI;
use Interop\Container\Exception\ContainerException;
/**
* Exception for the Container
*/
class DependencyException extends \Exception implements ContainerException
{
}

View file

@ -0,0 +1,34 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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 = array());
}

View file

@ -0,0 +1,45 @@
<?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
) {
foreach ($resolvedParameters as &$parameter) {
if ($parameter instanceof DefinitionHelper) {
$definition = $parameter->getDefinition('');
$parameter = $this->definitionResolver->resolve($definition);
}
}
return $resolvedParameters;
}
}

View file

@ -0,0 +1,19 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI;
/**
* Invoke a callable.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface InvokerInterface extends \Invoker\InvokerInterface
{
}

View file

@ -0,0 +1,19 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,95 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Proxy;
use ProxyManager\Configuration;
use ProxyManager\Factory\LazyLoadingValueHolderFactory;
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
/**
* 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 boolean
*/
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('ProxyManager\Configuration')) {
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:~0.3".');
}
$config = new Configuration();
/**
* @todo useless since ProxyManager 0.5, line kept for compatibility with 0.3 and 0.4 which are
* the only versions that work with PHP < 5.3.23
* Remove when support for PHP 5.3 is dropped
*/
$config->setAutoGenerateProxies(true);
if ($this->writeProxiesToFile) {
$config->setProxiesTargetDir($this->proxyDirectory);
spl_autoload_register($config->getProxyAutoloader());
} else {
$config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
}
$this->proxyManager = new LazyLoadingValueHolderFactory($config);
}
}

View file

@ -0,0 +1,53 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI\Reflection;
/**
* Create a reflection object from a callable.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class CallableReflectionFactory
{
/**
* @param callable $callable
*
* @return \ReflectionFunctionAbstract
*
* TODO Use the `callable` type-hint once support for PHP 5.4 and up.
*/
public static function fromCallable($callable)
{
// Array callable
if (is_array($callable)) {
list($class, $method) = $callable;
return new \ReflectionMethod($class, $method);
}
// Closure
if ($callable instanceof \Closure) {
return new \ReflectionFunction($callable);
}
// 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
return new \ReflectionFunction($callable);
}
}

View file

@ -0,0 +1,54 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
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,181 @@
<?php
/**
* PHP-DI
*
* @link http://php-di.org/
* @copyright Matthieu Napoli (http://mnapoli.fr/)
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace DI;
use DI\Definition\EntryReference;
use DI\Definition\Helper\ArrayDefinitionExtensionHelper;
use DI\Definition\Helper\FactoryDefinitionHelper;
use DI\Definition\Helper\ObjectDefinitionHelper;
use DI\Definition\Helper\EnvironmentVariableDefinitionHelper;
use DI\Definition\Helper\ValueDefinitionHelper;
use DI\Definition\Helper\StringDefinitionHelper;
if (! function_exists('DI\value')) {
/**
* Helper for defining an object.
*
* @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 = array($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);
}
}