From: Alexander Ebert Date: Tue, 17 Nov 2015 12:58:43 +0000 (+0100) Subject: Added PHP-DI in order to phase out SingletonFactory X-Git-Tag: 3.0.0_Beta_1~2030^2~247^2 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=d11a8c9ecdef07195c15ba5f85f8da98b25491b1;p=GitHub%2FWoltLab%2FWCF.git Added PHP-DI in order to phase out SingletonFactory --- diff --git a/wcfsetup/install/files/lib/system/SingletonFactory.class.php b/wcfsetup/install/files/lib/system/SingletonFactory.class.php index 2c90207a0e..d15fc3f2f6 100644 --- a/wcfsetup/install/files/lib/system/SingletonFactory.class.php +++ b/wcfsetup/install/files/lib/system/SingletonFactory.class.php @@ -13,17 +13,11 @@ use wcf\system\exception\SystemException; * @category Community Framework */ abstract class SingletonFactory { - /** - * list of singletons - * @var array - */ - 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(); } @@ -50,26 +44,6 @@ abstract class SingletonFactory { * @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()); } } diff --git a/wcfsetup/install/files/lib/system/WCF.class.php b/wcfsetup/install/files/lib/system/WCF.class.php index 92be155773..dd4209d835 100644 --- a/wcfsetup/install/files/lib/system/WCF.class.php +++ b/wcfsetup/install/files/lib/system/WCF.class.php @@ -1,5 +1,6 @@ build(); + // start initialization $this->initDB(); $this->loadOptions(); @@ -148,6 +159,15 @@ class WCF { 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. * @@ -308,7 +328,7 @@ class WCF { $factory = new SessionFactory(); $factory->load(); - self::$sessionObj = SessionHandler::getInstance(); + self::$sessionObj = self::$diContainer->get(SessionHandler::class); self::$sessionObj->setHasValidCookie($factory->hasValidCookie()); } @@ -333,7 +353,7 @@ class WCF { * 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(); @@ -348,7 +368,9 @@ class WCF { 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()); } /** diff --git a/wcfsetup/install/files/lib/system/api/autoload.php b/wcfsetup/install/files/lib/system/api/autoload.php new file mode 100644 index 0000000000..4694b275f6 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/autoload.php @@ -0,0 +1,7 @@ +=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": [] +} diff --git a/wcfsetup/install/files/lib/system/api/composer/ClassLoader.php b/wcfsetup/install/files/lib/system/api/composer/ClassLoader.php new file mode 100644 index 0000000000..5e1469e830 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/composer/ClassLoader.php @@ -0,0 +1,413 @@ + + * Jordi Boggiano + * + * 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 + * @author Jordi Boggiano + */ +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; +} diff --git a/wcfsetup/install/files/lib/system/api/composer/LICENSE b/wcfsetup/install/files/lib/system/api/composer/LICENSE new file mode 100644 index 0000000000..c8d57af8b2 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/composer/LICENSE @@ -0,0 +1,21 @@ + +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. + diff --git a/wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php b/wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php new file mode 100644 index 0000000000..1bd6482f9e --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + 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'), +); diff --git a/wcfsetup/install/files/lib/system/api/composer/autoload_real.php b/wcfsetup/install/files/lib/system/api/composer/autoload_real.php new file mode 100644 index 0000000000..cf860b92b3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/composer/autoload_real.php @@ -0,0 +1,55 @@ + $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; +} diff --git a/wcfsetup/install/files/lib/system/api/composer/installed.json b/wcfsetup/install/files/lib/system/api/composer/installed.json new file mode 100644 index 0000000000..6b6c2056aa --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/composer/installed.json @@ -0,0 +1,174 @@ +[ + { + "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" + ] + } +] diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/.gitignore b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/.gitignore new file mode 100644 index 0000000000..b2395aa055 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/.gitignore @@ -0,0 +1,3 @@ +composer.lock +composer.phar +/vendor/ diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/LICENSE b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/LICENSE new file mode 100644 index 0000000000..7671d9020f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/LICENSE @@ -0,0 +1,20 @@ +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. diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/README.md b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/README.md new file mode 100644 index 0000000000..ec434d0f26 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/README.md @@ -0,0 +1,85 @@ +# 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. diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/composer.json b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/composer.json new file mode 100644 index 0000000000..84f3875282 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/composer.json @@ -0,0 +1,11 @@ +{ + "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/" + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface-meta.md b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface-meta.md new file mode 100644 index 0000000000..90711c9051 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface-meta.md @@ -0,0 +1,114 @@ +# 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) diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface.md b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface.md new file mode 100644 index 0000000000..9f609674c8 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface.md @@ -0,0 +1,153 @@ +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 +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) + diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/Delegate-lookup.md b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/Delegate-lookup.md new file mode 100644 index 0000000000..04eb3aea02 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/Delegate-lookup.md @@ -0,0 +1,60 @@ +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. diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/interoperating_containers.png b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/interoperating_containers.png new file mode 100644 index 0000000000..9c672e16c7 Binary files /dev/null and b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/interoperating_containers.png differ diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/priority.png b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/priority.png new file mode 100644 index 0000000000..5760dc71b5 Binary files /dev/null and b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/priority.png differ diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/side_by_side_containers.png b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/side_by_side_containers.png new file mode 100644 index 0000000000..24ca03c7cf Binary files /dev/null and b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/side_by_side_containers.png differ diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/src/Interop/Container/ContainerInterface.php b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/src/Interop/Container/ContainerInterface.php new file mode 100644 index 0000000000..dee5ffa7a3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/src/Interop/Container/ContainerInterface.php @@ -0,0 +1,37 @@ +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. diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/composer.json b/wcfsetup/install/files/lib/system/api/php-di/invoker/composer.json new file mode 100644 index 0000000000..3f0c04169b --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/composer.json @@ -0,0 +1,25 @@ +{ + "name": "php-di/invoker", + "description": "Generic and extensible callable invoker", + "keywords": ["invoker", "dependency-injection", "dependency", "injection", "callable", "invoke"], + "homepage": "https://github.com/PHP-DI/Invoker", + "license": "MIT", + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Invoker\\Test\\": "tests/" + } + }, + "require": { + "container-interop/container-interop": "~1.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "athletic/athletic": "~0.1.8" + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/doc/parameter-resolvers.md b/wcfsetup/install/files/lib/system/api/php-di/invoker/doc/parameter-resolvers.md new file mode 100644 index 0000000000..bfccb7f10e --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/doc/parameter-resolvers.md @@ -0,0 +1,109 @@ +# Parameter resolvers + +Extending the behavior of the `Invoker` is easy and is done by implementing a [`ParameterResolver`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/ParameterResolver.php): + +```php +interface ParameterResolver +{ + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ); +} +``` + +- `$providedParameters` contains the parameters provided by the user when calling `$invoker->call($callable, $parameters)` +- `$resolvedParameters` contains parameters that have already been resolved by other parameter resolvers + +An `Invoker` can chain multiple parameter resolvers to mix behaviors, e.g. you can mix "named parameters" support with "dependency injection" support. This is why a `ParameterResolver` should skip parameters that are already resolved in `$resolvedParameters`. + +Here is an implementation example for dumb dependency injection that creates a new instance of the classes type-hinted: + +```php +class MyParameterResolver implements ParameterResolver +{ + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ) { + foreach ($reflection->getParameters() as $index => $parameter) { + if (array_key_exists($index, $resolvedParameters)) { + // Skip already resolved parameters + continue; + } + + $class = $parameter->getClass(); + + if ($class) { + $resolvedParameters[$index] = $class->newInstance(); + } + } + + return $resolvedParameters; + } +} +``` + +To use it: + +```php +$invoker = new Invoker\Invoker(new MyParameterResolver); + +$invoker->call(function (ArticleManager $articleManager) { + $articleManager->publishArticle('Hello world', 'This is the article content.'); +}); +``` + +A new instance of `ArticleManager` will be created by our parameter resolver. + +## Chaining parameter resolvers + +The fun starts to happen when we want to add support for many things: + +- named parameters +- dependency injection for type-hinted parameters +- ... + +This is where we should use the [`ResolverChain`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/ResolverChain.php). This resolver implements the [Chain of responsibility](http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern) design pattern. + +For example the default chain is: + +```php +$parameterResolver = new ResolverChain([ + new NumericArrayResolver, + new AssociativeArrayResolver, + new DefaultValueResolver, +]); +``` + +It allows to support even the weirdest use cases like: + +```php +$parameters = []; + +// First parameter will receive "Welcome" +$parameters[] = 'Welcome'; + +// Parameter named "content" will receive "Hello world!" +$parameters['content'] = 'Hello world!'; + +// $published is not defined so it will use its default value +$invoker->call(function ($title, $content, $published = true) { + // ... +}, $parameters); +``` + +We can put our custom parameter resolver in the list and created a super-duper invoker that also supports basic dependency injection: + +```php +$parameterResolver = new ResolverChain([ + new MyParameterResolver, // Our resolver is at the top for highest priority + new NumericArrayResolver, + new AssociativeArrayResolver, + new DefaultValueResolver, +]); + +$invoker = new Invoker\Invoker($parameterResolver); +``` diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/CallableResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/CallableResolver.php new file mode 100644 index 0000000000..eab8c0efce --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/CallableResolver.php @@ -0,0 +1,131 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/InvocationException.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/InvocationException.php new file mode 100644 index 0000000000..a5f6ffcd12 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/InvocationException.php @@ -0,0 +1,12 @@ + + */ +class InvocationException extends \Exception +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotCallableException.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotCallableException.php new file mode 100644 index 0000000000..1f3e35246a --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotCallableException.php @@ -0,0 +1,12 @@ + + */ +class NotCallableException extends InvocationException +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotEnoughParametersException.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotEnoughParametersException.php new file mode 100644 index 0000000000..1be4eb98ca --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotEnoughParametersException.php @@ -0,0 +1,12 @@ + + */ +class NotEnoughParametersException extends InvocationException +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Invoker.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Invoker.php new file mode 100644 index 0000000000..ffdcd0bf20 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Invoker.php @@ -0,0 +1,122 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/InvokerInterface.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/InvokerInterface.php new file mode 100644 index 0000000000..cd6980e2d7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/InvokerInterface.php @@ -0,0 +1,29 @@ + + */ +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()); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/AssociativeArrayResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/AssociativeArrayResolver.php new file mode 100644 index 0000000000..d51f2c7d05 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/AssociativeArrayResolver.php @@ -0,0 +1,39 @@ +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 + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/ParameterNameContainerResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/ParameterNameContainerResolver.php new file mode 100644 index 0000000000..983dea414f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/ParameterNameContainerResolver.php @@ -0,0 +1,51 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/TypeHintContainerResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/TypeHintContainerResolver.php new file mode 100644 index 0000000000..65a4079497 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/TypeHintContainerResolver.php @@ -0,0 +1,51 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/DefaultValueResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/DefaultValueResolver.php new file mode 100644 index 0000000000..6fa8f8c366 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/DefaultValueResolver.php @@ -0,0 +1,40 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/NumericArrayResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/NumericArrayResolver.php new file mode 100644 index 0000000000..dc5f02c46d --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/NumericArrayResolver.php @@ -0,0 +1,39 @@ +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 + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ParameterResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ParameterResolver.php new file mode 100644 index 0000000000..39c7abcdfd --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ParameterResolver.php @@ -0,0 +1,33 @@ + + */ +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 + ); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ResolverChain.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ResolverChain.php new file mode 100644 index 0000000000..d5247462bf --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ResolverChain.php @@ -0,0 +1,69 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Reflection/CallableReflection.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Reflection/CallableReflection.php new file mode 100644 index 0000000000..7835f44855 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Reflection/CallableReflection.php @@ -0,0 +1,57 @@ + + */ +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) + )); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/.coveralls.yml b/wcfsetup/install/files/lib/system/api/php-di/php-di/.coveralls.yml new file mode 100644 index 0000000000..bc71b62f87 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/.coveralls.yml @@ -0,0 +1,2 @@ +coverage_clover: clover.xml +json_path: coveralls-upload.json diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitattributes b/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitattributes new file mode 100644 index 0000000000..362238c15d --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitattributes @@ -0,0 +1,8 @@ +# .gitattributes +tests/ export-ignore +website/ export-ignore +doc/ export-ignore +news/ export-ignore + +# Auto detect text files and perform LF normalization +* text=auto diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitignore b/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitignore new file mode 100644 index 0000000000..b39e0cdff5 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitignore @@ -0,0 +1,9 @@ +/.idea/ +/vendor/ +/composer.phar +/composer.lock +/theme/ +/.couscous/ +/website/bower_components/ +/website/css/all.min.css +/logo/ diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/.travis.yml b/wcfsetup/install/files/lib/system/api/php-di/php-di/.travis.yml new file mode 100644 index 0000000000..1fed6a382d --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/.travis.yml @@ -0,0 +1,26 @@ +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 diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/404.md b/wcfsetup/install/files/lib/system/api/php-di/php-di/404.md new file mode 100644 index 0000000000..c4e870fdc7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/404.md @@ -0,0 +1,3 @@ +--- +layout: 404 +--- diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/CONTRIBUTING.md b/wcfsetup/install/files/lib/system/api/php-di/php-di/CONTRIBUTING.md new file mode 100644 index 0000000000..6e3bd2407b --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing + +[![Build Status](https://travis-ci.org/PHP-DI/PHP-DI.png?branch=master)](https://travis-ci.org/PHP-DI/PHP-DI) [![Coverage Status](https://coveralls.io/repos/PHP-DI/PHP-DI/badge.png?branch=master)](https://coveralls.io/r/PHP-DI/PHP-DI?branch=master) + +PHP-DI is licensed under the MIT License. + + +## Set up + +* Check out the sources using git or download them + +```bash +$ git clone https://github.com/PHP-DI/PHP-DI.git +``` + +* Install the libraries using composer: + +```bash +$ curl -s http://getcomposer.org/installer | php +$ php composer.phar install +``` + +If you are running Windows or are having trouble, read [the official documentation](http://getcomposer.org/doc/00-intro.md#installation). + + +## Run the tests + +The tests are run with [PHPUnit](http://www.phpunit.de/manual/current/en/installation.html): + +```bash +$ phpunit +``` + + +## Learning the internals + +Read the [How it works](doc/how-it-works.md) documentation. + + +## What to do? + +- Add tests: pick up uncovered situations in the [code coverage report](https://coveralls.io/r/PHP-DI/PHP-DI) +- Resolve issues: [issue list](https://github.com/PHP-DI/PHP-DI/issues) +- Improve the documentation +- … + + +## Coding style + +The code follows PSR0, PSR1 and [PSR2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). + +Also, do not hesitate to add your name to the author list of a class in the docblock if you improve it. diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/LICENSE b/wcfsetup/install/files/lib/system/api/php-di/php-di/LICENSE new file mode 100644 index 0000000000..f24e183de7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/LICENSE @@ -0,0 +1,18 @@ +PHP-DI - PHP Dependency Injection + +Copyright (C) Matthieu Napoli + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/README.md b/wcfsetup/install/files/lib/system/api/php-di/php-di/README.md new file mode 100644 index 0000000000..7e40802540 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/README.md @@ -0,0 +1,21 @@ +--- +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) diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/change-log.md b/wcfsetup/install/files/lib/system/api/php-di/php-di/change-log.md new file mode 100644 index 0000000000..3a7ba86873 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/change-log.md @@ -0,0 +1,303 @@ +# 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 diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/composer.json b/wcfsetup/install/files/lib/system/api/php-di/php-di/composer.json new file mode 100644 index 0000000000..3111f93603 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/composer.json @@ -0,0 +1,43 @@ +{ + "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)" + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/couscous.yml b/wcfsetup/install/files/lib/system/api/php-di/php-di/couscous.yml new file mode 100644 index 0000000000..c18e3cf688 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/couscous.yml @@ -0,0 +1,110 @@ +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 diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/phpunit.xml.dist b/wcfsetup/install/files/lib/system/api/php-di/php-di/phpunit.xml.dist new file mode 100644 index 0000000000..81c7f73867 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + ./tests/UnitTest/ + + + ./tests/IntegrationTest/ + + + + + + src + + + + diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Inject.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Inject.php new file mode 100644 index 0000000000..d0776a62d5 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Inject.php @@ -0,0 +1,95 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Injectable.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Injectable.php new file mode 100644 index 0000000000..2176b6782e --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Injectable.php @@ -0,0 +1,74 @@ + + * @author Matthieu Napoli + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Cache/ArrayCache.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Cache/ArrayCache.php new file mode 100644 index 0000000000..dcd6c0ffcd --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Cache/ArrayCache.php @@ -0,0 +1,79 @@ + + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +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(); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Container.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Container.php new file mode 100644 index 0000000000..642e56b84f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Container.php @@ -0,0 +1,341 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/ContainerBuilder.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/ContainerBuilder.php new file mode 100644 index 0000000000..53871e2bd0 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/ContainerBuilder.php @@ -0,0 +1,258 @@ +build(); + * + * @since 3.2 + * @author Matthieu Napoli + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Debug.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Debug.php new file mode 100644 index 0000000000..a8b3ae2101 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Debug.php @@ -0,0 +1,39 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/AliasDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/AliasDefinition.php new file mode 100644 index 0000000000..ea26506b12 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/AliasDefinition.php @@ -0,0 +1,66 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinition.php new file mode 100644 index 0000000000..f225f89a5d --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinition.php @@ -0,0 +1,66 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinitionExtension.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinitionExtension.php new file mode 100644 index 0000000000..e2f2b76f75 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinitionExtension.php @@ -0,0 +1,61 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/CacheableDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/CacheableDefinition.php new file mode 100644 index 0000000000..eca8e3a86b --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/CacheableDefinition.php @@ -0,0 +1,19 @@ + + */ +interface CacheableDefinition extends Definition +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/DecoratorDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/DecoratorDefinition.php new file mode 100644 index 0000000000..68b4b60d65 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/DecoratorDefinition.php @@ -0,0 +1,48 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Definition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Definition.php new file mode 100644 index 0000000000..849c382c78 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Definition.php @@ -0,0 +1,32 @@ + + */ +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(); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/AliasDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/AliasDefinitionDumper.php new file mode 100644 index 0000000000..c50c6a7f0d --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/AliasDefinitionDumper.php @@ -0,0 +1,48 @@ + + */ +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() + ); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ArrayDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ArrayDefinitionDumper.php new file mode 100644 index 0000000000..c31b7b1f38 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ArrayDefinitionDumper.php @@ -0,0 +1,65 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DecoratorDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DecoratorDefinitionDumper.php new file mode 100644 index 0000000000..9aaa55fcea --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DecoratorDefinitionDumper.php @@ -0,0 +1,37 @@ + + */ +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() . ')'; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumper.php new file mode 100644 index 0000000000..7083c97a5c --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumper.php @@ -0,0 +1,30 @@ + + */ +interface DefinitionDumper +{ + /** + * Returns the given definition as string representation. + * + * @param Definition $definition + * + * @return string + */ + public function dump(Definition $definition); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumperDispatcher.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumperDispatcher.php new file mode 100644 index 0000000000..89add31d07 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumperDispatcher.php @@ -0,0 +1,68 @@ + + */ +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(), + ]; + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/EnvironmentVariableDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/EnvironmentVariableDefinitionDumper.php new file mode 100644 index 0000000000..9886731761 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/EnvironmentVariableDefinitionDumper.php @@ -0,0 +1,63 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/FactoryDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/FactoryDefinitionDumper.php new file mode 100644 index 0000000000..8477f5dab8 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/FactoryDefinitionDumper.php @@ -0,0 +1,37 @@ + + */ +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'; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ObjectDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ObjectDefinitionDumper.php new file mode 100644 index 0000000000..66d87d778a --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ObjectDefinitionDumper.php @@ -0,0 +1,158 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/StringDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/StringDefinitionDumper.php new file mode 100644 index 0000000000..b7267d7cf4 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/StringDefinitionDumper.php @@ -0,0 +1,37 @@ + + */ +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(); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ValueDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ValueDefinitionDumper.php new file mode 100644 index 0000000000..c6decdc277 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ValueDefinitionDumper.php @@ -0,0 +1,44 @@ + + */ +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()) + ); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EntryReference.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EntryReference.php new file mode 100644 index 0000000000..3a136045d3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EntryReference.php @@ -0,0 +1,52 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EnvironmentVariableDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EnvironmentVariableDefinition.php new file mode 100644 index 0000000000..1cc0997765 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EnvironmentVariableDefinition.php @@ -0,0 +1,116 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/AnnotationException.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/AnnotationException.php new file mode 100644 index 0000000000..3657c20c9f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/AnnotationException.php @@ -0,0 +1,19 @@ + + */ +class AnnotationException extends DefinitionException +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/DefinitionException.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/DefinitionException.php new file mode 100644 index 0000000000..cfa806aa20 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/DefinitionException.php @@ -0,0 +1,30 @@ + + */ +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) + )); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/FactoryDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/FactoryDefinition.php new file mode 100644 index 0000000000..6c007a08d5 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/FactoryDefinition.php @@ -0,0 +1,75 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/HasSubDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/HasSubDefinition.php new file mode 100644 index 0000000000..76391200a8 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/HasSubDefinition.php @@ -0,0 +1,28 @@ + + */ +interface HasSubDefinition extends Definition +{ + /** + * @return string + */ + public function getSubDefinitionName(); + + /** + * @param Definition $definition + */ + public function setSubDefinition(Definition $definition); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ArrayDefinitionExtensionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ArrayDefinitionExtensionHelper.php new file mode 100644 index 0000000000..6961b4c1df --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ArrayDefinitionExtensionHelper.php @@ -0,0 +1,46 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/DefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/DefinitionHelper.php new file mode 100644 index 0000000000..2493b2fc73 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/DefinitionHelper.php @@ -0,0 +1,24 @@ + + */ +interface DefinitionHelper +{ + /** + * @param string $entryName Container entry name + * @return \DI\Definition\Definition + */ + public function getDefinition($entryName); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/EnvironmentVariableDefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/EnvironmentVariableDefinitionHelper.php new file mode 100644 index 0000000000..8e525bd0ab --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/EnvironmentVariableDefinitionHelper.php @@ -0,0 +1,64 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/FactoryDefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/FactoryDefinitionHelper.php new file mode 100644 index 0000000000..0278023cf1 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/FactoryDefinitionHelper.php @@ -0,0 +1,72 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ObjectDefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ObjectDefinitionHelper.php new file mode 100644 index 0000000000..a05725b440 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ObjectDefinitionHelper.php @@ -0,0 +1,279 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/StringDefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/StringDefinitionHelper.php new file mode 100644 index 0000000000..b6949527c8 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/StringDefinitionHelper.php @@ -0,0 +1,39 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ValueDefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ValueDefinitionHelper.php new file mode 100644 index 0000000000..436ebe31d9 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ValueDefinitionHelper.php @@ -0,0 +1,42 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/InstanceDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/InstanceDefinition.php new file mode 100644 index 0000000000..7be8e6571f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/InstanceDefinition.php @@ -0,0 +1,76 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition.php new file mode 100644 index 0000000000..03cbf99be9 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition.php @@ -0,0 +1,337 @@ + + */ +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(); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/MethodInjection.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/MethodInjection.php new file mode 100644 index 0000000000..5861641fdb --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/MethodInjection.php @@ -0,0 +1,94 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/PropertyInjection.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/PropertyInjection.php new file mode 100644 index 0000000000..07ecf9e322 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/PropertyInjection.php @@ -0,0 +1,74 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/AliasResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/AliasResolver.php new file mode 100644 index 0000000000..b92772d381 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/AliasResolver.php @@ -0,0 +1,63 @@ + + */ +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()); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ArrayResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ArrayResolver.php new file mode 100644 index 0000000000..dcc76822bc --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ArrayResolver.php @@ -0,0 +1,91 @@ + + */ +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); + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DecoratorResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DecoratorResolver.php new file mode 100644 index 0000000000..db19a60cb6 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DecoratorResolver.php @@ -0,0 +1,93 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DefinitionResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DefinitionResolver.php new file mode 100644 index 0000000000..a4358b15ba --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DefinitionResolver.php @@ -0,0 +1,44 @@ + + */ +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 = []); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/EnvironmentVariableResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/EnvironmentVariableResolver.php new file mode 100644 index 0000000000..0a9849ca3f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/EnvironmentVariableResolver.php @@ -0,0 +1,81 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/FactoryResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/FactoryResolver.php new file mode 100644 index 0000000000..28c797b70f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/FactoryResolver.php @@ -0,0 +1,82 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/InstanceInjector.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/InstanceInjector.php new file mode 100644 index 0000000000..eb825bae8b --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/InstanceInjector.php @@ -0,0 +1,53 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ObjectCreator.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ObjectCreator.php new file mode 100644 index 0000000000..cc2169a818 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ObjectCreator.php @@ -0,0 +1,258 @@ + + */ +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() + )); + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ParameterResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ParameterResolver.php new file mode 100644 index 0000000000..63b0ba975a --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ParameterResolver.php @@ -0,0 +1,140 @@ + + */ +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(); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ResolverDispatcher.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ResolverDispatcher.php new file mode 100644 index 0000000000..d93d57c74a --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ResolverDispatcher.php @@ -0,0 +1,138 @@ + + */ +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)); + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/StringResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/StringResolver.php new file mode 100644 index 0000000000..96854d40cf --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/StringResolver.php @@ -0,0 +1,81 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ValueResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ValueResolver.php new file mode 100644 index 0000000000..04d98fcf49 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ValueResolver.php @@ -0,0 +1,44 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/AnnotationReader.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/AnnotationReader.php new file mode 100644 index 0000000000..e12ab86e05 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/AnnotationReader.php @@ -0,0 +1,280 @@ + + */ +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()); + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/Autowiring.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/Autowiring.php new file mode 100644 index 0000000000..730799bc8b --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/Autowiring.php @@ -0,0 +1,68 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/CachedDefinitionSource.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/CachedDefinitionSource.php new file mode 100644 index 0000000000..19bc7c0e62 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/CachedDefinitionSource.php @@ -0,0 +1,104 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionArray.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionArray.php new file mode 100644 index 0000000000..21b6eeb9ce --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionArray.php @@ -0,0 +1,142 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionFile.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionFile.php new file mode 100644 index 0000000000..a79ccf8a24 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionFile.php @@ -0,0 +1,74 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionSource.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionSource.php new file mode 100644 index 0000000000..7aa5836c41 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionSource.php @@ -0,0 +1,31 @@ + + */ +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); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/MutableDefinitionSource.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/MutableDefinitionSource.php new file mode 100644 index 0000000000..287cc32304 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/MutableDefinitionSource.php @@ -0,0 +1,15 @@ + + */ +interface MutableDefinitionSource extends DefinitionSource +{ + public function addDefinition(Definition $definition); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/SourceChain.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/SourceChain.php new file mode 100644 index 0000000000..962d6f4e65 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/SourceChain.php @@ -0,0 +1,108 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/StringDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/StringDefinition.php new file mode 100644 index 0000000000..27d2811fbd --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/StringDefinition.php @@ -0,0 +1,66 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ValueDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ValueDefinition.php new file mode 100644 index 0000000000..b0123f916e --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ValueDefinition.php @@ -0,0 +1,67 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/DependencyException.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/DependencyException.php new file mode 100644 index 0000000000..f410f38a09 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/DependencyException.php @@ -0,0 +1,19 @@ + + */ +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 = []); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Invoker/DefinitionParameterResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Invoker/DefinitionParameterResolver.php new file mode 100644 index 0000000000..f9718f2164 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Invoker/DefinitionParameterResolver.php @@ -0,0 +1,66 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/InvokerInterface.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/InvokerInterface.php new file mode 100644 index 0000000000..65d0746b82 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/InvokerInterface.php @@ -0,0 +1,19 @@ + + */ +interface InvokerInterface extends \Invoker\InvokerInterface +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/NotFoundException.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/NotFoundException.php new file mode 100644 index 0000000000..0119578cd6 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/NotFoundException.php @@ -0,0 +1,19 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Reflection/CallableReflectionFactory.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Reflection/CallableReflectionFactory.php new file mode 100644 index 0000000000..f6062d3121 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Reflection/CallableReflectionFactory.php @@ -0,0 +1,51 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Scope.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Scope.php new file mode 100644 index 0000000000..cddd7d5c54 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Scope.php @@ -0,0 +1,54 @@ + + */ +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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/functions.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/functions.php new file mode 100644 index 0000000000..313c71f441 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/functions.php @@ -0,0 +1,181 @@ + 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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitattributes b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitattributes new file mode 100644 index 0000000000..912292cf40 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitattributes @@ -0,0 +1,7 @@ +# .gitattributes +tests/ export-ignore +phpunit.xml.dist export-ignore +.travis.yml export-ignore + +# Auto detect text files and perform LF normalization +* text=auto diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitignore b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitignore new file mode 100644 index 0000000000..f7be360d40 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +.idea/* +vendor/* +composer.phar +composer.lock diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/LICENSE b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/LICENSE new file mode 100644 index 0000000000..f859a317a2 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/LICENSE @@ -0,0 +1,16 @@ +Copyright (C) 2015 Matthieu Napoli + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/README.md b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/README.md new file mode 100644 index 0000000000..9a59545479 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/README.md @@ -0,0 +1,59 @@ +# 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); +``` diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/composer.json b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/composer.json new file mode 100644 index 0000000000..11da6a52e9 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/composer.json @@ -0,0 +1,23 @@ +{ + "name": "php-di/phpdoc-reader", + "type": "library", + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": ["phpdoc", "reflection"], + "license": "MIT", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "autoload-dev": { + "psr-4": { + "UnitTest\\PhpDocReader\\": "tests/" + } + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.6" + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/AnnotationException.php b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/AnnotationException.php new file mode 100644 index 0000000000..577d73f28c --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/AnnotationException.php @@ -0,0 +1,10 @@ + + */ +class AnnotationException extends \Exception +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpDocReader.php b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpDocReader.php new file mode 100644 index 0000000000..6251371b6f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpDocReader.php @@ -0,0 +1,270 @@ + + */ +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); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/TokenParser.php b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/TokenParser.php new file mode 100644 index 0000000000..d3965d753a --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/TokenParser.php @@ -0,0 +1,159 @@ + + * @author Christian Kaps + */ +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("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; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/UseStatementParser.php b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/UseStatementParser.php new file mode 100644 index 0000000000..1b0d63d20c --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/UseStatementParser.php @@ -0,0 +1,68 @@ + + * @author Christian Kaps + */ +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('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; + } +}