* @category Community Framework
*/
abstract class SingletonFactory {
- /**
- * list of singletons
- * @var array<SingletonFactory>
- */
- protected static $__singletonObjects = array();
-
/**
* Singletons do not support a public constructor. Override init() if
* your class needs to initialize components on creation.
*/
- protected final function __construct() {
+ public final function __construct() {
$this->init();
}
* @return static
*/
public static final function getInstance() {
- $className = get_called_class();
- if (!array_key_exists($className, self::$__singletonObjects)) {
- self::$__singletonObjects[$className] = null;
- self::$__singletonObjects[$className] = new $className();
- }
- else if (self::$__singletonObjects[$className] === null) {
- throw new SystemException("Infinite loop detected while trying to retrieve object for '".$className."'");
- }
-
- return self::$__singletonObjects[$className];
- }
-
- /**
- * Returns whether this singleton is already initialized.
- *
- * @return boolean
- */
- public static final function isInitialized() {
- $className = get_called_class();
-
- return isset(self::$__singletonObjects[$className]);
+ return WCF::getDIContainer()->get(get_called_class());
}
}
<?php
namespace wcf\system;
+use DI\ContainerBuilder;
use wcf\data\application\Application;
use wcf\data\option\OptionEditor;
use wcf\data\package\Package;
// wcf imports
if (!defined('NO_IMPORTS')) {
require_once(WCF_DIR.'lib/core.functions.php');
+ require_once(WCF_DIR.'lib/system/api/autoload.php');
}
/**
*/
protected static $dbObj = null;
+ /**
+ * dependency injection container
+ * @var \DI\Container
+ */
+ protected static $diContainer = null;
+
/**
* language object
* @var \wcf\data\language\Language
// define tmp directory
if (!defined('TMP_DIR')) define('TMP_DIR', FileUtil::getTempFolder());
+ $builder = new ContainerBuilder();
+ self::$diContainer = $builder->build();
+
// start initialization
$this->initDB();
$this->loadOptions();
EventHandler::getInstance()->fireAction($this, 'initialized');
}
+ /**
+ * Returns the dependency injection container.
+ *
+ * @return \DI\Container
+ */
+ public static final function getDIContainer() {
+ return self::$diContainer;
+ }
+
/**
* Flushes the output, closes the session, performs background tasks and more.
*
$factory = new SessionFactory();
$factory->load();
- self::$sessionObj = SessionHandler::getInstance();
+ self::$sessionObj = self::$diContainer->get(SessionHandler::class);
self::$sessionObj->setHasValidCookie($factory->hasValidCookie());
}
* Initialises the template engine.
*/
protected function initTPL() {
- self::$tplObj = TemplateEngine::getInstance();
+ self::$tplObj = self::$diContainer->get(TemplateEngine::class);
self::getTPL()->setLanguageID(self::getLanguage()->languageID);
$this->assignDefaultTemplateVariables();
self::getSession()->setStyleID(intval($_REQUEST['styleID']));
}
- StyleHandler::getInstance()->changeStyle(self::getSession()->getStyleID());
+ /** @var $styleHandler \wcf\system\style\StyleHandler */
+ $styleHandler = self::$diContainer->get(StyleHandler::class);
+ $styleHandler->changeStyle(self::getSession()->getStyleID());
}
/**
--- /dev/null
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer' . '/autoload_real.php';
+
+return ComposerAutoloaderInitbf0ab6890db693133cef0043fae5b370::getLoader();
--- /dev/null
+{
+ "config": {
+ "vendor-dir": "./"
+ },
+ "require": {
+ "php-di/php-di": "5.1.*"
+ }
+}
--- /dev/null
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "hash": "42cea85fb0771ad2e63dbf2fd651fcc9",
+ "content-hash": "85555ff71b5f67266b9d82865ad103e5",
+ "packages": [
+ {
+ "name": "container-interop/container-interop",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/container-interop/container-interop.git",
+ "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/container-interop/container-interop/zipball/fc08354828f8fd3245f77a66b9e23a6bca48297e",
+ "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Interop\\Container\\": "src/Interop/Container/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
+ "time": "2014-12-30 15:22:37"
+ },
+ {
+ "name": "php-di/invoker",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHP-DI/Invoker.git",
+ "reference": "9949fff87fcf14e8f2ccfbe36dac1e5921944c48"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/9949fff87fcf14e8f2ccfbe36dac1e5921944c48",
+ "reference": "9949fff87fcf14e8f2ccfbe36dac1e5921944c48",
+ "shasum": ""
+ },
+ "require": {
+ "container-interop/container-interop": "~1.1"
+ },
+ "require-dev": {
+ "athletic/athletic": "~0.1.8",
+ "phpunit/phpunit": "~4.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Invoker\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Generic and extensible callable invoker",
+ "homepage": "https://github.com/PHP-DI/Invoker",
+ "keywords": [
+ "callable",
+ "dependency",
+ "dependency-injection",
+ "injection",
+ "invoke",
+ "invoker"
+ ],
+ "time": "2015-10-22 19:49:23"
+ },
+ {
+ "name": "php-di/php-di",
+ "version": "5.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHP-DI/PHP-DI.git",
+ "reference": "f4a8088fa4eb480ee66c51b5ee4e4e30cce78489"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/f4a8088fa4eb480ee66c51b5ee4e4e30cce78489",
+ "reference": "f4a8088fa4eb480ee66c51b5ee4e4e30cce78489",
+ "shasum": ""
+ },
+ "require": {
+ "container-interop/container-interop": "~1.0",
+ "php": ">=5.4.0",
+ "php-di/invoker": "^1.0.1",
+ "php-di/phpdoc-reader": "~2.0"
+ },
+ "replace": {
+ "mnapoli/php-di": "*"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.2",
+ "doctrine/cache": "~1.4",
+ "mnapoli/phpunit-easymock": "~0.1.4",
+ "ocramius/proxy-manager": "~1.0",
+ "phpunit/phpunit": "~4.5"
+ },
+ "suggest": {
+ "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)",
+ "doctrine/cache": "Install it if you want to use the cache (version ~1.4)",
+ "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~1.0)"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DI\\": "src/DI/"
+ },
+ "files": [
+ "src/DI/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "The dependency injection container for humans",
+ "homepage": "http://php-di.org/",
+ "keywords": [
+ "container",
+ "dependency injection",
+ "di"
+ ],
+ "time": "2015-09-08 08:56:29"
+ },
+ {
+ "name": "php-di/phpdoc-reader",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHP-DI/PhpDocReader.git",
+ "reference": "21dce5e29f640d655e7b4583ecfb7d166127a5da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/21dce5e29f640d655e7b4583ecfb7d166127a5da",
+ "reference": "21dce5e29f640d655e7b4583ecfb7d166127a5da",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.6"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PhpDocReader\\": "src/PhpDocReader"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)",
+ "keywords": [
+ "phpdoc",
+ "reflection"
+ ],
+ "time": "2015-06-01 14:23:20"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": []
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0 class loader
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+
+ private $classMapAuthoritative = false;
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-0 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
+ if ('\\' == $class[0]) {
+ $class = substr($class, 1);
+ }
+
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative) {
+ return false;
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if ($file === null && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if ($file === null) {
+ // Remember that this class does not exist.
+ return $this->classMap[$class] = false;
+ }
+
+ return $file;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
--- /dev/null
+
+Copyright (c) 2015 Nils Adermann, Jordi Boggiano
+
+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.
+
--- /dev/null
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = $vendorDir;
+
+return array(
+);
--- /dev/null
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = $vendorDir;
+
+return array(
+ $vendorDir . '/php-di/php-di/src/DI/functions.php',
+);
--- /dev/null
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = $vendorDir;
+
+return array(
+);
--- /dev/null
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = $vendorDir;
+
+return array(
+ 'PhpDocReader\\' => array($vendorDir . '/php-di/phpdoc-reader/src/PhpDocReader'),
+ 'Invoker\\' => array($vendorDir . '/php-di/invoker/src'),
+ 'Interop\\Container\\' => array($vendorDir . '/container-interop/container-interop/src/Interop/Container'),
+ 'DI\\' => array($vendorDir . '/php-di/php-di/src/DI'),
+);
--- /dev/null
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInitbf0ab6890db693133cef0043fae5b370
+{
+ private static $loader;
+
+ public static function loadClassLoader($class)
+ {
+ if ('Composer\Autoload\ClassLoader' === $class) {
+ require __DIR__ . '/ClassLoader.php';
+ }
+ }
+
+ public static function getLoader()
+ {
+ if (null !== self::$loader) {
+ return self::$loader;
+ }
+
+ spl_autoload_register(array('ComposerAutoloaderInitbf0ab6890db693133cef0043fae5b370', 'loadClassLoader'), true, true);
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+ spl_autoload_unregister(array('ComposerAutoloaderInitbf0ab6890db693133cef0043fae5b370', 'loadClassLoader'));
+
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+
+ $loader->register(true);
+
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ foreach ($includeFiles as $file) {
+ composerRequirebf0ab6890db693133cef0043fae5b370($file);
+ }
+
+ return $loader;
+ }
+}
+
+function composerRequirebf0ab6890db693133cef0043fae5b370($file)
+{
+ require $file;
+}
--- /dev/null
+[
+ {
+ "name": "php-di/phpdoc-reader",
+ "version": "2.0.0",
+ "version_normalized": "2.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHP-DI/PhpDocReader.git",
+ "reference": "21dce5e29f640d655e7b4583ecfb7d166127a5da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/21dce5e29f640d655e7b4583ecfb7d166127a5da",
+ "reference": "21dce5e29f640d655e7b4583ecfb7d166127a5da",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.6"
+ },
+ "time": "2015-06-01 14:23:20",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "PhpDocReader\\": "src/PhpDocReader"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)",
+ "keywords": [
+ "phpdoc",
+ "reflection"
+ ]
+ },
+ {
+ "name": "container-interop/container-interop",
+ "version": "1.1.0",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/container-interop/container-interop.git",
+ "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/container-interop/container-interop/zipball/fc08354828f8fd3245f77a66b9e23a6bca48297e",
+ "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e",
+ "shasum": ""
+ },
+ "time": "2014-12-30 15:22:37",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Interop\\Container\\": "src/Interop/Container/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Promoting the interoperability of container objects (DIC, SL, etc.)"
+ },
+ {
+ "name": "php-di/invoker",
+ "version": "1.2.0",
+ "version_normalized": "1.2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHP-DI/Invoker.git",
+ "reference": "9949fff87fcf14e8f2ccfbe36dac1e5921944c48"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/9949fff87fcf14e8f2ccfbe36dac1e5921944c48",
+ "reference": "9949fff87fcf14e8f2ccfbe36dac1e5921944c48",
+ "shasum": ""
+ },
+ "require": {
+ "container-interop/container-interop": "~1.1"
+ },
+ "require-dev": {
+ "athletic/athletic": "~0.1.8",
+ "phpunit/phpunit": "~4.5"
+ },
+ "time": "2015-10-22 19:49:23",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Invoker\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Generic and extensible callable invoker",
+ "homepage": "https://github.com/PHP-DI/Invoker",
+ "keywords": [
+ "callable",
+ "dependency",
+ "dependency-injection",
+ "injection",
+ "invoke",
+ "invoker"
+ ]
+ },
+ {
+ "name": "php-di/php-di",
+ "version": "5.1.0",
+ "version_normalized": "5.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHP-DI/PHP-DI.git",
+ "reference": "f4a8088fa4eb480ee66c51b5ee4e4e30cce78489"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/f4a8088fa4eb480ee66c51b5ee4e4e30cce78489",
+ "reference": "f4a8088fa4eb480ee66c51b5ee4e4e30cce78489",
+ "shasum": ""
+ },
+ "require": {
+ "container-interop/container-interop": "~1.0",
+ "php": ">=5.4.0",
+ "php-di/invoker": "^1.0.1",
+ "php-di/phpdoc-reader": "~2.0"
+ },
+ "replace": {
+ "mnapoli/php-di": "*"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.2",
+ "doctrine/cache": "~1.4",
+ "mnapoli/phpunit-easymock": "~0.1.4",
+ "ocramius/proxy-manager": "~1.0",
+ "phpunit/phpunit": "~4.5"
+ },
+ "suggest": {
+ "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)",
+ "doctrine/cache": "Install it if you want to use the cache (version ~1.4)",
+ "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~1.0)"
+ },
+ "time": "2015-09-08 08:56:29",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "DI\\": "src/DI/"
+ },
+ "files": [
+ "src/DI/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "The dependency injection container for humans",
+ "homepage": "http://php-di.org/",
+ "keywords": [
+ "container",
+ "dependency injection",
+ "di"
+ ]
+ }
+]
--- /dev/null
+composer.lock
+composer.phar
+/vendor/
--- /dev/null
+The MIT License (MIT)
+
+Copyright (c) 2013 container-interop
+
+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.
--- /dev/null
+# Container Interoperability
+
+[![Latest Stable Version](https://poser.pugx.org/container-interop/container-interop/v/stable.png)](https://packagist.org/packages/container-interop/container-interop)
+
+*container-interop* tries to identify and standardize features in *container* objects (service locators,
+dependency injection containers, etc.) to achieve interopererability.
+
+Through discussions and trials, we try to create a standard, made of common interfaces but also recommendations.
+
+If PHP projects that provide container implementations begin to adopt these common standards, then PHP
+applications and projects that use containers can depend on the common interfaces instead of specific
+implementations. This facilitates a high-level of interoperability and flexibility that allows users to consume
+*any* container implementation that can be adapted to these interfaces.
+
+The work done in this project is not officially endorsed by the [PHP-FIG](http://www.php-fig.org/), but it is being
+worked on by members of PHP-FIG and other good developers. We adhere to the spirit and ideals of PHP-FIG, and hope
+this project will pave the way for one or more future PSRs.
+
+
+## Installation
+
+You can install this package through Composer:
+
+```json
+{
+ "require": {
+ "container-interop/container-interop": "~1.0"
+ }
+}
+```
+
+The packages adheres to the [SemVer](http://semver.org/) specification, and there will be full backward compatibility
+between minor versions.
+
+## Standards
+
+### Available
+
+- [`ContainerInterface`](src/Interop/Container/ContainerInterface.php).
+[Description](docs/ContainerInterface.md) [Meta Document](docs/ContainerInterface-meta.md).
+Describes the interface of a container that exposes methods to read its entries.
+- [*Delegate lookup feature*](docs/Delegate-lookup.md).
+[Meta Document](docs/Delegate-lookup-meta.md).
+Describes the ability for a container to delegate the lookup of its dependencies to a third-party container. This
+feature lets several containers work together in a single application.
+
+### Proposed
+
+View open [request for comments](https://github.com/container-interop/container-interop/labels/RFC)
+
+## Compatible projects
+
+### Projects implementing `ContainerInterface`
+
+- [Acclimate](https://github.com/jeremeamia/acclimate-container)
+- [dcp-di](https://github.com/estelsmith/dcp-di)
+- [Mouf](http://mouf-php.com)
+- [Njasm Container](https://github.com/njasm/container)
+- [PHP-DI](http://php-di.org)
+- [PimpleInterop](https://github.com/moufmouf/pimple-interop)
+- [XStatic](https://github.com/jeremeamia/xstatic)
+
+### Projects implementing the *delegate lookup* feature
+
+- [Mouf](http://mouf-php.com)
+- [PHP-DI](http://php-di.org)
+- [PimpleInterop](https://github.com/moufmouf/pimple-interop)
+
+## Workflow
+
+Everyone is welcome to join and contribute.
+
+The general workflow looks like this:
+
+1. Someone opens a discussion (GitHub issue) to suggest an interface
+1. Feedback is gathered
+1. The interface is added to a development branch
+1. We release alpha versions so that the interface can be experimented with
+1. Discussions and edits ensue until the interface is deemed stable by a general consensus
+1. A new minor version of the package is released
+
+We try to not break BC by creating new interfaces instead of editing existing ones.
+
+While we currently work on interfaces, we are open to anything that might help towards interoperability, may that
+be code, best practices, etc.
--- /dev/null
+{
+ "name": "container-interop/container-interop",
+ "type": "library",
+ "description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "Interop\\Container\\": "src/Interop/Container/"
+ }
+ }
+}
--- /dev/null
+# ContainerInterface Meta Document
+
+## Introduction
+
+This document describes the process and discussions that lead to the `ContainerInterface`.
+Its goal is to explain the reasons behind each decision.
+
+## Goal
+
+The goal set by `ContainerInterface` is to standardize how frameworks and libraries make use of a
+container to obtain objects and parameters.
+
+By standardizing such a behavior, frameworks and libraries using the `ContainerInterface`
+could work with any compatible container.
+That would allow end users to choose their own container based on their own preferences.
+
+It is important to distinguish the two usages of a container:
+
+- configuring entries
+- fetching entries
+
+Most of the time, those two sides are not used by the same party.
+While it is often end users who tend to configure entries, it is generally the framework that fetch
+entries to build the application.
+
+This is why this interface focuses only on how entries can be fetched from a container.
+
+## Interface name
+
+The interface name has been thoroughly discussed and was decided by a vote.
+
+The list of options considered with their respective votes are:
+
+- `ContainerInterface`: +8
+- `ProviderInterface`: +2
+- `LocatorInterface`: 0
+- `ReadableContainerInterface`: -5
+- `ServiceLocatorInterface`: -6
+- `ObjectFactory`: -6
+- `ObjectStore`: -8
+- `ConsumerInterface`: -9
+
+[Full results of the vote](https://github.com/container-interop/container-interop/wiki/%231-interface-name:-Vote)
+
+The complete discussion can be read in [the issue #1](https://github.com/container-interop/container-interop/issues/1).
+
+## Interface methods
+
+The choice of which methods the interface would contain was made after a statistical analysis of existing containers.
+The results of this analysis are available [in this document](https://gist.github.com/mnapoli/6159681).
+
+The summary of the analysis showed that:
+
+- all containers offer a method to get an entry by its id
+- a large majority name such method `get()`
+- for all containers, the `get()` method has 1 mandatory parameter of type string
+- some containers have an optional additional argument for `get()`, but it doesn't same the same purpose between containers
+- a large majority of the containers offer a method to test if it can return an entry by its id
+- a majority name such method `has()`
+- for all containers offering `has()`, the method has exactly 1 parameter of type string
+- a large majority of the containers throw an exception rather than returning null when an entry is not found in `get()`
+- a large majority of the containers don't implement `ArrayAccess`
+
+The question of whether to include methods to define entries has been discussed in
+[issue #1](https://github.com/container-interop/container-interop/issues/1).
+It has been judged that such methods do not belong in the interface described here because it is out of its scope
+(see the "Goal" section).
+
+As a result, the `ContainerInterface` contains two methods:
+
+- `get()`, returning anything, with one mandatory string parameter. Should throw an exception if the entry is not found.
+- `has()`, returning a boolean, with one mandatory string parameter.
+
+### Number of parameters in `get()` method
+
+While `ContainerInterface` only defines one mandatory parameter in `get()`, it is not incompatible with
+existing containers that have additional optional parameters. PHP allows an implementation to offer more parameters
+as long as they are optional, because the implementation *does* satisfy the interface.
+
+This issue has been discussed in [issue #6](https://github.com/container-interop/container-interop/issues/6).
+
+### Type of the `$id` parameter
+
+The type of the `$id` parameter in `get()` and `has()` has been discussed in
+[issue #6](https://github.com/container-interop/container-interop/issues/6).
+While `string` is used in all the containers that were analyzed, it was suggested that allowing
+anything (such as objects) could allow containers to offer a more advanced query API.
+
+An example given was to use the container as an object builder. The `$id` parameter would then be an
+object that would describe how to create an instance.
+
+The conclusion of the discussion was that this was beyond the scope of getting entries from a container without
+knowing how the container provided them, and it was more fit for a factory.
+
+## Contributors
+
+Are listed here all people that contributed in the discussions or votes, by alphabetical order:
+
+- [Amy Stephen](https://github.com/AmyStephen)
+- [David Négrier](https://github.com/moufmouf)
+- [Don Gilbert](https://github.com/dongilbert)
+- [Jason Judge](https://github.com/judgej)
+- [Jeremy Lindblom](https://github.com/jeremeamia)
+- [Marco Pivetta](https://github.com/Ocramius)
+- [Matthieu Napoli](https://github.com/mnapoli)
+- [Paul M. Jones](https://github.com/pmjones)
+- [Stephan Hochdörfer](https://github.com/shochdoerfer)
+- [Taylor Otwell](https://github.com/taylorotwell)
+
+## Relevant links
+
+- [`ContainerInterface.php`](https://github.com/container-interop/container-interop/blob/master/src/Interop/Container/ContainerInterface.php)
+- [List of all issues](https://github.com/container-interop/container-interop/issues?labels=ContainerInterface&milestone=&page=1&state=closed)
+- [Vote for the interface name](https://github.com/container-interop/container-interop/wiki/%231-interface-name:-Vote)
--- /dev/null
+Container interface
+===================
+
+This document describes a common interface for dependency injection containers.
+
+The goal set by `ContainerInterface` is to standardize how frameworks and libraries make use of a
+container to obtain objects and parameters (called *entries* in the rest of this document).
+
+The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
+"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
+interpreted as described in [RFC 2119][].
+
+The word `implementor` in this document is to be interpreted as someone
+implementing the `ContainerInterface` in a depency injection-related library or framework.
+Users of dependency injections containers (DIC) are refered to as `user`.
+
+[RFC 2119]: http://tools.ietf.org/html/rfc2119
+
+1. Specification
+-----------------
+
+### 1.1 Basics
+
+- The `Interop\Container\ContainerInterface` exposes two methods : `get` and `has`.
+
+- `get` takes one mandatory parameter: an entry identifier. It MUST be a string.
+ A call to `get` can return anything (a *mixed* value), or throws an exception if the identifier
+ is not known to the container. Two successive calls to `get` with the same
+ identifier SHOULD return the same value. However, depending on the `implementor`
+ design and/or `user` configuration, different values might be returned, so
+ `user` SHOULD NOT rely on getting the same value on 2 successive calls.
+ While `ContainerInterface` only defines one mandatory parameter in `get()`, implementations
+ MAY accept additional optional parameters.
+
+- `has` takes one unique parameter: an entry identifier. It MUST return `true`
+ if an entry identifier is known to the container and `false` if it is not.
+
+### 1.2 Exceptions
+
+Exceptions directly thrown by the container MUST implement the
+[`Interop\Container\Exception\ContainerException`](../src/Interop/Container/Exception/ContainerException.php).
+
+A call to the `get` method with a non-existing id should throw a
+[`Interop\Container\Exception\NotFoundException`](../src/Interop/Container/Exception/NotFoundException.php).
+
+### 1.3 Additional features
+
+This section describes additional features that MAY be added to a container. Containers are not
+required to implement these features to respect the ContainerInterface.
+
+#### 1.3.1 Delegate lookup feature
+
+The goal of the *delegate lookup* feature is to allow several containers to share entries.
+Containers implementing this feature can perform dependency lookups in other containers.
+
+Containers implementing this feature will offer a greater lever of interoperability
+with other containers. Implementation of this feature is therefore RECOMMENDED.
+
+A container implementing this feature:
+
+- MUST implement the `ContainerInterface`
+- MUST provide a way to register a delegate container (using a constructor parameter, or a setter,
+ or any possible way). The delegate container MUST implement the `ContainerInterface`.
+
+When a container is configured to use a delegate container for dependencies:
+
+- Calls to the `get` method should only return an entry if the entry is part of the container.
+ If the entry is not part of the container, an exception should be thrown
+ (as requested by the `ContainerInterface`).
+- Calls to the `has` method should only return `true` if the entry is part of the container.
+ If the entry is not part of the container, `false` should be returned.
+- If the fetched entry has dependencies, **instead** of performing
+ the dependency lookup in the container, the lookup is performed on the *delegate container*.
+
+Important! By default, the lookup SHOULD be performed on the delegate container **only**, not on the container itself.
+
+It is however allowed for containers to provide exception cases for special entries, and a way to lookup
+into the same container (or another container) instead of the delegate container.
+
+2. Package
+----------
+
+The interfaces and classes described as well as relevant exception are provided as part of the
+[container-interop/container-interop](https://packagist.org/packages/container-interop/container-interop) package.
+
+3. `Interop\Container\ContainerInterface`
+-----------------------------------------
+
+```php
+<?php
+namespace Interop\Container;
+
+use Interop\Container\Exception\ContainerException;
+use Interop\Container\Exception\NotFoundException;
+
+/**
+ * Describes the interface of a container that exposes methods to read its entries.
+ */
+interface ContainerInterface
+{
+ /**
+ * Finds an entry of the container by its identifier and returns it.
+ *
+ * @param string $id Identifier of the entry to look for.
+ *
+ * @throws NotFoundException No entry was found for this identifier.
+ * @throws ContainerException Error while retrieving the entry.
+ *
+ * @return mixed Entry.
+ */
+ public function get($id);
+
+ /**
+ * Returns true if the container can return an entry for the given identifier.
+ * Returns false otherwise.
+ *
+ * @param string $id Identifier of the entry to look for.
+ *
+ * @return boolean
+ */
+ public function has($id);
+}
+```
+
+4. `Interop\Container\Exception\ContainerException`
+---------------------------------------------------
+
+```php
+<?php
+namespace Interop\Container\Exception;
+
+/**
+ * Base interface representing a generic exception in a container.
+ */
+interface ContainerException
+{
+}
+```
+
+5. `Interop\Container\Exception\NotFoundException`
+---------------------------------------------------
+
+```php
+<?php
+namespace Interop\Container\Exception;
+
+/**
+ * No entry was found in the container.
+ */
+interface NotFoundException extends ContainerException
+{
+}
+```
--- /dev/null
+Delegate lookup feature Meta Document
+=====================================
+
+1. Summary
+----------
+
+This document describes the *delegate lookup feature*.
+Containers are not required to implement this feature to respect the `ContainerInterface`.
+However, containers implementing this feature will offer a greater lever of interoperability
+with other containers, allowing multiple containers to share entries in the same application.
+Implementation of this feature is therefore recommanded.
+
+2. Why Bother?
+--------------
+
+The [`ContainerInterface`](../src/Interop/Container/ContainerInterface.php) ([meta doc](ContainerInterface.md))
+standardizes how frameworks and libraries make use of a container to obtain objects and parameters.
+
+By standardizing such a behavior, frameworks and libraries relying on the `ContainerInterface`
+could work with any compatible container.
+That would allow end users to choose their own container based on their own preferences.
+
+The `ContainerInterface` is also enough if we want to have several containers side-by-side in the same
+application. For instance, this is what the [CompositeContainer](https://github.com/jeremeamia/acclimate-container/blob/master/src/CompositeContainer.php)
+class of [Acclimate](https://github.com/jeremeamia/acclimate-container) is designed for:
+
+![Side by side containers](images/side_by_side_containers.png)
+
+However, an instance in container 1 cannot reference an instance in container 2.
+
+It would be better if an instance of container 1 could reference an instance in container 2,
+and the opposite should be true.
+
+![Interoperating containers](images/interoperating_containers.png)
+
+In the sample above, entry 1 in container 1 is referencing entry 3 in container 2.
+
+3. Scope
+--------
+
+### 3.1 Goals
+
+The goal of the *delegate lookup* feature is to allow several containers to share entries.
+
+4. Approaches
+-------------
+
+### 4.1 Chosen Approach
+
+Containers implementing this feature can perform dependency lookups in other containers.
+
+A container implementing this feature:
+
+- must implement the `ContainerInterface`
+- must provide a way to register a *delegate container* (using a constructor parameter, or a setter, or any
+possible way). The *delegate container* must implement the `ContainerInterface`.
+
+When a *delegate container* is configured on a container:
+
+- Calls to the `get` method should only return an entry if the entry is part of the container.
+If the entry is not part of the container, an exception should be thrown (as required in the `ContainerInterface`).
+- Calls to the `has` method should only return *true* if the entry is part of the container.
+If the entry is not part of the container, *false* should be returned.
+ - Finally, the important part: if the entry we are fetching has dependencies,
+**instead** of perfoming the dependency lookup in the container, the lookup is performed on the *delegate container*.
+
+Important! By default, the lookup should be performed on the delegate container **only**, not on the container itself.
+
+It is however allowed for containers to provide exception cases for special entries, and a way to lookup into
+the same container (or another container) instead of the delegate container.
+
+### 4.2 Typical usage
+
+The *delegate container* will usually be a composite container. A composite container is a container that
+contains several other containers. When performing a lookup on a composite container, the inner containers are
+queried until one container returns an entry.
+An inner container implementing the *delegate lookup feature* will return entries it contains, but if these
+entries have dependencies, the dependencies lookup calls will be performed on the composite container, giving
+a chance to all containers to answer.
+
+Interestingly enough, the order in which containers are added in the composite container matters. Indeed,
+the first containers to be added in the composite container can "override" the entries of containers with
+lower priority.
+
+![Containers priority](images/priority.png)
+
+In the example above, "container 2" contains a controller "myController" and the controller is referencing an
+"entityManager" entry. "Container 1" contains also an entry named "entityManager".
+Without the *delegate lookup* feature, when requesting the "myController" instance to container 2, it would take
+in charge the instanciation of both entries.
+
+However, using the *delegate lookup* feature, here is what happens when we ask the composite controller for the
+"myController" instance:
+
+- The composite controller asks container 1 if if contains the "myController" instance. The answer is no.
+- The composite controller asks container 2 if if contains the "myController" instance. The answer is yes.
+- The composite controller performs a `get` call on container 2 for the "myController" instance.
+- Container 2 sees that "myController" has a dependency on "entityManager".
+- Container 2 delegates the lookup of "entityManager" to the composite controller.
+- The composite controller asks container 1 if if contains the "entityManager" instance. The answer is yes.
+- The composite controller performs a `get` call on container 1 for the "entityManager" instance.
+
+In the end, we get a controller instanciated by container 2 that references an entityManager instanciated
+by container 1.
+
+### 4.3 Alternative: the fallback strategy
+
+The first proposed approach we tried was to perform all the lookups in the "local" container,
+and if a lookup fails in the container, to use the delegate container. In this scenario, the
+delegate container is used in "fallback" mode.
+
+This strategy has been described in @moufmouf blog post: http://mouf-php.com/container-interop-whats-next (solution 1).
+It was also discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-33570697) and
+[here](https://github.com/container-interop/container-interop/pull/20#issuecomment-56599631).
+
+Problems with this strategy:
+
+- Heavy problem regarding infinite loops
+- Unable to overload a container entry with the delegate container entry
+
+### 4.4 Alternative: force implementing an interface
+
+The first proposed approach was to develop a `ParentAwareContainerInterface` interface.
+It was proposed here: https://github.com/container-interop/container-interop/pull/8
+
+The interface would have had the behaviour of the delegate lookup feature but would have forced the addition of
+a `setParentContainter` method:
+
+```php
+interface ParentAwareContainerInterface extends ReadableContainerInterface {
+ /**
+ * Sets the parent container associated to that container. This container will call
+ * the parent container to fetch dependencies.
+ *
+ * @param ContainerInterface $container
+ */
+ public function setParentContainer(ContainerInterface $container);
+}
+```
+
+The interface idea was first questioned by @Ocramius [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-51721777).
+@Ocramius expressed the idea that an interface should not contain setters, otherwise, it is forcing implementation
+details on the class implementing the interface.
+Then @mnapoli made a proposal for a "convention" [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-51841079),
+this idea was further discussed until all participants in the discussion agreed to remove the interface idea
+and replace it with a "standard" feature.
+
+**Pros:**
+
+If we had had an interface, we could have delegated the registration of the delegate/composite container to the
+the delegate/composite container itself.
+For instance:
+
+```php
+$containerA = new ContainerA();
+$containerB = new ContainerB();
+
+$compositeContainer = new CompositeContainer([$containerA, $containerB]);
+
+// The call to 'setParentContainer' is delegated to the CompositeContainer
+// It is not the responsibility of the user anymore.
+class CompositeContainer {
+ ...
+
+ public function __construct($containers) {
+ foreach ($containers as $container) {
+ if ($container instanceof ParentAwareContainerInterface) {
+ $container->setParentContainer($this);
+ }
+ }
+ ...
+ }
+}
+
+```
+
+**Cons:**
+
+Cons have been extensively discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-51721777).
+Basically, forcing a setter into an interface is a bad idea. Setters are similar to constructor arguments,
+and it's a bad idea to standardize a constructor: how the delegate container is configured into a container is an implementation detail. This outweights the benefits of the interface.
+
+### 4.4 Alternative: no exception case for delegate lookups
+
+Originally, the proposed wording for delegate lookup calls was:
+
+> Important! The lookup MUST be performed on the delegate container **only**, not on the container itself.
+
+This was later replaced by:
+
+> Important! By default, the lookup SHOULD be performed on the delegate container **only**, not on the container itself.
+>
+> It is however allowed for containers to provide exception cases for special entries, and a way to lookup
+> into the same container (or another container) instead of the delegate container.
+
+Exception cases have been allowed to avoid breaking dependencies with some services that must be provided
+by the container (on @njasm proposal). This was proposed here: https://github.com/container-interop/container-interop/pull/20#issuecomment-56597235
+
+### 4.5 Alternative: having one of the containers act as the composite container
+
+In real-life scenarios, we usually have a big framework (Symfony 2, Zend Framework 2, etc...) and we want to
+add another DI container to this container. Most of the time, the "big" framework will be responsible for
+creating the controller's instances, using it's own DI container. Until *container-interop* is fully adopted,
+the "big" framework will not be aware of the existence of a composite container that it should use instead
+of its own container.
+
+For this real-life use cases, @mnapoli and @moufmouf proposed to extend the "big" framework's DI container
+to make it act as a composite container.
+
+This has been discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-40367194)
+and [here](http://mouf-php.com/container-interop-whats-next#solution4).
+
+This was implemented in Symfony 2 using:
+
+- [interop.symfony.di](https://github.com/thecodingmachine/interop.symfony.di/tree/v0.1.0)
+- [framework interop](https://github.com/mnapoli/framework-interop/)
+
+This was implemented in Silex using:
+
+- [interop.silex.di](https://github.com/thecodingmachine/interop.silex.di)
+
+Having a container act as the composite container is not part of the delegate lookup standard because it is
+simply a temporary design pattern used to make existing frameworks that do not support yet ContainerInterop
+play nice with other DI containers.
+
+
+5. Implementations
+------------------
+
+The following projects already implement the delegate lookup feature:
+
+- [Mouf](http://mouf-php.com), through the [`setDelegateLookupContainer` method](https://github.com/thecodingmachine/mouf/blob/2.0/src/Mouf/MoufManager.php#L2120)
+- [PHP-DI](http://php-di.org/), through the [`$wrapperContainer` parameter of the constructor](https://github.com/mnapoli/PHP-DI/blob/master/src/DI/Container.php#L72)
+- [pimple-interop](https://github.com/moufmouf/pimple-interop), through the [`$container` parameter of the constructor](https://github.com/moufmouf/pimple-interop/blob/master/src/Interop/Container/Pimple/PimpleInterop.php#L62)
+
+6. People
+---------
+
+Are listed here all people that contributed in the discussions, by alphabetical order:
+
+- [Alexandru Pătrănescu](https://github.com/drealecs)
+- [Ben Peachey](https://github.com/potherca)
+- [David Négrier](https://github.com/moufmouf)
+- [Jeremy Lindblom](https://github.com/jeremeamia)
+- [Marco Pivetta](https://github.com/Ocramius)
+- [Matthieu Napoli](https://github.com/mnapoli)
+- [Nelson J Morais](https://github.com/njasm)
+- [Phil Sturgeon](https://github.com/philsturgeon)
+- [Stephan Hochdörfer](https://github.com/shochdoerfer)
+
+7. Relevant Links
+-----------------
+
+_**Note:** Order descending chronologically._
+
+- [Pull request on the delegate lookup feature](https://github.com/container-interop/container-interop/pull/20)
+- [Pull request on the interface idea](https://github.com/container-interop/container-interop/pull/8)
+- [Original article exposing the delegate lookup idea along many others](http://mouf-php.com/container-interop-whats-next)
+
--- /dev/null
+Delegate lookup feature
+=======================
+
+This document describes a standard for dependency injection containers.
+
+The goal set by the *delegate lookup* feature is to allow several containers to share entries.
+Containers implementing this feature can perform dependency lookups in other containers.
+
+Containers implementing this feature will offer a greater lever of interoperability
+with other containers. Implementation of this feature is therefore RECOMMENDED.
+
+The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
+"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
+interpreted as described in [RFC 2119][].
+
+The word `implementor` in this document is to be interpreted as someone
+implementing the delegate lookup feature in a dependency injection-related library or framework.
+Users of dependency injections containers (DIC) are refered to as `user`.
+
+[RFC 2119]: http://tools.ietf.org/html/rfc2119
+
+1. Vocabulary
+-------------
+
+In a dependency injection container, the container is used to fetch entries.
+Entries can have dependencies on other entries. Usually, these other entries are fetched by the container.
+
+The *delegate lookup* feature is the ability for a container to fetch dependencies in
+another container. In the rest of the document, the word "container" will reference the container
+implemented by the implementor. The word "delegate container" will reference the container we are
+fetching the dependencies from.
+
+2. Specification
+----------------
+
+A container implementing the *delegate lookup* feature:
+
+- MUST implement the [`ContainerInterface`](ContainerInterface.md)
+- MUST provide a way to register a delegate container (using a constructor parameter, or a setter,
+ or any possible way). The delegate container MUST implement the [`ContainerInterface`](ContainerInterface.md).
+
+When a container is configured to use a delegate container for dependencies:
+
+- Calls to the `get` method should only return an entry if the entry is part of the container.
+ If the entry is not part of the container, an exception should be thrown
+ (as requested by the [`ContainerInterface`](ContainerInterface.md)).
+- Calls to the `has` method should only return `true` if the entry is part of the container.
+ If the entry is not part of the container, `false` should be returned.
+- If the fetched entry has dependencies, **instead** of performing
+ the dependency lookup in the container, the lookup is performed on the *delegate container*.
+
+Important: By default, the dependency lookups SHOULD be performed on the delegate container **only**, not on the container itself.
+
+It is however allowed for containers to provide exception cases for special entries, and a way to lookup
+into the same container (or another container) instead of the delegate container.
+
+3. Package / Interface
+----------------------
+
+This feature is not tied to any code, interface or package.
--- /dev/null
+<?php
+/**
+ * @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
+ */
+
+namespace Interop\Container;
+
+use Interop\Container\Exception\ContainerException;
+use Interop\Container\Exception\NotFoundException;
+
+/**
+ * Describes the interface of a container that exposes methods to read its entries.
+ */
+interface ContainerInterface
+{
+ /**
+ * Finds an entry of the container by its identifier and returns it.
+ *
+ * @param string $id Identifier of the entry to look for.
+ *
+ * @throws NotFoundException No entry was found for this identifier.
+ * @throws ContainerException Error while retrieving the entry.
+ *
+ * @return mixed Entry.
+ */
+ public function get($id);
+
+ /**
+ * Returns true if the container can return an entry for the given identifier.
+ * Returns false otherwise.
+ *
+ * @param string $id Identifier of the entry to look for.
+ *
+ * @return boolean
+ */
+ public function has($id);
+}
--- /dev/null
+<?php
+/**
+ * @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
+ */
+
+namespace Interop\Container\Exception;
+
+/**
+ * Base interface representing a generic exception in a container.
+ */
+interface ContainerException
+{
+}
--- /dev/null
+<?php
+/**
+ * @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
+ */
+
+namespace Interop\Container\Exception;
+
+/**
+ * No entry was found in the container.
+ */
+interface NotFoundException extends ContainerException
+{
+}
--- /dev/null
+# 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.
--- /dev/null
+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.
--- /dev/null
+# 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.
--- /dev/null
+{
+ "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"
+ }
+}
--- /dev/null
+# 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);
+```
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?php
+
+namespace Invoker\Exception;
+
+/**
+ * Impossible to invoke the callable.
+ *
+ * @author Matthieu Napoli <matthieu@mnapoli.fr>
+ */
+class InvocationException extends \Exception
+{
+}
--- /dev/null
+<?php
+
+namespace Invoker\Exception;
+
+/**
+ * The given callable is not actually callable.
+ *
+ * @author Matthieu Napoli <matthieu@mnapoli.fr>
+ */
+class NotCallableException extends InvocationException
+{
+}
--- /dev/null
+<?php
+
+namespace Invoker\Exception;
+
+/**
+ * Not enough parameters could be resolved to invoke the callable.
+ *
+ * @author Matthieu Napoli <matthieu@mnapoli.fr>
+ */
+class NotEnoughParametersException extends InvocationException
+{
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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());
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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
+ );
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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)
+ ));
+ }
+}
--- /dev/null
+coverage_clover: clover.xml
+json_path: coveralls-upload.json
--- /dev/null
+# .gitattributes
+tests/ export-ignore
+website/ export-ignore
+doc/ export-ignore
+news/ export-ignore
+
+# Auto detect text files and perform LF normalization
+* text=auto
--- /dev/null
+/.idea/
+/vendor/
+/composer.phar
+/composer.lock
+/theme/
+/.couscous/
+/website/bower_components/
+/website/css/all.min.css
+/logo/
--- /dev/null
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - hhvm
+
+matrix:
+ include:
+ - php: 5.4
+ env: dependencies=lowest
+
+before_script:
+ - composer self-update
+ - if [[ $(phpenv version-name) == '5.6' ]]; then composer require satooshi/php-coveralls:dev-master -n ; fi
+ - if [[ $(phpenv version-name) != '5.6' ]]; then composer install -n ; fi
+ - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable -n; fi;
+
+script:
+ - if [[ $(phpenv version-name) == '5.6' ]]; then phpunit --coverage-clover clover.xml ; fi
+ - if [[ $(phpenv version-name) != '5.6' ]]; then phpunit ; fi
+
+after_script:
+ - if [[ $(phpenv version-name) == '5.6' ]]; then php vendor/bin/coveralls -v ; fi
--- /dev/null
+---
+layout: 404
+---
--- /dev/null
+# Contributing
+
+[![Build Status](https://travis-ci.org/PHP-DI/PHP-DI.png?branch=master)](https://travis-ci.org/PHP-DI/PHP-DI) [![Coverage Status](https://coveralls.io/repos/PHP-DI/PHP-DI/badge.png?branch=master)](https://coveralls.io/r/PHP-DI/PHP-DI?branch=master)
+
+PHP-DI is licensed under the MIT License.
+
+
+## Set up
+
+* Check out the sources using git or download them
+
+```bash
+$ git clone https://github.com/PHP-DI/PHP-DI.git
+```
+
+* Install the libraries using composer:
+
+```bash
+$ curl -s http://getcomposer.org/installer | php
+$ php composer.phar install
+```
+
+If you are running Windows or are having trouble, read [the official documentation](http://getcomposer.org/doc/00-intro.md#installation).
+
+
+## Run the tests
+
+The tests are run with [PHPUnit](http://www.phpunit.de/manual/current/en/installation.html):
+
+```bash
+$ phpunit
+```
+
+
+## Learning the internals
+
+Read the [How it works](doc/how-it-works.md) documentation.
+
+
+## What to do?
+
+- Add tests: pick up uncovered situations in the [code coverage report](https://coveralls.io/r/PHP-DI/PHP-DI)
+- Resolve issues: [issue list](https://github.com/PHP-DI/PHP-DI/issues)
+- Improve the documentation
+- …
+
+
+## Coding style
+
+The code follows PSR0, PSR1 and [PSR2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md).
+
+Also, do not hesitate to add your name to the author list of a class in the docblock if you improve it.
--- /dev/null
+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.
--- /dev/null
+---
+layout: home
+---
+
+PHP-DI is a Dependency Injection Container made for humans.
+
+[![Build Status](https://img.shields.io/travis/PHP-DI/PHP-DI.svg?style=flat-square)](https://travis-ci.org/PHP-DI/PHP-DI)
+[![HHVM Status](https://img.shields.io/hhvm/php-di/php-di.svg?style=flat-square)](http://hhvm.h4cc.de/package/php-di/php-di)
+[![Coverage Status](https://img.shields.io/coveralls/PHP-DI/PHP-DI/master.svg?style=flat-square)](https://coveralls.io/r/PHP-DI/PHP-DI?branch=master)
+[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/PHP-DI/PHP-DI.svg?style=flat-square)](https://scrutinizer-ci.com/g/PHP-DI/PHP-DI/?branch=master)
+[![Latest Version](https://img.shields.io/github/release/PHP-DI/PHP-DI.svg?style=flat-square)](https://packagist.org/packages/php-di/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/PHP-DI/PHP-DI.svg)](http://isitmaintained.com/project/PHP-DI/PHP-DI "Average time to resolve an issue")
+[![Percentage of issues still open](http://isitmaintained.com/badge/open/PHP-DI/PHP-DI.svg)](http://isitmaintained.com/project/PHP-DI/PHP-DI "Percentage of issues still open")
+
+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/PHP-DI/PHP-DI.png)](https://gitter.im/PHP-DI/PHP-DI)
--- /dev/null
+# Change log
+
+## 5.1
+
+Read the [news entry](news/16-php-di-5-1-released.md).
+
+Improvements:
+
+- [Zend Framework 2 integration](https://github.com/PHP-DI/ZF2-Bridge) (by @Rastusik)
+- [#308](https://github.com/PHP-DI/PHP-DI/pull/308): Instantiate factories using the container (`DI\factory(['FooFactory', 'create'])`)
+- Many performances improvements - some benchmarks show up to 35% performance improvements, real results may vary of course
+- Many documentation improvements (@jdreesen, @mindplay-dk, @mnapoli, @holtkamp, @Rastusik)
+- [#296](https://github.com/PHP-DI/PHP-DI/issues/296): Provide a faster `ArrayCache` implementation, mostly useful in micro-benchmarks
+
+Bugfixes:
+
+- [#257](https://github.com/PHP-DI/PHP-DI/issues/257) & [#274](https://github.com/PHP-DI/PHP-DI/issues/274): Private properties of parent classes are not injected when using annotations
+- [#300](https://github.com/PHP-DI/PHP-DI/pull/300): Exception if object definition extends an incompatible definition
+- [#306](https://github.com/PHP-DI/PHP-DI/issues/306): Errors when using parameters passed by reference (fixed by @bradynpoulsen)
+- [#318](https://github.com/PHP-DI/PHP-DI/issues/318): `Container::call()` ignores parameter's default value
+
+Internal changes:
+
+- [#276](https://github.com/PHP-DI/PHP-DI/pull/276): Tests now pass on Windows (@bgaillard)
+
+## 5.0
+
+This is the complete change log. You can also read the [migration guide](doc/migration/5.0.md) for upgrading, or [the news article](news/15-php-di-5-0-released.md) for a nicer introduction to this new version.
+
+Improvements:
+
+- Moved to an organization on GitHub: [github.com/PHP-DI/PHP-DI](https://github.com/PHP-DI/PHP-DI)
+- The package has been renamed to: from `mnapoli/php-di` to [`php-di/php-di`](https://packagist.org/packages/php-di/php-di)
+- New [Silex integration](doc/frameworks/silex.md)
+- Lighter package: from 10 to 3 Composer dependencies!
+- [#235](https://github.com/PHP-DI/PHP-DI/issues/235): `DI\link()` is now deprecated in favor of `DI\get()`. There is no BC break as `DI\link()` still works.
+- [#207](https://github.com/PHP-DI/PHP-DI/issues/207): Support for `DI\link()` in arrays
+- [#203](https://github.com/PHP-DI/PHP-DI/issues/203): New `DI\string()` helper ([documentation](doc/php-definitions.md))
+- [#208](https://github.com/PHP-DI/PHP-DI/issues/208): Support for nested definitions
+- [#226](https://github.com/PHP-DI/PHP-DI/pull/226): `DI\factory()` can now be omitted with closures:
+
+ ```php
+ // before
+ 'My\Class' => DI\factory(function () { ... })
+ // now (optional shortcut)
+ 'My\Class' => function () { ... }
+ ```
+- [#193](https://github.com/PHP-DI/PHP-DI/issues/193): `DI\object()->method()` now supports calling the same method twice (or more).
+- [#248](https://github.com/PHP-DI/PHP-DI/issues/248): New `DI\decorate()` helper to decorate a previously defined entry ([documentation](doc/definition-overriding.md))
+- [#215](https://github.com/PHP-DI/PHP-DI/pull/215): New `DI\add()` helper to add entries to an existing array ([documentation](doc/definition-overriding.md))
+- [#218](https://github.com/PHP-DI/PHP-DI/issues/218): `ContainerBuilder::addDefinitions()` can now take an array of definitions
+- [#211](https://github.com/PHP-DI/PHP-DI/pull/211): `ContainerBuilder::addDefinitions()` is now fluent (return `$this`)
+- [#250](https://github.com/PHP-DI/PHP-DI/issues/250): `Container::call()` now also accepts parameters not indexed by name as well as embedded definitions ([documentation](doc/container.md))
+- Various performance improvements, e.g. lower the number of files loaded, simpler architecture, …
+
+BC breaks:
+
+- PHP-DI now requires a version of PHP >= 5.4.0
+- The package is lighter by default:
+ - [#251](https://github.com/PHP-DI/PHP-DI/issues/251): Annotations are disabled by default, if you use annotations enable them with `$containerBuilder->useAnnotations(true)`. Additionally the `doctrine/annotations` package isn't required by default anymore, so you also need to run `composer require doctrine/annotations`.
+ - `doctrine/cache` is not installed by default anymore, you need to require it in `composer.json` (`~1.0`) if you want to configure a cache for PHP-DI
+ - [#198](https://github.com/PHP-DI/PHP-DI/issues/198): `ocramius/proxy-manager` is not installed by default anymore, you need to require it in `composer.json` (`~1.0`) if you want to use **lazy injection**
+- Closures are now converted into factory definitions automatically. If you ever defined a closure as a value (e.g. to have the closure injected in a class), you need to wrap the closure with the new `DI\value()` helper.
+- [#223](https://github.com/PHP-DI/PHP-DI/issues/223): `DI\ContainerInterface` was deprecated since v4.1 and has been removed
+
+Internal changes in case you were replacing/extending some parts:
+
+- the definition sources architecture has been refactored, if you defined custom definition sources you will need to update your code (it should be much easier now)
+- [#252](https://github.com/PHP-DI/PHP-DI/pull/252): `DI\Scope` internal implementation has changed. You are encouraged to use the constants (`DI\Scope::SINGLETON` and `DI\Scope::PROTOTYPE`) instead of the static methods, but backward compatibility is kept (static methods still work).
+- [#241](https://github.com/PHP-DI/PHP-DI/issues/241): `Container::call()` now uses the *Invoker* external library
+
+## 4.4
+
+Read the [news entry](news/13-php-di-4-4-released.md).
+
+- [#185](https://github.com/PHP-DI/PHP-DI/issues/185) Support for invokable objects in `Container::call()`
+- [#192](https://github.com/PHP-DI/PHP-DI/pull/192) Support for invokable classes in `Container::call()` (will instantiate the class)
+- [#184](https://github.com/PHP-DI/PHP-DI/pull/184) Option to ignore phpdoc errors
+
+## 4.3
+
+Read the [news entry](news/11-php-di-4-3-released.md).
+
+- [#176](https://github.com/PHP-DI/PHP-DI/pull/176) New definition type for reading environment variables: `DI\env()`
+- [#181](https://github.com/PHP-DI/PHP-DI/pull/181) `DI\FactoryInterface` and `DI\InvokerInterface` are now auto-registered inside the container so that you can inject them without any configuration needed
+- [#173](https://github.com/PHP-DI/PHP-DI/pull/173) `$container->call(['MyClass', 'method]);` will get `MyClass` from the container if `method()` is not a static method
+
+## 4.2.2
+
+- Fixed [#180](https://github.com/PHP-DI/PHP-DI/pull/180): `Container::call()` with object methods (`[$object, 'method']`) is now supported
+
+## 4.2.1
+
+- Support for PHP 5.3.3, which was previously incomplete because of a bug in the reflection (there is now a workaround for this bug)
+
+But if you can, seriously avoid this (really old) PHP version and upgrade.
+
+## 4.2
+
+Read the [news entry](news/10-php-di-4-2-released.md).
+
+**Minor BC-break**: Optional parameters (that were not configured) were injected, they are now ignored, which is what naturally makes sense since they are optional.
+Example:
+
+```php
+ public function __construct(Bar $bar = null)
+ {
+ $this->bar = $bar ?: $this->createDefaultBar();
+ }
+```
+
+Before 4.2, PHP-DI would try to inject a `Bar` instance. From 4.2 and onwards, it will inject `null`.
+
+Of course, you can still explicitly define an injection for the optional parameters and that will work.
+
+All changes:
+
+* [#162](https://github.com/PHP-DI/PHP-DI/pull/162) Added `Container::call()` to call functions with dependency injection
+* [#156](https://github.com/PHP-DI/PHP-DI/issues/156) Wildcards (`*`) in definitions
+* [#164](https://github.com/PHP-DI/PHP-DI/issues/164) Prototype scope is now available for `factory()` definitions too
+* FIXED [#168](https://github.com/PHP-DI/PHP-DI/pull/168) `Container::has()` now returns false for interfaces and abstract classes that are not mapped in the definitions
+* FIXED [#171](https://github.com/PHP-DI/PHP-DI/issues/171) Optional parameters are now ignored (not injected) if not set in the definitions (see the BC-break warning above)
+
+## 4.1
+
+Read the [news entry](news/09-php-di-4-1-released.md).
+
+BC-breaks: None.
+
+* [#138](https://github.com/PHP-DI/PHP-DI/issues/138) [Container-interop](https://github.com/container-interop/container-interop) compliance
+* [#143](https://github.com/PHP-DI/PHP-DI/issues/143) Much more explicit exception messages
+* [#157](https://github.com/PHP-DI/PHP-DI/issues/157) HHVM support
+* [#158](https://github.com/PHP-DI/PHP-DI/issues/158) Improved the documentation for [Symfony 2 integration](http://php-di.org/doc/frameworks/symfony2.html)
+
+## 4.0
+
+Major changes:
+
+* The configuration format has changed ([read more here to understand why](news/06-php-di-4-0-new-definitions.md))
+
+Read the migration guide if you are using 3.x: [Migration guide from 3.x to 4.0](doc/migration/4.0.md).
+
+BC-breaks:
+
+* YAML, XML and JSON definitions have been removed, and the PHP definition format has changed (see above)
+* `ContainerSingleton` has been removed
+* You cannot configure an injection as lazy anymore, you can only configure a container entry as lazy
+* The Container constructor now takes mandatory parameters. Use the ContainerBuilder to create a Container.
+* Removed `ContainerBuilder::setDefinitionsValidation()` (no definition validation anymore)
+* `ContainerBuilder::useReflection()` is now named: `ContainerBuilder::useAutowiring()`
+* `ContainerBuilder::addDefinitionsFromFile()` is now named: `ContainerBuilder::addDefinitions()`
+* The `$proxy` parameter in `Container::get($name, $proxy = true)` hase been removed. To get a proxy, you now need to define an entry as "lazy".
+
+Other changes:
+
+* Added `ContainerInterface` and `FactoryInterface`, both implemented by the container.
+* [#115](https://github.com/PHP-DI/PHP-DI/issues/115) Added `Container::has()`
+* [#142](https://github.com/PHP-DI/PHP-DI/issues/142) Added `Container::make()` to resolve an entry
+* [#127](https://github.com/PHP-DI/PHP-DI/issues/127) Added support for cases where PHP-DI is wrapped by another container (like Acclimate): PHP-DI can now use the wrapping container to perform injections
+* [#128](https://github.com/PHP-DI/PHP-DI/issues/128) Configure entry aliases
+* [#110](https://github.com/PHP-DI/PHP-DI/issues/110) XML definitions are not supported anymore
+* [#122](https://github.com/PHP-DI/PHP-DI/issues/122) JSON definitions are not supported anymore
+* `ContainerSingleton` has finally been removed
+* Added `ContainerBuilder::buildDevContainer()` to get started with a default container very easily.
+* [#99](https://github.com/PHP-DI/PHP-DI/issues/99) Fixed "`@param` with PHP internal type throws exception"
+
+## 3.5.1
+
+* FIXED [#126](https://github.com/PHP-DI/PHP-DI/issues/126): `Container::set` without effect if a value has already been set and retrieved
+
+## 3.5
+
+Read the [news entry](news/05-php-di-3-5.md).
+
+* Importing `@Inject` and `@Injectable` annotations is now optional! It means that you don't have to write `use DI\Annotation\Inject` anymore
+* FIXED [#124](https://github.com/PHP-DI/PHP-DI/issues/124): `@Injects` annotation conflicts with other annotations
+
+## 3.4
+
+Read the [news entry](news/04-php-di-3-4.md).
+
+* [#106](https://github.com/PHP-DI/PHP-DI/pull/106) You can now define arrays of values (in YAML, PHP, …) thanks to [@unkind](https://github.com/unkind)
+* [#98](https://github.com/PHP-DI/PHP-DI/issues/98) `ContainerBuilder` is now fluent thanks to [@drdamour](https://github.com/drdamour)
+* [#101](https://github.com/PHP-DI/PHP-DI/pull/101) Optional parameters are now supported: if you don't define a value to inject, their default value will be used
+* XML definitions have been deprecated, there weren't even documented and were not maintained. They will be removed in 4.0.
+* FIXED [#100](https://github.com/PHP-DI/PHP-DI/issues/100): bug for lazy injection in constructors
+
+## 3.3
+
+Read the [news entry](news/03-php-di-3-3.md).
+
+* Inject dependencies on an existing instance with `Container::injectOn` (work from [Jeff Flitton](https://github.com/jflitton): [#89](https://github.com/PHP-DI/PHP-DI/pull/89)).
+* [#86](https://github.com/PHP-DI/PHP-DI/issues/86): Optimized definition lookup (faster)
+* FIXED [#87](https://github.com/PHP-DI/PHP-DI/issues/87): Rare bug in the `PhpDocParser`, fixed by [drdamour](https://github.com/drdamour)
+
+## 3.2
+
+Read the [news entry](news/02-php-di-3-2.md).
+
+Small BC-break: PHP-DI 3.0 and 3.1 injected properties before calling the constructor. This was confusing and [not supported for internal classes](https://github.com/PHP-DI/PHP-DI/issues/74).
+From 3.2 and on, properties are injected after calling the constructor.
+
+* **[Lazy injection](doc/lazy-injection.md)**: it is now possible to use lazy injection on properties and methods (setters and constructors).
+* Lazy dependencies are now proxies that extend the class they proxy, so type-hinting works.
+* Addition of the **`ContainerBuilder`** object, that helps to [create and configure a `Container`](doc/container-configuration.md).
+* Some methods for configuring the Container have gone **deprecated** in favor of the `ContainerBuilder`. Fear not, these deprecated methods will remain until next major version (4.0).
+ * `Container::useReflection`, use ContainerBuilder::useReflection instead
+ * `Container::useAnnotations`, use ContainerBuilder::useAnnotations instead
+ * `Container::setDefinitionCache`, use ContainerBuilder::setDefinitionCache instead
+ * `Container::setDefinitionsValidation`, use ContainerBuilder::setDefinitionsValidation instead
+* The container is now auto-registered (as 'DI\Container'). You can now inject the container without registering it.
+
+## 3.1.1
+
+* Value definitions (`$container->set('foo', 80)`) are not cached anymore
+* FIXED [#82](https://github.com/PHP-DI/PHP-DI/issues/82): Serialization error when using a cache
+
+## 3.1
+
+Read the [news entry](news/01-php-di-3-1.md).
+
+* Zend Framework 1 integration through the [PHP-DI-ZF1 project](https://github.com/PHP-DI/PHP-DI-ZF1)
+* Fixed the order of priorities when you mix different definition sources (reflection, annotations, files, …). See [Definition overriding](doc/definition-overriding.md)
+* Now possible to define null values with `$container->set('foo', null)` (see [#79](https://github.com/PHP-DI/PHP-DI/issues/79)).
+* Deprecated usage of `ContainerSingleton`, will be removed in next major version (4.0)
+
+## 3.0.6
+
+* FIXED [#76](https://github.com/PHP-DI/PHP-DI/issues/76): Definition conflict when setting a closure for a class name
+
+## 3.0.5
+
+* FIXED [#70](https://github.com/PHP-DI/PHP-DI/issues/70): Definition conflict when setting a value for a class name
+
+## 3.0.4
+
+* FIXED [#69](https://github.com/PHP-DI/PHP-DI/issues/69): YamlDefinitionFileLoader crashes if YAML file is empty
+
+## 3.0.3
+
+* Fixed over-restrictive dependencies in composer.json
+
+## 3.0.2
+
+* [#64](https://github.com/PHP-DI/PHP-DI/issues/64): Non PHP-DI exceptions are not captured-rethrown anymore when injecting dependencies (cleaner stack trace)
+
+## 3.0.1
+
+* [#62](https://github.com/PHP-DI/PHP-DI/issues/62): When using aliases, definitions are now merged
+
+## 3.0
+
+Major compatibility breaks with 2.x.
+
+* The container is no longer a Singleton (but `ContainerSingleton::getInstance()` is available for fools who like it)
+* Setter injection
+* Constructor injection
+* Scopes: singleton (share the same instance of the class) or prototype (create a new instance each time it is fetched). Defined at class level.
+* Configuration is reworked from scratch. Now every configuration backend can do 100% of the job.
+* Provided configuration backends:
+ * Reflection
+ * Annotations: @Inject, @Injectable
+ * PHP code (`Container::set()`)
+ * PHP array
+ * YAML file
+* As a consequence, annotations are not mandatory anymore, all functionalities can be used with or without annotations.
+* Renamed `DI\Annotations\` to `DI\Annotation\`
+* `Container` no longer implements ArrayAccess, use only `$container->get($key)` now
+* ZF1 integration broken and removed (work in progress for next releases)
+* Code now follows PSR1 and PSR2 coding styles
+* FIXED: [#58](https://github.com/PHP-DI/PHP-DI/issues/58) Getting a proxy of an alias didn't work
+
+## 2.1
+
+* `use` statements to import classes from other namespaces are now taken into account with the `@var` annotation
+* Updated and lightened the dependencies : `doctrine/common` has been replaced with more specific `doctrine/annotations` and `doctrine/cache`
+
+## 2.0
+
+Major compatibility breaks with 1.x.
+
+* `Container::resolveDependencies()` has been renamed to `Container::injectAll()`
+* Dependencies are now injected **before** the constructor is called, and thus are available in the constructor
+* Merged `@Value` annotation with `@Inject`: no difference between value and bean injection anymore
+* Container implements ArrayAccess for get() and set() (`$container['db.host'] = 'localhost';`)
+* Ini configuration files removed: configuration is done in PHP
+* Allow to define beans within closures for lazy-loading
+* Switched to MIT License
+
+Warning:
+
+* If you use PHP 5.3 and __wakeup() methods, they will be called when PHP-DI creates new instances of those classes.
+
+## 1.1
+
+* Caching of annotations based on Doctrine caches
+
+## 1.0
+
+* DependencyManager renamed to Container
+* Refactored basic Container usage with `get` and `set`
+* Allow named injection `@Inject(name="")`
+* Zend Framework integration
--- /dev/null
+{
+ "name": "php-di/php-di",
+ "type": "library",
+ "description": "The dependency injection container for humans",
+ "keywords": ["di", "dependency injection", "container"],
+ "homepage": "http://php-di.org/",
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "DI\\": "src/DI/"
+ },
+ "files": [
+ "src/DI/functions.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "DI\\Test\\IntegrationTest\\": "tests/IntegrationTest/",
+ "DI\\Test\\UnitTest\\": "tests/UnitTest/"
+ }
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "container-interop/container-interop": "~1.0",
+ "php-di/invoker": "^1.0.1",
+ "php-di/phpdoc-reader": "~2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.5",
+ "mnapoli/phpunit-easymock": "~0.1.4",
+ "doctrine/cache": "~1.4",
+ "doctrine/annotations": "~1.2",
+ "ocramius/proxy-manager": "~1.0"
+ },
+ "replace": {
+ "mnapoli/php-di": "*"
+ },
+ "suggest": {
+ "doctrine/cache": "Install it if you want to use the cache (version ~1.4)",
+ "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)",
+ "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~1.0)"
+ }
+}
--- /dev/null
+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:
+ section: Definitions
+ items:
+ introduction:
+ text: Introduction
+ url: doc/definition.html
+ autowiring:
+ text: Autowiring
+ url: doc/autowiring.html
+ php:
+ text: PHP definitions
+ url: doc/php-definitions.html
+ annotations:
+ text: Annotations
+ url: doc/annotations.html
+ definition-overriding:
+ text: Definition extensions and overriding
+ url: doc/definition-overriding.html
+ frameworks:
+ section: Frameworks
+ items:
+ symfony2:
+ text: Symfony 2
+ url: doc/frameworks/symfony2.html
+ silex:
+ text: Silex
+ url: doc/frameworks/silex.html
+ zf2:
+ text: Zend Framework 2
+ url: doc/frameworks/zf2.html
+ zf1:
+ text: Zend Framework 1
+ url: doc/frameworks/zf1.html
+ silly:
+ text: Silly
+ url: doc/frameworks/silly.html
+ advanced:
+ section: Advanced topics
+ items:
+ performances:
+ text: Performances
+ url: doc/performances.html
+ scopes:
+ text: Scopes
+ url: doc/scopes.html
+ lazy-injection:
+ text: Lazy injection
+ url: doc/lazy-injection.html
+ inject-on-instance:
+ text: Inject on an existing instance
+ url: doc/inject-on-instance.html
+ environments:
+ text: Injections depending on the environment
+ url: doc/environments.html
+ 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/PHP-DI/PHP-DI/tree/3.x/doc
+ v4:
+ text: PHP-DI 4.x
+ absoluteUrl: https://github.com/PHP-DI/PHP-DI/tree/4.x/doc
--- /dev/null
+<?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>
--- /dev/null
+<?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 = [];
+
+ /**
+ * @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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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\Cache;
+
+use Doctrine\Common\Cache\Cache;
+use Doctrine\Common\Cache\ClearableCache;
+use Doctrine\Common\Cache\FlushableCache;
+
+/**
+ * Simple implementation of a cache based on an array.
+ *
+ * This implementation can be used instead of Doctrine's ArrayCache for
+ * better performances (because simpler implementation).
+ *
+ * The code is based on Doctrine's ArrayCache provider:
+ * @see \Doctrine\Common\Cache\ArrayCache
+ * @link www.doctrine-project.org
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author David Abdemoulaie <dave@hobodave.com>
+ */
+class ArrayCache implements Cache, FlushableCache, ClearableCache
+{
+ /**
+ * @var array $data
+ */
+ private $data = [];
+
+ public function fetch($id)
+ {
+ return $this->contains($id) ? $this->data[$id] : false;
+ }
+
+ public function contains($id)
+ {
+ // isset() is required for performance optimizations, to avoid unnecessary function calls to array_key_exists.
+ return isset($this->data[$id]) || array_key_exists($id, $this->data);
+ }
+
+ public function save($id, $data, $lifeTime = 0)
+ {
+ $this->data[$id] = $data;
+
+ return true;
+ }
+
+ public function delete($id)
+ {
+ unset($this->data[$id]);
+
+ return true;
+ }
+
+ public function getStats()
+ {
+ return null;
+ }
+
+ public function flushAll()
+ {
+ $this->data = array();
+
+ return true;
+ }
+
+ public function deleteAll()
+ {
+ return $this->flushAll();
+ }
+}
--- /dev/null
+<?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\DefaultValueResolver;
+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 = [];
+
+ /**
+ * @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 = [];
+
+ /**
+ * @var \Invoker\InvokerInterface|null
+ */
+ private $invoker;
+
+ /**
+ * Container that wraps this container. If none, points to $this.
+ *
+ * @var ContainerInterface
+ */
+ private $wrapperContainer;
+
+ /**
+ * Use the ContainerBuilder to ease constructing the Container.
+ *
+ * @see ContainerBuilder
+ *
+ * @param DefinitionSource $definitionSource
+ * @param ProxyFactory $proxyFactory
+ * @param ContainerInterface $wrapperContainer If the container is wrapped by another container.
+ */
+ public function __construct(
+ DefinitionSource $definitionSource,
+ ProxyFactory $proxyFactory,
+ ContainerInterface $wrapperContainer = null
+ ) {
+ $this->wrapperContainer = $wrapperContainer ?: $this;
+
+ $this->definitionSource = $definitionSource;
+ $this->definitionResolver = new ResolverDispatcher($this->wrapperContainer, $proxyFactory);
+
+ // Auto-register the container
+ $this->singletonEntries['DI\Container'] = $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 = [])
+ {
+ 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 = [])
+ {
+ return $this->getInvoker()->call($callable, $parameters);
+ }
+
+ /**
+ * Define an object or a value in the container.
+ *
+ * @param string $name Entry name
+ * @param mixed|DefinitionHelper $value Value, use definition helpers to define objects
+ */
+ public function set($name, $value)
+ {
+ if ($value instanceof DefinitionHelper) {
+ $value = $value->getDefinition($name);
+ } elseif ($value instanceof \Closure) {
+ $value = new FactoryDefinition($name, $value);
+ }
+
+ if ($value instanceof Definition) {
+ $this->setDefinition($name, $value);
+ } else {
+ $this->singletonEntries[$name] = $value;
+ }
+ }
+
+ /**
+ * Resolves a definition.
+ *
+ * Checks for circular dependencies while resolving the definition.
+ *
+ * @param Definition $definition
+ * @param array $parameters
+ *
+ * @throws DependencyException Error while resolving the entry.
+ * @return mixed
+ */
+ private function resolveDefinition(Definition $definition, array $parameters = [])
+ {
+ $entryName = $definition->getName();
+
+ // Check if we are already getting this entry -> circular dependency
+ if (isset($this->entriesBeingResolved[$entryName])) {
+ throw new DependencyException("Circular dependency detected while trying to resolve entry '$entryName'");
+ }
+ $this->entriesBeingResolved[$entryName] = true;
+
+ // Resolve the definition
+ try {
+ $value = $this->definitionResolver->resolve($definition, $parameters);
+ } catch (Exception $exception) {
+ unset($this->entriesBeingResolved[$entryName]);
+ throw $exception;
+ }
+
+ unset($this->entriesBeingResolved[$entryName]);
+
+ return $value;
+ }
+
+ private function setDefinition($name, Definition $definition)
+ {
+ if ($this->definitionSource instanceof CachedDefinitionSource) {
+ throw new \LogicException('You cannot set a definition at runtime on a container that has a cache configured. Doing so would risk caching the definition for the next execution, where it might be different. You can either put your definitions in a file, remove the cache or ->set() a raw value directly (PHP object, string, int, ...) instead of a PHP-DI definition.');
+ }
+
+ if (! $this->definitionSource instanceof MutableDefinitionSource) {
+ // This can happen if you instantiate the container yourself
+ throw new \LogicException('The container has not been initialized correctly');
+ }
+
+ // Clear existing entry if it exists
+ if (array_key_exists($name, $this->singletonEntries)) {
+ unset($this->singletonEntries[$name]);
+ }
+
+ $this->definitionSource->addDefinition($definition);
+ }
+
+ /**
+ * @return \Invoker\InvokerInterface
+ */
+ private function getInvoker()
+ {
+ if (! $this->invoker) {
+ $parameterResolver = new ResolverChain([
+ new DefinitionParameterResolver($this->definitionResolver),
+ new NumericArrayResolver,
+ new AssociativeArrayResolver,
+ new DefaultValueResolver,
+ new TypeHintContainerResolver($this->wrapperContainer),
+ ]);
+
+ $this->invoker = new Invoker($parameterResolver, $this);
+ }
+
+ return $this->invoker;
+ }
+}
--- /dev/null
+<?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 = [];
+
+ /**
+ * 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;
+ }
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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
+{
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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();
+}
--- /dev/null
+<?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()
+ );
+ }
+}
--- /dev/null
+<?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(PHP_EOL, PHP_EOL . " ", $str);
+ }
+}
--- /dev/null
+<?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() . ')';
+ }
+}
--- /dev/null
+<?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);
+}
--- /dev/null
+<?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 = [];
+
+ 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 = [
+ '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(),
+ ];
+ }
+ }
+}
--- /dev/null
+<?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 .= PHP_EOL . " 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 .= PHP_EOL . " default = " . $defaultValueStr;
+ }
+
+ return sprintf(
+ "Environment variable (" . PHP_EOL . "%s" . PHP_EOL . ")",
+ $str
+ );
+ }
+
+ private function indent($str)
+ {
+ return str_replace(PHP_EOL, PHP_EOL . " ", $str);
+ }
+}
--- /dev/null
+<?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';
+ }
+}
--- /dev/null
+<?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 .= PHP_EOL . " scope = " . $definition->getScope();
+
+ // Lazy
+ $str .= PHP_EOL . " lazy = " . var_export($definition->isLazy(), true);
+
+ if ($classExist) {
+ // Constructor
+ $str .= $this->dumpConstructor($className, $definition);
+
+ // Properties
+ $str .= $this->dumpProperties($definition);
+
+ // Methods
+ $str .= $this->dumpMethods($className, $definition);
+ }
+
+ return sprintf("Object (" . PHP_EOL . "%s" . PHP_EOL . ")", $str);
+ }
+
+ private function dumpConstructor($className, ObjectDefinition $definition)
+ {
+ $str = '';
+
+ $constructorInjection = $definition->getConstructorInjection();
+
+ if ($constructorInjection !== null) {
+ $parameters = $this->dumpMethodParameters($className, $constructorInjection);
+
+ $str .= sprintf(PHP_EOL . " __construct(" . PHP_EOL . " %s" . PHP_EOL . " )", $parameters);
+ }
+
+ return $str;
+ }
+
+ private function dumpProperties(ObjectDefinition $definition)
+ {
+ $str = '';
+
+ foreach ($definition->getPropertyInjections() as $propertyInjection) {
+ $value = $propertyInjection->getValue();
+ if ($value instanceof EntryReference) {
+ $valueStr = sprintf('get(%s)', $value->getName());
+ } else {
+ $valueStr = var_export($value, true);
+ }
+
+ $str .= sprintf(PHP_EOL . " $%s = %s", $propertyInjection->getPropertyName(), $valueStr);
+ }
+
+ return $str;
+ }
+
+ private function dumpMethods($className, ObjectDefinition $definition)
+ {
+ $str = '';
+
+ foreach ($definition->getMethodInjections() as $methodInjection) {
+ $parameters = $this->dumpMethodParameters($className, $methodInjection);
+
+ $str .= sprintf(PHP_EOL . " %s(" . PHP_EOL . " %s" . PHP_EOL . " )", $methodInjection->getMethodName(), $parameters);
+ }
+
+ return $str;
+ }
+
+ private function dumpMethodParameters($className, MethodInjection $methodInjection)
+ {
+ $methodReflection = new \ReflectionMethod($className, $methodInjection->getMethodName());
+
+ $args = [];
+
+ $definitionParameters = $methodInjection->getParameters();
+
+ foreach ($methodReflection->getParameters() as $index => $parameter) {
+ if (array_key_exists($index, $definitionParameters)) {
+ $value = $definitionParameters[$index];
+
+ if ($value instanceof EntryReference) {
+ $args[] = sprintf('$%s = get(%s)', $parameter->getName(), $value->getName());
+ } else {
+ $args[] = sprintf('$%s = %s', $parameter->getName(), var_export($value, true));
+ }
+ continue;
+ }
+
+ // If the parameter is optional and wasn't specified, we take its default value
+ if ($parameter->isOptional()) {
+ try {
+ $value = $parameter->getDefaultValue();
+
+ $args[] = sprintf(
+ '$%s = (default value) %s',
+ $parameter->getName(),
+ var_export($value, true)
+ );
+ continue;
+ } catch (ReflectionException $e) {
+ // The default value can't be read through Reflection because it is a PHP internal class
+ }
+ }
+
+ $args[] = sprintf('$%s = #UNDEFINED#', $parameter->getName());
+ }
+
+ return implode(PHP_EOL . ' ', $args);
+ }
+}
--- /dev/null
+<?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();
+ }
+}
--- /dev/null
+<?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 (" . PHP_EOL . " %s" . PHP_EOL . ")",
+ trim(ob_get_clean())
+ );
+ }
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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
+{
+}
--- /dev/null
+<?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" . PHP_EOL . "Full definition:" . PHP_EOL . "%s",
+ $message,
+ Debug::dumpDefinition($definition)
+ ));
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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);
+}
--- /dev/null
+<?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 = [];
+
+ /**
+ * @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);
+ }
+}
--- /dev/null
+<?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);
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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;
+use DI\Definition\Exception\DefinitionException;
+
+/**
+ * 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 of properties and their value.
+ * @var array
+ */
+ private $properties = [];
+
+ /**
+ * Array of methods and their parameters.
+ * @var array
+ */
+ private $methods = [];
+
+ /**
+ * Helper for defining an object.
+ *
+ * @param string|null $className Class name of the object.
+ * If null, the name of the entry (in the container) will be used as class name.
+ */
+ public function __construct($className = null)
+ {
+ $this->className = $className;
+ }
+
+ /**
+ * Define the entry as lazy.
+ *
+ * A lazy entry is created only when it is used, a proxy is injected instead.
+ *
+ * @return ObjectDefinitionHelper
+ */
+ public function lazy()
+ {
+ $this->lazy = true;
+ return $this;
+ }
+
+ /**
+ * Defines the scope of the entry.
+ *
+ * @param string $scope
+ *
+ * @return ObjectDefinitionHelper
+ */
+ public function scope($scope)
+ {
+ $this->scope = $scope;
+ return $this;
+ }
+
+ /**
+ * Defines the arguments to use to call the constructor.
+ *
+ * This method takes a variable number of arguments, example:
+ * ->constructor($param1, $param2, $param3)
+ *
+ * @param mixed ... Parameters to use for calling the constructor of the class.
+ *
+ * @return ObjectDefinitionHelper
+ */
+ public function constructor()
+ {
+ $this->constructor = func_get_args();
+ return $this;
+ }
+
+ /**
+ * Defines a value for a specific argument of the constructor.
+ *
+ * This method is usually used together with annotations or autowiring, when a parameter
+ * is not (or cannot be) type-hinted. Using this method instead of constructor() allows to
+ * avoid defining all the parameters (letting them being resolved using annotations or autowiring)
+ * and only define one.
+ *
+ * @param string $parameter Parameter for which the value will be given.
+ * @param mixed $value Value to give to this parameter.
+ *
+ * @return ObjectDefinitionHelper
+ */
+ public function constructorParameter($parameter, $value)
+ {
+ $this->constructor[$parameter] = $value;
+ return $this;
+ }
+
+ /**
+ * Defines a value to inject in a property of the object.
+ *
+ * @param string $property Entry in which to inject the value.
+ * @param mixed $value Value to inject in the property.
+ *
+ * @return ObjectDefinitionHelper
+ */
+ public function property($property, $value)
+ {
+ $this->properties[$property] = $value;
+ return $this;
+ }
+
+ /**
+ * Defines a method to call and the arguments to use.
+ *
+ * This method takes a variable number of arguments after the method name, example:
+ *
+ * ->method('myMethod', $param1, $param2)
+ *
+ * Can be used multiple times to declare multiple calls.
+ *
+ * @param string $method Name of the method to call.
+ * @param mixed ... Parameters to use for calling the method.
+ *
+ * @return ObjectDefinitionHelper
+ */
+ public function method($method)
+ {
+ $args = func_get_args();
+ array_shift($args);
+
+ if (! isset($this->methods[$method])) {
+ $this->methods[$method] = [];
+ }
+
+ $this->methods[$method][] = $args;
+
+ return $this;
+ }
+
+ /**
+ * Defines a method to call and a value for a specific argument.
+ *
+ * This method is usually used together with annotations or autowiring, when a parameter
+ * is not (or cannot be) type-hinted. Using this method instead of method() allows to
+ * avoid defining all the parameters (letting them being resolved using annotations or
+ * autowiring) and only define one.
+ *
+ * If multiple calls to the method have been configured already (e.g. in a previous definition)
+ * then this method only overrides the parameter for the *first* call.
+ *
+ * @param string $method Name of the method to call.
+ * @param string $parameter Name or index of the parameter for which the value will be given.
+ * @param mixed $value Value to give to this parameter.
+ *
+ * @return ObjectDefinitionHelper
+ */
+ public function methodParameter($method, $parameter, $value)
+ {
+ // Special case for the constructor
+ if ($method === '__construct') {
+ $this->constructor[$parameter] = $value;
+ return $this;
+ }
+
+ if (! isset($this->methods[$method])) {
+ $this->methods[$method] = [0 => []];
+ }
+
+ $this->methods[$method][0][$parameter] = $value;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDefinition($entryName)
+ {
+ $definition = new ObjectDefinition($entryName, $this->className);
+
+ if ($this->lazy !== null) {
+ $definition->setLazy($this->lazy);
+ }
+ if ($this->scope !== null) {
+ $definition->setScope($this->scope);
+ }
+
+ if (! empty($this->constructor)) {
+ $parameters = $this->fixParameters($definition, '__construct', $this->constructor);
+ $constructorInjection = MethodInjection::constructor($parameters);
+ $definition->setConstructorInjection($constructorInjection);
+ }
+
+ if (! empty($this->properties)) {
+ foreach ($this->properties as $property => $value) {
+ $definition->addPropertyInjection(
+ new PropertyInjection($property, $value)
+ );
+ }
+ }
+
+ if (! empty($this->methods)) {
+ foreach ($this->methods as $method => $calls) {
+ foreach ($calls as $parameters) {
+ $parameters = $this->fixParameters($definition, $method, $parameters);
+ $methodInjection = new MethodInjection($method, $parameters);
+ $definition->addMethodInjection($methodInjection);
+ }
+ }
+ }
+
+ return $definition;
+ }
+
+ /**
+ * Fixes parameters indexed by the parameter name -> reindex by position.
+ *
+ * This is necessary so that merging definitions between sources is possible.
+ *
+ * @param ObjectDefinition $definition
+ * @param string $method
+ * @param array $parameters
+ * @throws DefinitionException
+ * @return array
+ */
+ private function fixParameters(ObjectDefinition $definition, $method, $parameters)
+ {
+ $fixedParameters = [];
+
+ foreach ($parameters as $index => $parameter) {
+ // Parameter indexed by the parameter name, we reindex it with its position
+ if (is_string($index)) {
+ $callable = [$definition->getClassName(), $method];
+ try {
+ $reflectionParameter = new \ReflectionParameter($callable, $index);
+ } catch (\ReflectionException $e) {
+ throw DefinitionException::create($definition, "Parameter with name '$index' could not be found");
+ }
+
+ $index = $reflectionParameter->getPosition();
+ }
+
+ $fixedParameters[$index] = $parameter;
+ }
+
+ return $fixedParameters;
+ }
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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\Scope;
+use ReflectionClass;
+
+/**
+ * Defines how an object can be instantiated.
+ *
+ * @author Matthieu Napoli <matthieu@mnapoli.fr>
+ */
+class ObjectDefinition implements Definition, CacheableDefinition, HasSubDefinition
+{
+ /**
+ * Entry name (most of the time, same as $classname)
+ * @var string
+ */
+ private $name;
+
+ /**
+ * Class name (if null, then the class name is $name)
+ * @var string|null
+ */
+ private $className;
+
+ /**
+ * Constructor parameter injection
+ * @var MethodInjection|null
+ */
+ private $constructorInjection;
+
+ /**
+ * Property injections
+ * @var PropertyInjection[]
+ */
+ private $propertyInjections = [];
+
+ /**
+ * Method calls
+ * @var MethodInjection[][]
+ */
+ private $methodInjections = [];
+
+ /**
+ * @var string|null
+ */
+ private $scope;
+
+ /**
+ * @var boolean|null
+ */
+ private $lazy;
+
+ /**
+ * Store if the class exists. Storing it (in cache) avoids recomputing this.
+ *
+ * @var bool
+ */
+ private $classExists;
+
+ /**
+ * Store if the class is instantiable. Storing it (in cache) avoids recomputing this.
+ *
+ * @var bool
+ */
+ private $isInstantiable;
+
+ /**
+ * @param string $name Class name
+ * @param string $className
+ */
+ public function __construct($name, $className = null)
+ {
+ $this->name = (string) $name;
+ $this->setClassName($className);
+ }
+
+ /**
+ * @return string Entry name
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string|null $className
+ */
+ public function setClassName($className)
+ {
+ $this->className = $className;
+
+ $this->updateCache();
+ }
+
+ /**
+ * @return string Class name
+ */
+ public function getClassName()
+ {
+ if ($this->className !== null) {
+ return $this->className;
+ }
+ return $this->name;
+ }
+
+ /**
+ * @return MethodInjection|null
+ */
+ public function getConstructorInjection()
+ {
+ return $this->constructorInjection;
+ }
+
+ /**
+ * @param MethodInjection $constructorInjection
+ */
+ public function setConstructorInjection(MethodInjection $constructorInjection)
+ {
+ $this->constructorInjection = $constructorInjection;
+ }
+
+ /**
+ * @return PropertyInjection[] Property injections
+ */
+ public function getPropertyInjections()
+ {
+ return $this->propertyInjections;
+ }
+
+ /**
+ * @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_walk_recursive($this->methodInjections, function ($injection) use (&$injections) {
+ $injections[] = $injection;
+ });
+ return $injections;
+ }
+
+ /**
+ * @param MethodInjection $methodInjection
+ */
+ public function addMethodInjection(MethodInjection $methodInjection)
+ {
+ $method = $methodInjection->getMethodName();
+ if (! isset($this->methodInjections[$method])) {
+ $this->methodInjections[$method] = [];
+ }
+ $this->methodInjections[$method][] = $methodInjection;
+ }
+
+ /**
+ * @param string $scope
+ */
+ public function setScope($scope)
+ {
+ $this->scope = $scope;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getScope()
+ {
+ return $this->scope ?: Scope::SINGLETON;
+ }
+
+ /**
+ * @param 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;
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function classExists()
+ {
+ return $this->classExists;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isInstantiable()
+ {
+ return $this->isInstantiable;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSubDefinitionName()
+ {
+ return $this->getClassName();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setSubDefinition(Definition $definition)
+ {
+ if (! $definition instanceof ObjectDefinition) {
+ return;
+ }
+
+ // The current prevails
+ if ($this->className === null) {
+ $this->setClassName($definition->className);
+ }
+ if ($this->scope === null) {
+ $this->scope = $definition->scope;
+ }
+ if ($this->lazy === null) {
+ $this->lazy = $definition->lazy;
+ }
+
+ // Merge constructor injection
+ $this->mergeConstructorInjection($definition);
+
+ // Merge property injections
+ $this->mergePropertyInjections($definition);
+
+ // Merge method injections
+ $this->mergeMethodInjections($definition);
+ }
+
+ 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;
+ }
+ }
+ }
+
+ private function updateCache()
+ {
+ $className = $this->getClassName();
+
+ $this->classExists = class_exists($className) || interface_exists($className);
+
+ if (! $this->classExists) {
+ $this->isInstantiable = false;
+ return;
+ }
+
+ $class = new ReflectionClass($className);
+ $this->isInstantiable = $class->isInstantiable();
+ }
+}
--- /dev/null
+<?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\Definition;
+use DI\Scope;
+
+/**
+ * Describe an injection in an object method.
+ *
+ * @author Matthieu Napoli <matthieu@mnapoli.fr>
+ */
+class MethodInjection implements Definition
+{
+ /**
+ * @var string
+ */
+ private $methodName;
+
+ /**
+ * @var array
+ */
+ private $parameters = [];
+
+ /**
+ * @param string $methodName
+ * @param array $parameters
+ */
+ public function __construct($methodName, array $parameters = [])
+ {
+ $this->methodName = (string) $methodName;
+ $this->parameters = $parameters;
+ }
+
+ public static function constructor(array $parameters = [])
+ {
+ return new self('__construct', $parameters);
+ }
+
+ /**
+ * @return string Method name
+ */
+ public function getMethodName()
+ {
+ return $this->methodName;
+ }
+
+ /**
+ * @return array
+ */
+ public function getParameters()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * Replace the parameters of the definition by a new array of parameters.
+ *
+ * @param array $parameters
+ */
+ public function replaceParameters(array $parameters)
+ {
+ $this->parameters = $parameters;
+ }
+
+ public function merge(MethodInjection $definition)
+ {
+ // In case of conflicts, the current definition prevails.
+ $this->parameters = $this->parameters + $definition->parameters;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getScope()
+ {
+ return Scope::PROTOTYPE;
+ }
+}
--- /dev/null
+<?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;
+
+ /**
+ * Use for injecting in properties of parent classes: the class name
+ * must be the name of the parent class because private properties
+ * can be attached to the parent classes, not the one we are resolving.
+ * @var string|null
+ */
+ private $className;
+
+ /**
+ * @param string $propertyName Property name
+ * @param mixed $value Value that should be injected in the property
+ * @param string|null $className
+ */
+ public function __construct($propertyName, $value, $className = null)
+ {
+ $this->propertyName = (string) $propertyName;
+ $this->value = $value;
+ $this->className = $className;
+ }
+
+ /**
+ * @return string Property name
+ */
+ public function getPropertyName()
+ {
+ return $this->propertyName;
+ }
+
+ /**
+ * @return string Value that should be injected in the property
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getClassName()
+ {
+ return $this->className;
+ }
+}
--- /dev/null
+<?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 = [])
+ {
+ return $this->container->get($definition->getTargetEntryName());
+ }
+
+ /**
+ * @param AliasDefinition $definition
+ *
+ * {@inheritdoc}
+ */
+ public function isResolvable(Definition $definition, array $parameters = [])
+ {
+ return $this->container->has($definition->getTargetEntryName());
+ }
+}
--- /dev/null
+<?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 = [])
+ {
+ $values = $definition->getValues();
+
+ $values = $this->resolveNestedDefinitions($definition, $values);
+
+ return $values;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isResolvable(Definition $definition, array $parameters = [])
+ {
+ return true;
+ }
+
+ 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);
+ }
+ }
+}
--- /dev/null
+<?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 = [])
+ {
+ $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 = [])
+ {
+ return true;
+ }
+}
--- /dev/null
+<?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 = []);
+
+ /**
+ * 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 = []);
+}
--- /dev/null
+<?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 = [])
+ {
+ $value = call_user_func($this->variableReader, $definition->getVariableName());
+
+ if (false !== $value) {
+ return $value;
+ }
+
+ if (!$definition->isOptional()) {
+ throw new DefinitionException(sprintf(
+ "The environment variable '%s' has not been defined",
+ $definition->getVariableName()
+ ));
+ }
+
+ $value = $definition->getDefaultValue();
+
+ // Nested definition
+ if ($value instanceof DefinitionHelper) {
+ return $this->definitionResolver->resolve($value->getDefinition(''));
+ }
+
+ return $value;
+ }
+
+ /**
+ * @param EnvironmentVariableDefinition $definition
+ *
+ * {@inheritdoc}
+ */
+ public function isResolvable(Definition $definition, array $parameters = [])
+ {
+ return true;
+ }
+}
--- /dev/null
+<?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;
+use Invoker\Invoker;
+use Invoker\ParameterResolver\NumericArrayResolver;
+
+/**
+ * Resolves a factory definition to a value.
+ *
+ * @since 4.0
+ * @author Matthieu Napoli <matthieu@mnapoli.fr>
+ */
+class FactoryResolver implements DefinitionResolver
+{
+ /**
+ * @var ContainerInterface
+ */
+ private $container;
+
+ /**
+ * @var Invoker|null
+ */
+ private $invoker;
+
+ /**
+ * 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 = [])
+ {
+ $callable = $definition->getCallable();
+
+ if (! is_callable($callable)) {
+ throw new DefinitionException(sprintf(
+ 'The factory definition "%s" is not callable',
+ $definition->getName()
+ ));
+ }
+
+ if (! $this->invoker) {
+ $this->invoker = new Invoker(new NumericArrayResolver, $this->container);
+ }
+
+ return $this->invoker->call($callable, [$this->container]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isResolvable(Definition $definition, array $parameters = [])
+ {
+ return true;
+ }
+}
--- /dev/null
+<?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 = [])
+ {
+ 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 = [])
+ {
+ return true;
+ }
+}
--- /dev/null
+<?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 = [])
+ {
+ // Lazy?
+ if ($definition->isLazy()) {
+ return $this->createProxy($definition, $parameters);
+ }
+
+ return $this->createInstance($definition, $parameters);
+ }
+
+ /**
+ * The definition is not resolvable if the class is not instantiable (interface or abstract)
+ * or if the class doesn't exist.
+ *
+ * @param ObjectDefinition $definition
+ *
+ * {@inheritdoc}
+ */
+ public function isResolvable(Definition $definition, array $parameters = [])
+ {
+ return $definition->isInstantiable();
+ }
+
+ /**
+ * Returns a proxy instance
+ *
+ * @param ObjectDefinition $definition
+ * @param array $parameters
+ *
+ * @return LazyLoadingInterface Proxy instance
+ */
+ private function createProxy(ObjectDefinition $definition, array $parameters)
+ {
+ /** @noinspection PhpUnusedParameterInspection */
+ $proxy = $this->proxyFactory->createProxy(
+ $definition->getClassName(),
+ function (& $wrappedObject, $proxy, $method, $params, & $initializer) use ($definition, $parameters) {
+ $wrappedObject = $this->createInstance($definition, $parameters);
+ $initializer = null; // turning off further lazy initialization
+ return true;
+ }
+ );
+
+ return $proxy;
+ }
+
+ /**
+ * Creates an instance of the class and injects dependencies..
+ *
+ * @param ObjectDefinition $definition
+ * @param array $parameters Optional parameters to use to create the instance.
+ *
+ * @throws DefinitionException
+ * @throws DependencyException
+ * @return object
+ */
+ private function createInstance(ObjectDefinition $definition, array $parameters)
+ {
+ $this->assertClassExists($definition);
+
+ $classname = $definition->getClassName();
+ $classReflection = new ReflectionClass($classname);
+
+ $this->assertClassIsInstantiable($definition);
+
+ $constructorInjection = $definition->getConstructorInjection();
+
+ try {
+ $args = $this->parameterResolver->resolveParameters(
+ $constructorInjection,
+ $classReflection->getConstructor(),
+ $parameters
+ );
+
+ if (count($args) > 0) {
+ $object = $classReflection->newInstanceArgs($args);
+ } else {
+ $object = new $classname;
+ }
+
+ $this->injectMethodsAndProperties($object, $definition);
+ } catch (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()
+ ));
+ }
+
+ if (! $object) {
+ throw new DependencyException(sprintf(
+ "Entry %s cannot be resolved: %s could not be constructed",
+ $definition->getName(),
+ $classReflection->getName()
+ ));
+ }
+
+ return $object;
+ }
+
+ protected function injectMethodsAndProperties($object, ObjectDefinition $objectDefinition)
+ {
+ // Property injections
+ foreach ($objectDefinition->getPropertyInjections() as $propertyInjection) {
+ $this->injectProperty($object, $propertyInjection);
+ }
+
+ // Method injections
+ foreach ($objectDefinition->getMethodInjections() as $methodInjection) {
+ $methodReflection = new \ReflectionMethod($object, $methodInjection->getMethodName());
+ $args = $this->parameterResolver->resolveParameters($methodInjection, $methodReflection);
+
+ $methodReflection->invokeArgs($object, $args);
+ }
+ }
+
+ /**
+ * Inject dependencies into properties.
+ *
+ * @param object $object Object to inject dependencies into
+ * @param PropertyInjection $propertyInjection Property injection definition
+ *
+ * @throws DependencyException
+ * @throws DefinitionException
+ */
+ private function injectProperty($object, PropertyInjection $propertyInjection)
+ {
+ $propertyName = $propertyInjection->getPropertyName();
+
+ $className = $propertyInjection->getClassName();
+ $className = $className ?: get_class($object);
+ $property = new ReflectionProperty($className, $propertyName);
+
+ $value = $propertyInjection->getValue();
+
+ if ($value instanceof DefinitionHelper) {
+ /** @var Definition $nestedDefinition */
+ $nestedDefinition = $value->getDefinition('');
+
+ try {
+ $value = $this->definitionResolver->resolve($nestedDefinition);
+ } catch (DependencyException $e) {
+ throw $e;
+ } catch (Exception $e) {
+ throw new DependencyException(sprintf(
+ "Error while injecting in %s::%s. %s",
+ get_class($object),
+ $propertyName,
+ $e->getMessage()
+ ), 0, $e);
+ }
+ }
+
+ if (! $property->isPublic()) {
+ $property->setAccessible(true);
+ }
+ $property->setValue($object, $value);
+ }
+
+ private function assertClassExists(ObjectDefinition $definition)
+ {
+ if (! $definition->classExists()) {
+ throw DefinitionException::create($definition,
+ sprintf(
+ "Entry %s cannot be resolved: class %s doesn't exist",
+ $definition->getName(),
+ $definition->getClassName()
+ ));
+ }
+ }
+
+ private function assertClassIsInstantiable(ObjectDefinition $definition)
+ {
+ if (! $definition->isInstantiable()) {
+ throw DefinitionException::create($definition,
+ sprintf(
+ "Entry %s cannot be resolved: class %s is not instantiable",
+ $definition->getName(),
+ $definition->getClassName()
+ ));
+ }
+ }
+}
--- /dev/null
+<?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\Exception\DefinitionException;
+use DI\Definition\Helper\DefinitionHelper;
+use DI\Definition\ObjectDefinition\MethodInjection;
+
+/**
+ * Resolves parameters for a function call.
+ *
+ * @since 4.2
+ * @author Matthieu Napoli <matthieu@mnapoli.fr>
+ */
+class ParameterResolver
+{
+ /**
+ * @var DefinitionResolver
+ */
+ private $definitionResolver;
+
+ /**
+ * @param DefinitionResolver $definitionResolver Will be used to resolve nested definitions.
+ */
+ public function __construct(DefinitionResolver $definitionResolver)
+ {
+ $this->definitionResolver = $definitionResolver;
+ }
+
+ /**
+ * @param MethodInjection $definition
+ * @param \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(
+ MethodInjection $definition = null,
+ \ReflectionFunctionAbstract $functionReflection = null,
+ array $parameters = []
+ ) {
+ $args = [];
+
+ if (! $functionReflection) {
+ return $args;
+ }
+
+ $definitionParameters = $definition ? $definition->getParameters() : array();
+
+ foreach ($functionReflection->getParameters() as $index => $parameter) {
+ if (array_key_exists($parameter->getName(), $parameters)) {
+ // Look in the $parameters array
+ $value = &$parameters[$parameter->getName()];
+ } elseif (array_key_exists($index, $definitionParameters)) {
+ // Look in the definition
+ $value = &$definitionParameters[$index];
+ } else {
+ // If the parameter is optional and wasn't specified, we take its default value
+ if ($parameter->isOptional()) {
+ $args[] = $this->getParameterDefaultValue($parameter, $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 ($parameter->isOptional() && !$this->definitionResolver->isResolvable($nestedDefinition)) {
+ $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();
+ }
+}
--- /dev/null
+<?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 = [])
+ {
+ $definitionResolver = $this->getDefinitionResolver($definition);
+
+ return $definitionResolver->resolve($definition, $parameters);
+ }
+
+ /**
+ * Check if a definition can be resolved.
+ *
+ * @param Definition $definition Object that defines how the value should be obtained.
+ * @param array $parameters Optional parameters to use to build the entry.
+ *
+ * @return bool
+ */
+ public function isResolvable(Definition $definition, array $parameters = [])
+ {
+ $definitionResolver = $this->getDefinitionResolver($definition);
+
+ return $definitionResolver->isResolvable($definition, $parameters);
+ }
+
+ /**
+ * Returns a resolver capable of handling the given definition.
+ *
+ * @param Definition $definition
+ *
+ * @throws \RuntimeException No definition resolver was found for this type of definition.
+ * @return DefinitionResolver
+ */
+ private function getDefinitionResolver(Definition $definition)
+ {
+ switch (true) {
+ case ($definition instanceof \DI\Definition\ObjectDefinition):
+ if (! $this->objectResolver) {
+ $this->objectResolver = new ObjectCreator($this, $this->proxyFactory);
+ }
+ return $this->objectResolver;
+ case ($definition instanceof \DI\Definition\ValueDefinition):
+ if (! $this->valueResolver) {
+ $this->valueResolver = new ValueResolver();
+ }
+ return $this->valueResolver;
+ case ($definition instanceof \DI\Definition\AliasDefinition):
+ if (! $this->aliasResolver) {
+ $this->aliasResolver = new AliasResolver($this->container);
+ }
+ return $this->aliasResolver;
+ case ($definition instanceof \DI\Definition\DecoratorDefinition):
+ if (! $this->decoratorResolver) {
+ $this->decoratorResolver = new DecoratorResolver($this->container, $this);
+ }
+ return $this->decoratorResolver;
+ case ($definition instanceof \DI\Definition\FactoryDefinition):
+ if (! $this->factoryResolver) {
+ $this->factoryResolver = new FactoryResolver($this->container);
+ }
+ return $this->factoryResolver;
+ case ($definition instanceof \DI\Definition\ArrayDefinition):
+ if (! $this->arrayResolver) {
+ $this->arrayResolver = new ArrayResolver($this);
+ }
+ return $this->arrayResolver;
+ case ($definition instanceof \DI\Definition\EnvironmentVariableDefinition):
+ if (! $this->envVariableResolver) {
+ $this->envVariableResolver = new EnvironmentVariableResolver($this);
+ }
+ return $this->envVariableResolver;
+ case ($definition instanceof \DI\Definition\StringDefinition):
+ if (! $this->stringResolver) {
+ $this->stringResolver = new StringResolver($this->container);
+ }
+ return $this->stringResolver;
+ case ($definition instanceof \DI\Definition\InstanceDefinition):
+ if (! $this->instanceResolver) {
+ $this->instanceResolver = new InstanceInjector($this, $this->proxyFactory);
+ }
+ return $this->instanceResolver;
+ default:
+ throw new \RuntimeException('No definition resolver was configured for definition of type ' . get_class($definition));
+ }
+ }
+}
--- /dev/null
+<?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 = [])
+ {
+ $expression = $definition->getExpression();
+
+ $result = preg_replace_callback('#\{([^\{\}]+)\}#', function (array $matches) use ($definition) {
+ try {
+ return $this->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 = [])
+ {
+ return true;
+ }
+}
--- /dev/null
+<?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 = [])
+ {
+ return $definition->getValue();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isResolvable(Definition $definition, array $parameters = [])
+ {
+ return true;
+ }
+}
--- /dev/null
+<?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;
+
+ public function __construct($ignorePhpDocErrors = false)
+ {
+ $this->ignorePhpDocErrors = (bool) $ignorePhpDocErrors;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws AnnotationException
+ * @throws InvalidArgumentException The class doesn't exist
+ */
+ public function getDefinition($name)
+ {
+ if (!class_exists($name) && !interface_exists($name)) {
+ return null;
+ }
+
+ $class = new ReflectionClass($name);
+ $definition = new ObjectDefinition($name);
+
+ $this->readInjectableAnnotation($class, $definition);
+
+ // Browse the class properties looking for annotated properties
+ $this->readProperties($class, $definition);
+
+ // Browse the object's methods looking for annotated methods
+ $this->readMethods($class, $definition);
+
+ return $definition;
+ }
+
+ /**
+ * Browse the class properties looking for annotated properties.
+ */
+ private function readProperties(ReflectionClass $class, ObjectDefinition $definition)
+ {
+ foreach ($class->getProperties() as $property) {
+ if ($property->isStatic()) {
+ continue;
+ }
+ $this->readProperty($property, $definition);
+ }
+
+ // Read also the *private* properties of the parent classes
+ /** @noinspection PhpAssignmentInConditionInspection */
+ while ($class = $class->getParentClass()) {
+ foreach ($class->getProperties(ReflectionProperty::IS_PRIVATE) as $property) {
+ if ($property->isStatic()) {
+ continue;
+ }
+ $this->readProperty($property, $definition, $class->getName());
+ }
+ }
+ }
+
+ private function readProperty(ReflectionProperty $property, ObjectDefinition $definition, $classname = null)
+ {
+ // Look for @Inject annotation
+ /** @var $annotation Inject */
+ $annotation = $this->getAnnotationReader()->getPropertyAnnotation($property, 'DI\Annotation\Inject');
+ if ($annotation === null) {
+ return null;
+ }
+
+ // @Inject("name") or look for @var content
+ $entryName = $annotation->getName() ?: $this->getPhpDocReader()->getPropertyClass($property);
+
+ if ($entryName === null) {
+ throw new AnnotationException(sprintf(
+ '@Inject found on property %s::%s but unable to guess what to inject, use a @var annotation',
+ $property->getDeclaringClass()->getName(),
+ $property->getName()
+ ));
+ }
+
+ $definition->addPropertyInjection(
+ new PropertyInjection($property->getName(), new EntryReference($entryName), $classname)
+ );
+ }
+
+ /**
+ * Browse the object's methods looking for annotated methods.
+ */
+ private function readMethods(ReflectionClass $class, ObjectDefinition $objectDefinition)
+ {
+ // This will look in all the methods, including those of the parent classes
+ foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
+ if ($method->isStatic()) {
+ continue;
+ }
+
+ $methodInjection = $this->getMethodInjection($method);
+
+ if (! $methodInjection) {
+ continue;
+ }
+
+ if ($method->isConstructor()) {
+ $objectDefinition->setConstructorInjection($methodInjection);
+ } else {
+ $objectDefinition->addMethodInjection($methodInjection);
+ }
+ }
+ }
+
+ private function getMethodInjection(ReflectionMethod $method)
+ {
+ // Look for @Inject annotation
+ /** @var $annotation Inject|null */
+ try {
+ $annotation = $this->getAnnotationReader()->getMethodAnnotation($method, 'DI\Annotation\Inject');
+ } catch (AnnotationException $e) {
+ throw new AnnotationException(sprintf(
+ '@Inject annotation on %s::%s is malformed. %s',
+ $method->getDeclaringClass()->getName(),
+ $method->getName(),
+ $e->getMessage()
+ ), 0, $e);
+ }
+ $annotationParameters = $annotation ? $annotation->getParameters() : [];
+
+ // @Inject on constructor is implicit
+ if (! ($annotation || $method->isConstructor())) {
+ return null;
+ }
+
+ $parameters = [];
+ foreach ($method->getParameters() as $index => $parameter) {
+ $entryName = $this->getMethodParameter($index, $parameter, $annotationParameters);
+
+ if ($entryName !== null) {
+ $parameters[$index] = new EntryReference($entryName);
+ }
+ }
+
+ if ($method->isConstructor()) {
+ return MethodInjection::constructor($parameters);
+ } else {
+ return new MethodInjection($method->getName(), $parameters);
+ }
+ }
+
+ /**
+ * @param int $parameterIndex
+ * @param ReflectionParameter $parameter
+ * @param array $annotationParameters
+ *
+ * @return string|null Entry name or null if not found.
+ */
+ private function getMethodParameter($parameterIndex, ReflectionParameter $parameter, array $annotationParameters)
+ {
+ // @Inject has definition for this parameter (by index, or by name)
+ if (isset($annotationParameters[$parameterIndex])) {
+ return $annotationParameters[$parameterIndex];
+ }
+ if (isset($annotationParameters[$parameter->getName()])) {
+ return $annotationParameters[$parameter->getName()];
+ }
+
+ // Skip optional parameters if not explicitly defined
+ if ($parameter->isOptional()) {
+ return null;
+ }
+
+ // Try to use the type-hinting
+ $parameterClass = $parameter->getClass();
+ if ($parameterClass) {
+ return $parameterClass->getName();
+ }
+
+ // Last resort, look for @param tag
+ return $this->getPhpDocReader()->getParameterClass($parameter);
+ }
+
+ /**
+ * @return Reader The annotation reader
+ */
+ public function getAnnotationReader()
+ {
+ if ($this->annotationReader === null) {
+ AnnotationRegistry::registerAutoloadNamespace('DI\Annotation', __DIR__ . '/../../../');
+ $this->annotationReader = new SimpleAnnotationReader();
+ $this->annotationReader->addNamespace('DI\Annotation');
+ }
+
+ return $this->annotationReader;
+ }
+
+ /**
+ * @return PhpDocReader
+ */
+ private function getPhpDocReader()
+ {
+ if ($this->phpDocReader === null) {
+ $this->phpDocReader = new PhpDocReader($this->ignorePhpDocErrors);
+ }
+
+ return $this->phpDocReader;
+ }
+
+ private function readInjectableAnnotation(ReflectionClass $class, ObjectDefinition $definition)
+ {
+ try {
+ /** @var $annotation Injectable|null */
+ $annotation = $this->getAnnotationReader()
+ ->getClassAnnotation($class, 'DI\Annotation\Injectable');
+ } catch (UnexpectedValueException $e) {
+ throw new DefinitionException(sprintf(
+ 'Error while reading @Injectable on %s: %s',
+ $class->getName(),
+ $e->getMessage()
+ ), 0, $e);
+ }
+
+ if (! $annotation) {
+ return;
+ }
+
+ if ($annotation->getScope()) {
+ $definition->setScope($annotation->getScope());
+ }
+ if ($annotation->isLazy() !== null) {
+ $definition->setLazy($annotation->isLazy());
+ }
+ }
+}
--- /dev/null
+<?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 = [];
+
+ 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;
+ }
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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 = [];
+
+ /**
+ * @param array $definitions
+ */
+ public function __construct(array $definitions = [])
+ {
+ $this->definitions = $definitions;
+ }
+
+ /**
+ * @param array $definitions DI definitions in a PHP array indexed by the definition name.
+ */
+ public function addDefinitions(array $definitions)
+ {
+ // The newly added data prevails
+ // "for keys that exist in both arrays, the elements from the left-hand array will be used"
+ $this->definitions = $definitions + $this->definitions;
+ }
+
+ /**
+ * {@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;
+ }
+}
--- /dev/null
+<?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([]);
+ }
+
+ /**
+ * {@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;
+ }
+}
--- /dev/null
+<?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);
+}
--- /dev/null
+<?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);
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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
+{
+}
--- /dev/null
+<?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 = []);
+}
--- /dev/null
+<?php
+
+namespace DI\Invoker;
+
+use DI\Definition\Helper\DefinitionHelper;
+use DI\Definition\Resolver\DefinitionResolver;
+use Invoker\ParameterResolver\ParameterResolver;
+use ReflectionFunctionAbstract;
+
+/**
+ * Resolves callable parameters using definitions.
+ *
+ * @since 5.0
+ * @author Matthieu Napoli <matthieu@mnapoli.fr>
+ */
+class DefinitionParameterResolver implements ParameterResolver
+{
+ /**
+ * @var DefinitionResolver
+ */
+ private $definitionResolver;
+
+ public function __construct(DefinitionResolver $definitionResolver)
+ {
+ $this->definitionResolver = $definitionResolver;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParameters(
+ ReflectionFunctionAbstract $reflection,
+ array $providedParameters,
+ array $resolvedParameters
+ ) {
+ // Skip parameters already resolved
+ if (! empty($resolvedParameters)) {
+ $providedParameters = array_diff_key($providedParameters, $resolvedParameters);
+ }
+
+ foreach ($providedParameters as $key => $value) {
+ if (! $value instanceof DefinitionHelper) {
+ continue;
+ }
+
+ $definition = $value->getDefinition('');
+ $value = $this->definitionResolver->resolve($definition);
+
+ if (is_int($key)) {
+ // Indexed by position
+ $resolvedParameters[$key] = $value;
+ } else {
+ // Indexed by parameter name
+ // TODO optimize?
+ $reflectionParameters = $reflection->getParameters();
+ foreach ($reflectionParameters as $reflectionParameter) {
+ if ($key === $reflectionParameter->name) {
+ $resolvedParameters[$reflectionParameter->getPosition()] = $value;
+ }
+ }
+ }
+ }
+
+ return $resolvedParameters;
+ }
+}
--- /dev/null
+<?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
+{
+}
--- /dev/null
+<?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
+{
+}
--- /dev/null
+<?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();
+
+ if ($this->writeProxiesToFile) {
+ $config->setProxiesTargetDir($this->proxyDirectory);
+ spl_autoload_register($config->getProxyAutoloader());
+ } else {
+ $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
+ }
+
+ $this->proxyManager = new LazyLoadingValueHolderFactory($config);
+ }
+}
--- /dev/null
+<?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
+ */
+ 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);
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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 = [$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);
+ }
+}
--- /dev/null
+# .gitattributes
+tests/ export-ignore
+phpunit.xml.dist export-ignore
+.travis.yml export-ignore
+
+# Auto detect text files and perform LF normalization
+* text=auto
--- /dev/null
+.DS_Store
+.idea/*
+vendor/*
+composer.phar
+composer.lock
--- /dev/null
+Copyright (C) 2015 Matthieu Napoli
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+# PhpDocReader
+
+[![Build Status](https://img.shields.io/travis/PHP-DI/PhpDocReader.svg)](https://travis-ci.org/mnapoli/PhpDocReader)
+[![Coverage Status](https://img.shields.io/coveralls/PHP-DI/PhpDocReader.svg)](https://coveralls.io/r/mnapoli/PhpDocReader)
+![](https://img.shields.io/packagist/dt/PHP-DI/phpdoc-reader.svg)
+
+This project is used by:
+
+- [PHP-DI](http://php-di.org/)
+- [phockito-unit-php-di](https://github.com/balihoo/phockito-unit-php-di)
+
+Fork the README to add your project here.
+
+## Features
+
+PhpDocReader parses `@var` and `@param` values in PHP docblocks:
+
+```php
+
+use My\Cache\Backend;
+
+class Cache
+{
+ /**
+ * @var Backend
+ */
+ protected $backend;
+
+ /**
+ * @param Backend $backend
+ */
+ public function __construct($backend)
+ {
+ }
+}
+```
+
+It supports namespaced class names with the same resolution rules as PHP:
+
+- fully qualified name (starting with `\`)
+- imported class name (eg. `use My\Cache\Backend;`)
+- relative class name (from the current namespace, like `SubNamespace\MyClass`)
+- aliased class name (eg. `use My\Cache\Backend as FooBar;`)
+
+Primitive types (`@var string`) are ignored (returns null), only valid class names are returned.
+
+## Usage
+
+```php
+$reader = new PhpDocReader();
+
+// Read a property type (@var phpdoc)
+$property = new ReflectionProperty($className, $propertyName);
+$propertyClass = $reader->getPropertyClass($property);
+
+// Read a parameter type (@param phpdoc)
+$parameter = new ReflectionParameter(array($className, $methodName), $parameterName);
+$parameterClass = $reader->getParameterClass($parameter);
+```
--- /dev/null
+{
+ "name": "php-di/phpdoc-reader",
+ "type": "library",
+ "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)",
+ "keywords": ["phpdoc", "reflection"],
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "PhpDocReader\\": "src/PhpDocReader"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "UnitTest\\PhpDocReader\\": "tests/"
+ }
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.6"
+ }
+}
--- /dev/null
+<?php
+
+namespace PhpDocReader;
+
+/**
+ * @author Matthieu Napoli <matthieu@mnapoli.fr>
+ */
+class AnnotationException extends \Exception
+{
+}
--- /dev/null
+<?php
+
+namespace PhpDocReader;
+
+use PhpDocReader\PhpParser\UseStatementParser;
+use ReflectionParameter;
+use ReflectionProperty;
+
+/**
+ * PhpDoc reader
+ *
+ * @author Matthieu Napoli <matthieu@mnapoli.fr>
+ */
+class PhpDocReader
+{
+ /**
+ * @var UseStatementParser
+ */
+ private $parser;
+
+ private $ignoredTypes = array(
+ 'bool',
+ 'boolean',
+ 'string',
+ 'int',
+ 'integer',
+ 'float',
+ 'double',
+ 'array',
+ 'object',
+ 'callable',
+ 'resource',
+ );
+
+ /**
+ * Enable or disable throwing errors when PhpDoc Errors occur (when parsing annotations)
+ *
+ * @var bool
+ */
+ private $ignorePhpDocErrors;
+
+ /**
+ *
+ * @param bool $ignorePhpDocErrors
+ */
+ public function __construct($ignorePhpDocErrors = false)
+ {
+ $this->parser = new UseStatementParser();
+ $this->ignorePhpDocErrors = $ignorePhpDocErrors;
+ }
+
+ /**
+ * Parse the docblock of the property to get the class of the var annotation.
+ *
+ * @param ReflectionProperty $property
+ *
+ * @throws AnnotationException
+ * @return string|null Type of the property (content of var annotation)
+ *
+ * @deprecated Use getPropertyClass instead.
+ */
+ public function getPropertyType(ReflectionProperty $property)
+ {
+ return $this->getPropertyClass($property);
+ }
+
+ /**
+ * Parse the docblock of the property to get the class of the var annotation.
+ *
+ * @param ReflectionProperty $property
+ *
+ * @throws AnnotationException
+ * @return string|null Type of the property (content of var annotation)
+ */
+ public function getPropertyClass(ReflectionProperty $property)
+ {
+ // Get the content of the @var annotation
+ if (preg_match('/@var\s+([^\s]+)/', $property->getDocComment(), $matches)) {
+ list(, $type) = $matches;
+ } else {
+ return null;
+ }
+
+ // Ignore primitive types
+ if (in_array($type, $this->ignoredTypes)) {
+ return null;
+ }
+
+ // Ignore types containing special characters ([], <> ...)
+ if (! preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
+ return null;
+ }
+
+ $class = $property->getDeclaringClass();
+
+ // If the class name is not fully qualified (i.e. doesn't start with a \)
+ if ($type[0] !== '\\') {
+ $alias = (false === $pos = strpos($type, '\\')) ? $type : substr($type, 0, $pos);
+ $loweredAlias = strtolower($alias);
+
+ // Retrieve "use" statements
+ $uses = $this->parser->parseUseStatements($property->getDeclaringClass());
+
+ $found = false;
+
+ if (isset($uses[$loweredAlias])) {
+ // Imported classes
+ if (false !== $pos) {
+ $type = $uses[$loweredAlias] . substr($type, $pos);
+ } else {
+ $type = $uses[$loweredAlias];
+ }
+ $found = true;
+ } elseif ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
+ $type = $class->getNamespaceName() . '\\' . $type;
+ $found = true;
+ } elseif (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
+ // Class namespace
+ $type = $uses['__NAMESPACE__'] . '\\' . $type;
+ $found = true;
+ } elseif ($this->classExists($type)) {
+ // No namespace
+ $found = true;
+ }
+
+ if (!$found && !$this->ignorePhpDocErrors) {
+ throw new AnnotationException(sprintf(
+ 'The @var annotation on %s::%s contains a non existent class "%s". '
+ . 'Did you maybe forget to add a "use" statement for this annotation?',
+ $class->name,
+ $property->getName(),
+ $type
+ ));
+ }
+ }
+
+ if (!$this->classExists($type) && !$this->ignorePhpDocErrors) {
+ throw new AnnotationException(sprintf(
+ 'The @var annotation on %s::%s contains a non existent class "%s"',
+ $class->name,
+ $property->getName(),
+ $type
+ ));
+ }
+
+ // Remove the leading \ (FQN shouldn't contain it)
+ $type = ltrim($type, '\\');
+
+ return $type;
+ }
+
+ /**
+ * Parse the docblock of the property to get the class of the param annotation.
+ *
+ * @param ReflectionParameter $parameter
+ *
+ * @throws AnnotationException
+ * @return string|null Type of the property (content of var annotation)
+ *
+ * @deprecated Use getParameterClass instead.
+ */
+ public function getParameterType(ReflectionParameter $parameter)
+ {
+ return $this->getParameterClass($parameter);
+ }
+
+ /**
+ * Parse the docblock of the property to get the class of the param annotation.
+ *
+ * @param ReflectionParameter $parameter
+ *
+ * @throws AnnotationException
+ * @return string|null Type of the property (content of var annotation)
+ */
+ public function getParameterClass(ReflectionParameter $parameter)
+ {
+ // Use reflection
+ $parameterClass = $parameter->getClass();
+ if ($parameterClass !== null) {
+ return $parameterClass->name;
+ }
+
+ $parameterName = $parameter->name;
+ // Get the content of the @param annotation
+ $method = $parameter->getDeclaringFunction();
+ if (preg_match('/@param\s+([^\s]+)\s+\$' . $parameterName . '/', $method->getDocComment(), $matches)) {
+ list(, $type) = $matches;
+ } else {
+ return null;
+ }
+
+ // Ignore primitive types
+ if (in_array($type, $this->ignoredTypes)) {
+ return null;
+ }
+
+ // Ignore types containing special characters ([], <> ...)
+ if (! preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
+ return null;
+ }
+
+ $class = $parameter->getDeclaringClass();
+
+ // If the class name is not fully qualified (i.e. doesn't start with a \)
+ if ($type[0] !== '\\') {
+ $alias = (false === $pos = strpos($type, '\\')) ? $type : substr($type, 0, $pos);
+ $loweredAlias = strtolower($alias);
+
+ // Retrieve "use" statements
+ $uses = $this->parser->parseUseStatements($class);
+
+ $found = false;
+
+ if (isset($uses[$loweredAlias])) {
+ // Imported classes
+ if (false !== $pos) {
+ $type = $uses[$loweredAlias] . substr($type, $pos);
+ } else {
+ $type = $uses[$loweredAlias];
+ }
+ $found = true;
+ } elseif ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
+ $type = $class->getNamespaceName() . '\\' . $type;
+ $found = true;
+ } elseif (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
+ // Class namespace
+ $type = $uses['__NAMESPACE__'] . '\\' . $type;
+ $found = true;
+ } elseif ($this->classExists($type)) {
+ // No namespace
+ $found = true;
+ }
+
+ if (!$found && !$this->ignorePhpDocErrors) {
+ throw new AnnotationException(sprintf(
+ 'The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s". '
+ . 'Did you maybe forget to add a "use" statement for this annotation?',
+ $parameterName,
+ $class->name,
+ $method->name,
+ $type
+ ));
+ }
+ }
+
+ if (!$this->classExists($type) && !$this->ignorePhpDocErrors) {
+ throw new AnnotationException(sprintf(
+ 'The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s"',
+ $parameterName,
+ $class->name,
+ $method->name,
+ $type
+ ));
+ }
+
+ // Remove the leading \ (FQN shouldn't contain it)
+ $type = ltrim($type, '\\');
+
+ return $type;
+ }
+
+ /**
+ * @param string $class
+ * @return bool
+ */
+ private function classExists($class)
+ {
+ return class_exists($class) || interface_exists($class);
+ }
+}
--- /dev/null
+<?php
+
+namespace PhpDocReader\PhpParser;
+
+/**
+ * Parses a file for namespaces/use/class declarations.
+ *
+ * Class taken and adapted from doctrine/annotations to avoid pulling the whole package.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Christian Kaps <christian.kaps@mohiva.com>
+ */
+class TokenParser
+{
+ /**
+ * The token list.
+ *
+ * @var array
+ */
+ private $tokens;
+
+ /**
+ * The number of tokens.
+ *
+ * @var int
+ */
+ private $numTokens;
+
+ /**
+ * The current array pointer.
+ *
+ * @var int
+ */
+ private $pointer = 0;
+
+ /**
+ * @param string $contents
+ */
+ public function __construct($contents)
+ {
+ $this->tokens = token_get_all($contents);
+
+ // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it
+ // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored
+ // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a
+ // docblock. If the first thing in the file is a class without a doc block this would cause calls to
+ // getDocBlock() on said class to return our long lost doc_comment. Argh.
+ // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least
+ // it's harmless to us.
+ token_get_all("<?php\n/**\n *\n */");
+
+ $this->numTokens = count($this->tokens);
+ }
+
+ /**
+ * Gets all use statements.
+ *
+ * @param string $namespaceName The namespace name of the reflected class.
+ *
+ * @return array A list with all found use statements.
+ */
+ public function parseUseStatements($namespaceName)
+ {
+ $statements = array();
+ while (($token = $this->next())) {
+ if ($token[0] === T_USE) {
+ $statements = array_merge($statements, $this->parseUseStatement());
+ continue;
+ }
+ if ($token[0] !== T_NAMESPACE || $this->parseNamespace() != $namespaceName) {
+ continue;
+ }
+
+ // Get fresh array for new namespace. This is to prevent the parser to collect the use statements
+ // for a previous namespace with the same name. This is the case if a namespace is defined twice
+ // or if a namespace with the same name is commented out.
+ $statements = array();
+ }
+
+ return $statements;
+ }
+
+ /**
+ * Gets the next non whitespace and non comment token.
+ *
+ * @param boolean $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
+ * If FALSE then only whitespace and normal comments are skipped.
+ *
+ * @return array|null The token if exists, null otherwise.
+ */
+ private function next($docCommentIsComment = true)
+ {
+ for ($i = $this->pointer; $i < $this->numTokens; $i++) {
+ $this->pointer++;
+ if ($this->tokens[$i][0] === T_WHITESPACE ||
+ $this->tokens[$i][0] === T_COMMENT ||
+ ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)) {
+
+ continue;
+ }
+
+ return $this->tokens[$i];
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses a single use statement.
+ *
+ * @return array A list with all found class names for a use statement.
+ */
+ private function parseUseStatement()
+ {
+ $class = '';
+ $alias = '';
+ $statements = array();
+ $explicitAlias = false;
+ while (($token = $this->next())) {
+ $isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR;
+ if (!$explicitAlias && $isNameToken) {
+ $class .= $token[1];
+ $alias = $token[1];
+ } elseif ($explicitAlias && $isNameToken) {
+ $alias .= $token[1];
+ } elseif ($token[0] === T_AS) {
+ $explicitAlias = true;
+ $alias = '';
+ } elseif ($token === ',') {
+ $statements[strtolower($alias)] = $class;
+ $class = '';
+ $alias = '';
+ $explicitAlias = false;
+ } elseif ($token === ';') {
+ $statements[strtolower($alias)] = $class;
+ break;
+ } else {
+ break;
+ }
+ }
+
+ return $statements;
+ }
+
+ /**
+ * Gets the namespace.
+ *
+ * @return string The found namespace.
+ */
+ private function parseNamespace()
+ {
+ $name = '';
+ while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) {
+ $name .= $token[1];
+ }
+
+ return $name;
+ }
+}
--- /dev/null
+<?php
+
+namespace PhpDocReader\PhpParser;
+
+use SplFileObject;
+
+/**
+ * Parses a file for "use" declarations.
+ *
+ * Class taken and adapted from doctrine/annotations to avoid pulling the whole package.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Christian Kaps <christian.kaps@mohiva.com>
+ */
+class UseStatementParser
+{
+ /**
+ * @return array A list with use statements in the form (Alias => FQN).
+ */
+ public function parseUseStatements(\ReflectionClass $class)
+ {
+ if (false === $filename = $class->getFilename()) {
+ return array();
+ }
+
+ $content = $this->getFileContent($filename, $class->getStartLine());
+
+ if (null === $content) {
+ return array();
+ }
+
+ $namespace = preg_quote($class->getNamespaceName());
+ $content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
+ $tokenizer = new TokenParser('<?php ' . $content);
+
+ $statements = $tokenizer->parseUseStatements($class->getNamespaceName());
+
+ return $statements;
+ }
+
+ /**
+ * Gets the content of the file right up to the given line number.
+ *
+ * @param string $filename The name of the file to load.
+ * @param integer $lineNumber The number of lines to read from file.
+ *
+ * @return string The content of the file.
+ */
+ private function getFileContent($filename, $lineNumber)
+ {
+ if ( ! is_file($filename)) {
+ return null;
+ }
+
+ $content = '';
+ $lineCnt = 0;
+ $file = new SplFileObject($filename);
+ while (!$file->eof()) {
+ if ($lineCnt++ == $lineNumber) {
+ break;
+ }
+
+ $content .= $file->fgets();
+ }
+
+ return $content;
+ }
+}