diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..71175c7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011 ResearchGate GmbH + +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/README.rst b/README.rst new file mode 100644 index 0000000..26c16e3 --- /dev/null +++ b/README.rst @@ -0,0 +1,6 @@ +============ +rg\injection +============ + +rg\injection is a sophisticated dependency injection container for PHP that was inspired by Guice. + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..05c0602 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,25 @@ + + + + + test + + + diff --git a/src/bootstrap.php b/src/bootstrap.php new file mode 100644 index 0000000..55d9dad --- /dev/null +++ b/src/bootstrap.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require_once(__DIR__ . '/../vendor/Symfony/Component/ClassLoader/UniversalClassLoader.php'); + +$loader = new \Symfony\Component\ClassLoader\UniversalClassLoader(); +$loader->registerNamespaces(array( + 'Symfony' => __DIR__ . '/../vendor', + 'Zend' => __DIR__ . '/../vendor', + 'rg' => __DIR__, +)); +$loader->register(); \ No newline at end of file diff --git a/src/rg/injection/Configuration.php b/src/rg/injection/Configuration.php new file mode 100644 index 0000000..a60a36f --- /dev/null +++ b/src/rg/injection/Configuration.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injection; + +class Configuration { + + /** + * @var array + */ + private $config = array(); + + /** + * @param string $configurationFilePath + */ + public function __construct($configurationFilePath) { + $this->loadConfigFile($configurationFilePath); + } + + /** + * @param string $fullClassName + * @return array + */ + public function getClassConfig($fullClassName) { + if (! isset($this->config[$fullClassName])) { + return array(); + } + + return $this->config[$fullClassName]; + } + + /** + * @param string $fullClassName + * @param array $config + */ + public function setClassConfig($fullClassName, array $config) { + $this->config[$fullClassName] = $config; + } + + /** + * @param string $configurationFilePath + */ + private function loadConfigFile($configurationFilePath) { + if ($configurationFilePath && file_exists($configurationFilePath)) { + $this->config = require $configurationFilePath; + } + } +} \ No newline at end of file diff --git a/src/rg/injection/DependencyInjectionContainer.php b/src/rg/injection/DependencyInjectionContainer.php new file mode 100644 index 0000000..476d0d4 --- /dev/null +++ b/src/rg/injection/DependencyInjectionContainer.php @@ -0,0 +1,448 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injection; + +class DependencyInjectionContainer { + + /** + * @var \rg\injection\Configuration + */ + private $config; + + /** + * @var array + */ + private $instances = array(); + + /** + * @var \rg\injection\DependencyInjectionContainer + */ + private static $instance; + + /** + * @param \rg\injection\Configuration $config + */ + public function __construct(Configuration $config) { + $this->config = $config; + + $this->instances[__CLASS__] = $this; + $this->config->setClassConfig(__CLASS__, array( + 'singleton' => true + )); + + self::$instance = $this; + } + + /** + * @static + * @return \rg\injection\DependencyInjectionContainer + */ + public static function getInstance() { + if (self::$instance) { + return self::$instance; + } + + throw new InjectionException('dependency injection container was not instanciated yet'); + } + + /** + * @param string $fullClassName + * @param array $constructorArguments + * @return object + */ + public function getInstanceOfClass($fullClassName, array $constructorArguments = array()) { + $fullClassName = trim($fullClassName, '\\'); + + $classConfig = $this->config->getClassConfig($fullClassName); + + $classReflection = new \ReflectionClass($fullClassName); + + $fullClassName = $this->getRealConfiguredClassName($classConfig, $classReflection); + + $classReflection = $this->getClassReflection($fullClassName); + + if ($this->isConfiguredAsSingleton($classConfig, $classReflection) && + isset($this->instances[$fullClassName]) + ) { + return $this->instances[$fullClassName]; + } + + + if ($this->isConfiguredAsSingleton($classConfig, $classReflection) && + $this->isSingleton($classReflection) + ) { + $constructorArguments = $this->getConstructorArguments($classReflection, $classConfig, $constructorArguments, 'getInstance'); + $instance = $classReflection->getMethod('getInstance')->invokeArgs(null, $constructorArguments); + } else { + $constructorArguments = $this->getConstructorArguments($classReflection, $classConfig, $constructorArguments); + $instance = $classReflection->newInstanceArgs($constructorArguments); + } + + if ($this->isConfiguredAsSingleton($classConfig, $classReflection)) { + $this->instances[$fullClassName] = $instance; + } + + $instance = $this->injectProperties($classConfig, $classReflection, $instance); + + return $instance; + } + + /** + * @param \ReflectionClass $classReflection + * @return bool + */ + public function isSingleton(\ReflectionClass $classReflection) { + return $classReflection->hasMethod('__construct') && + ! $classReflection->getMethod('__construct')->isPublic() && + $classReflection->hasMethod('getInstance') && + $classReflection->getMethod('getInstance')->isStatic() && + $classReflection->getMethod('getInstance')->isPublic(); + } + + /** + * @param \ReflectionClass $classReflection + * @param array $classConfig + * @param array $defaultConstructorArguments + * @param string $constructorMethod + * @return array + */ + public function getConstructorArguments($classReflection, $classConfig, array $defaultConstructorArguments = array(), $constructorMethod = '__construct') { + $methodReflection = $this->getMethodReflection($classReflection, $constructorMethod); + + if (!$methodReflection) { + return array(); + } + + $defaultConstructorArguments = $this->getDefaultArguments($classConfig, $defaultConstructorArguments); + + return $this->getMethodArguments($classConfig,$methodReflection, $defaultConstructorArguments); + } + + /** + * @param $classConfig + * @param $defaultConstructorArguments + * @return array + */ + private function getDefaultArguments($classConfig, $defaultConstructorArguments) { + if (isset($classConfig['params']) && is_array($classConfig['params'])) { + return array_merge($defaultConstructorArguments, $classConfig['params']); + } + return $defaultConstructorArguments; + } + + /** + * @param array $classConfig + * @param \ReflectionClass $classReflection + * @param object $instance + * @return object + * @throws InjectionException + */ + private function injectProperties($classConfig, $classReflection, $instance) { + $properties = $this->getInjectableProperties($classReflection); + foreach ($properties as $property) { + $this->injectProperty($classConfig, $property, $instance); + } + return $instance; + } + + /** + * @param \ReflectionClass $classReflection + * @return array + */ + public function getInjectableProperties($classReflection) { + $properties = $classReflection->getProperties(); + + $injectableProperties = array(); + + foreach ($properties as $property) { + if ($this->isInjectable($property->getDocComment())) { + if ($property->isPrivate()) { + throw new InjectionException('Property ' . $property->name . ' must not be private for property injection.'); + } + $injectableProperties[] = $property; + } + } + + return $injectableProperties; + } + + /** + * @param array $classConfig + * @param \ReflectionProperty $property + * @param object $instance + * @throws InjectionException + */ + private function injectProperty(array $classConfig, $property, $instance) { + $fullClassName = $this->getClassFromVarTypeHint($classConfig, $property->getDocComment()); + $propertyInstance = $this->getInstanceOfClass($fullClassName); + $property->setAccessible(true); + $property->setValue($instance, $propertyInstance); + $property->setAccessible(false); + } + + /** + * @param array $classConfig + * @param string $docComment + * @return string + * @throws InjectionException + */ + public function getClassFromVarTypeHint(array $classConfig, $docComment) { + $namedClass = $this->getNamedClassOfArgument($classConfig, $docComment); + if ($namedClass) { + return $namedClass; + } + return $this->getClassFromTypeHint($docComment, '@var'); + } + + /** + * @param string $docComment + * @param string $tag + * @return string mixed + * @throws InjectionException + */ + private function getClassFromTypeHint($docComment, $tag) { + $matches = array(); + preg_match('/' . $tag . '\s([a-zA-Z0-9\\\]+)/', $docComment, $matches); + if (isset($matches[1])) { + return $matches[1]; + } + throw new InjectionException('Expected tag ' . $tag . ' not found in doc comment.'); + } + + /** + * @param string $fullClassName + * @return \ReflectionClass + * @throws InjectionException + */ + public function getClassReflection($fullClassName) { + $classReflection = new \ReflectionClass($fullClassName); + + if ($classReflection->isAbstract()) { + throw new InjectionException('Can not instanciate abstract class ' . $fullClassName); + } + + if ($classReflection->isInterface()) { + throw new InjectionException('Can not instanciate interface ' . $fullClassName); + } + return $classReflection; + } + + /** + * @param array $classConfig + * @param \ReflectionClass $classReflection + * @return string + */ + private function getRealConfiguredClassName($classConfig, \ReflectionClass $classReflection) { + if (isset($classConfig['class'])) { + return $classConfig['class']; + } + + $annotatedClassName = $this->getAnnotatedImplementationClass($classReflection); + if ($annotatedClassName) { + return $annotatedClassName; + } + + return $classReflection->name; + } + + /** + * @param \ReflectionClass $classReflection + * @return string + */ + private function getAnnotatedImplementationClass(\ReflectionClass $classReflection) { + $docComment = $classReflection->getDocComment(); + + $matches = array(); + + preg_match('/@implementedBy\s+([a-zA-Z0-9\\\]+)/', $docComment, $matches); + + if (isset($matches[1])) { + return $matches[1]; + } + + return null; + } + + /** + * @param array $classConfig + * @param \ReflectionClass $classReflection + * @return bool + */ + public function isConfiguredAsSingleton(array $classConfig, \ReflectionClass $classReflection) { + if (isset($classConfig['singleton'])) { + return (bool) $classConfig['singleton']; + } + + $classComment = $classReflection->getDocComment(); + + return strpos($classComment, '@singleton') !== false; + } + + /** + * @param object $object + * @param string $methodName + * @return mixed + * @throws InjectionException + */ + public function callMethodOnObject($object, $methodName) { + $fullClassName = get_class($object); + if (substr($methodName, 0, 2) === '__') { + throw new InjectionException('You are not allowed to call magic method ' . $methodName . ' on ' . $fullClassName); + } + $classReflection = $this->getClassReflection($fullClassName); + + $methodReflection = $this->getMethodReflection($classReflection, $methodName); + + $this->checkAllowedHttpMethodAnnotation($methodReflection); + + $arguments = $this->getMethodArguments(array(), $methodReflection); + + return $methodReflection->invokeArgs($object, $arguments); + } + + /** + * @param \ReflectionMethod $methodReflection + * @return + */ + public function checkAllowedHttpMethodAnnotation(\ReflectionMethod $methodReflection) { + if (!isset($_SERVER['REQUEST_METHOD'])) { + return; + } + + $allowedHttpMethod = $this->getAllowedHttpMethod($methodReflection); + + if ($allowedHttpMethod && strtolower($allowedHttpMethod) !== strtolower($_SERVER['REQUEST_METHOD'])) { + throw new \RuntimeException('invalid http method ' . $_SERVER['REQUEST_METHOD'] . ' for ' . $methodReflection->class . '::' . $methodReflection->name . '(), ' . $allowedHttpMethod . ' expected'); + } + } + + /** + * @param \ReflectionMethod $methodReflection + * @return string + */ + public function getAllowedHttpMethod(\ReflectionMethod $methodReflection) { + $docComment = $methodReflection->getDocComment(); + $matches = array(); + preg_match('/@method\s+([a-z]+)/i', $docComment, $matches); + if (isset($matches[1])) { + return $matches[1]; + } + + return null; + } + + /** + * @param \ReflectionClass $classReflection + * @param string $methodName + * @return null|\ReflectionMethod + * @throws InjectionException + */ + private function getMethodReflection(\ReflectionClass $classReflection, $methodName) { + if (!$classReflection->hasMethod($methodName)) { + if ($methodName === '__construct') { + return null; + } + + throw new InjectionException('Method ' . $methodName . ' not found in ' . $classReflection->name); + } + + return $classReflection->getMethod($methodName); + } + + /** + * @param array $classConfig + * @param \ReflectionMethod $methodReflection + * @param array $defaultArguments + * @return array + */ + public function getMethodArguments(array $classConfig, \ReflectionMethod $methodReflection, array $defaultArguments = array()) { + $arguments = $methodReflection->getParameters(); + + $methodIsMarkedInjectible = $this->isInjectable($methodReflection->getDocComment()); + + $argumentValues = array(); + + foreach ($arguments as $argument) { + if (isset($defaultArguments[$argument->name])) { + $argumentValues[$argument->name] = $this->getValueOfDefaultArgument($defaultArguments[$argument->name]); + } else if ($methodIsMarkedInjectible) { + $argumentValues[$argument->name] = $this->getInstanceOfArgument($classConfig, $argument); + } else if (!$argument->isOptional()) { + throw new InjectionException('Parameter ' . $argument->name . ' in class ' . $methodReflection->class . ' is not injectable'); + } + } + + return $argumentValues; + } + + /** + * @param array $argumentConfig + * @return mixed + */ + private function getValueOfDefaultArgument(array $argumentConfig) { + if (isset($argumentConfig['value'])) { + return $argumentConfig['value']; + } + if (isset($argumentConfig['class'])) { + return $this->getInstanceOfClass($argumentConfig['class']); + } + return null; + } + + /** + * @param array $classConfig + * @param \ReflectionParameter $argument + * @return object + * @throws InjectionException + */ + private function getInstanceOfArgument($classConfig, \ReflectionParameter $argument) { + $namedClassName = $this->getNamedClassOfArgument($classConfig, $argument->getDeclaringFunction()->getDocComment(), $argument->name); + if ($namedClassName) { + return $this->getInstanceOfClass($namedClassName); + } + + if (!$argument->getClass()) { + throw new InjectionException('Invalid argument without class typehint ' . $argument->name); + } + + return $this->getInstanceOfClass($argument->getClass()->name); + } + + /** + * @param array $classConfig + * @param string $docComment + * @param string $argumentName + * @return string + */ + private function getNamedClassOfArgument(array $classConfig, $docComment, $argumentName = null) { + $matches = array(); + $pattern = '@named\s+([a-zA-Z0-9\\\]+)'; + if ($argumentName) { + $pattern .= '\s+\$' . preg_quote($argumentName, '/'); + } + preg_match('/' . $pattern . '/', $docComment, $matches); + if (isset($matches[1])) { + if (! isset($classConfig['named']) || ! isset($classConfig['named'][$matches[1]])) { + throw new InjectionException('Configuration for name ' . $matches[1] . ' not found.'); + } + return $classConfig['named'][$matches[1]]; + } + return null; + } + + /** + * @param $docComment + * @return bool + */ + public function isInjectable($docComment) { + return strpos($docComment, '@inject') !== false; + } + +} diff --git a/src/rg/injection/FactoryDependencyInjectionContainer.php b/src/rg/injection/FactoryDependencyInjectionContainer.php new file mode 100644 index 0000000..30f4162 --- /dev/null +++ b/src/rg/injection/FactoryDependencyInjectionContainer.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injection; + +class FactoryDependencyInjectionContainer extends DependencyInjectionContainer { + + /** + * @param string $fullClassName + * @param array $constructorArguments + * @return object + */ + public function getInstanceOfClass($fullClassName, array $constructorArguments = array()) { + $factoryClass = $this->getFullFactoryClassName($fullClassName); + + if (class_exists($factoryClass, true)) { + return $factoryClass::getInstance($constructorArguments); + } + + return parent::getInstanceOfClass($fullClassName, $constructorArguments); + } + + /** + * @param object $object + * @param string $methodName + * @return mixed + * @throws InjectionException + */ + public function callMethodOnObject($object, $methodName) { + $factoryClass = $this->getFactoryClassName(get_class($object)); + + if (class_exists($factoryClass, true)) { + $factoryMethod = $this->getFactoryMethodName($methodName); + return $factoryClass::$factoryMethod($object); + } + + return parent::callMethodOnObject($object, $methodName); + } + + /** + * @param string $methodName + * @return string + */ + public function getFactoryMethodName($methodName) { + return 'call' . ucfirst($methodName); + } + + /** + * @param string $fullClassName + * @return string + */ + public function getFactoryClassName($fullClassName) { + return $this->getStrippedClassName($fullClassName) . 'Factory'; + } + + /** + * @param string $fullClassName + * @return string + */ + public function getProxyClassName($fullClassName) { + return $this->getStrippedClassName($fullClassName) . 'Proxy'; + } + + /** + * @param string $fullClassName + * @return string + */ + private function getStrippedClassName($fullClassName) { + $strippedName = ''; + $classNameParts = explode('\\', $fullClassName); + foreach ($classNameParts as $classNamePart) { + $strippedName .= ucfirst($classNamePart); + } + return $strippedName; + } + + /** + * @param $fullClassName + * @return string + */ + public function getFullFactoryClassName($fullClassName) { + $factoryClass = 'rg\injection\generated\\' . $this->getFactoryClassName($fullClassName); + return $factoryClass; + } + +} diff --git a/src/rg/injection/FactoryGenerator.php b/src/rg/injection/FactoryGenerator.php new file mode 100644 index 0000000..6ad6ab2 --- /dev/null +++ b/src/rg/injection/FactoryGenerator.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injection; + +use Zend\Code\Generator; + +abstract class FactoryGenerator { + + /** + * @var array + */ + private $generated = array(); + + /** + * @var \rg\injection\Configuration + */ + private $config; + + /** + * @param Configuration $config + */ + public function __construct(Configuration $config) { + $this->config = $config; + } + + /** + * @abstract + * @param string $fullClassName + */ + abstract public function processFileForClass($fullClassName); + + /** + * @param string $fullClassName + * @param bool $addFileDocBlock + * @return \Zend\Code\Generator\FileGenerator + */ + protected function generateFileForClass($fullClassName, $addFileDocBlock = true) { + $fullClassName = trim($fullClassName, '\\'); + + if (in_array($fullClassName, $this->generated)) { + return; + } + + $this->generated[] = $fullClassName; + + $dic = new FactoryDependencyInjectionContainer($this->config); + + $classConfig = $this->config->getClassConfig($fullClassName); + $factoryName = $dic->getFactoryClassName($fullClassName); + + $factoryClass = new Generator\ClassGenerator($factoryName); + $instanceMethod = new Generator\MethodGenerator('getInstance'); + $parameter = new \Zend\Code\Generator\ParameterGenerator('parameters', 'array', array()); + $instanceMethod->setParameter($parameter); + $classReflection = $dic->getClassReflection($fullClassName); + if ($dic->isSingleton($classReflection)) { + $arguments = $dic->getConstructorArguments($classReflection, $classConfig, array(), 'getInstance'); + } else { + $arguments = $dic->getConstructorArguments($classReflection, $classConfig); + } + + $isSingleton = $dic->isConfiguredAsSingleton($classConfig, $classReflection); + + $body = ''; + + if ($isSingleton) { + $property = new Generator\PropertyGenerator('instance', null, Generator\PropertyGenerator::FLAG_PRIVATE); + $property->setStatic(true); + $factoryClass->setProperty($property); + + $body .= 'if (self::$instance) {' . PHP_EOL; + $body .= ' return self::$instance;' . PHP_EOL; + $body .= '}' . PHP_EOL . PHP_EOL; + } + $constructorArgumentStringParts = array(); + $realConstructorArgumentStringParts = array(); + $constructorArguments = array(); + + foreach ($arguments as $argumentName => $argumentClass) { + if (is_object($argumentClass)) { + $argumentClass = get_class($argumentClass); + $argumentFactory = $dic->getFullFactoryClassName($argumentClass); + $body .= '$' . $argumentName . ' = isset($parameters[\'' . $argumentName . '\']) ? $parameters[\'' . $argumentName . '\'] : ' . $argumentFactory . '::getInstance();' . PHP_EOL; + + $this->processFileForClass($argumentClass); + } else { + $body .= '$' . $argumentName . ' = isset($parameters[\'' . $argumentName . '\']) ? $parameters[\'' . $argumentName . '\'] : \'' . $argumentClass . '\';' . PHP_EOL; + } + $constructorArguments[] = $argumentName; + $constructorArgumentStringParts[] = '$' . $argumentName; + $realConstructorArgumentStringParts[] = '$' . $argumentName; + + } + + $injectableProperties = $dic->getInjectableProperties($classReflection); + $injectableArguments = array(); + foreach ($injectableProperties as $injectableProperty) { + + $propertyClass = $dic->getClassFromVarTypeHint($classConfig, $injectableProperty->getDocComment()); + + $propertyName = $injectableProperty->name; + $propertyFactory = $dic->getFullFactoryClassName($propertyClass); + $body .= '$' . $propertyName . ' = ' . $propertyFactory . '::getInstance();' . PHP_EOL; + + $injectableArguments[] = $propertyName; + $constructorArguments[] = $propertyName; + $constructorArgumentStringParts[] = '$' . $propertyName; + + $this->processFileForClass($propertyClass); + } + + $proxyClass = null; + + if (count($injectableProperties) > 0) { + $proxyName = $dic->getProxyClassName($fullClassName); + if ($dic->isSingleton($classReflection)) { + $proxyClass = $this->getStaticProxyClass($proxyName, $fullClassName, $constructorArguments, $injectableArguments, $realConstructorArgumentStringParts); + $body .= PHP_EOL . '$instance = ' . $proxyName . '::getInstance(' . implode(', ', $constructorArgumentStringParts) . ');' . PHP_EOL; + } else { + $proxyClass = $this->getProxyClass($proxyName, $fullClassName, $constructorArguments, $injectableArguments, $realConstructorArgumentStringParts); + $body .= PHP_EOL . '$instance = new ' . $proxyName . '(' . implode(', ', $constructorArgumentStringParts) . ');' . PHP_EOL; + } + } else { + if ($dic->isSingleton($classReflection)) { + $body .= PHP_EOL . '$instance = \\' . $fullClassName . '::getInstance(' . implode(', ', $constructorArgumentStringParts) . ');' . PHP_EOL; + } else { + $body .= PHP_EOL . '$instance = new \\' . $fullClassName . '(' . implode(', ', $constructorArgumentStringParts) . ');' . PHP_EOL; + } + + } + + if ($isSingleton) { + $body .= 'self::$instance = $instance;' . PHP_EOL; + } + + $body .= 'return $instance;' . PHP_EOL; + + $instanceMethod->setBody($body); + $instanceMethod->setStatic(true); + $factoryClass->setMethod($instanceMethod); + + $methods = $classReflection->getMethods(); + foreach ($methods as $method) { + if ($method->isPublic() && + $method->name !== '__construct' && + !$method->isStatic() + ) { + + $factoryMethod = new Generator\MethodGenerator($dic->getFactoryMethodName($method->name)); + $factoryMethod->setParameter(new Generator\ParameterGenerator('object')); + $factoryMethod->setStatic(true); + + try { + $arguments = $dic->getMethodArguments($classConfig, $method); + } catch (InjectionException $e) { + continue; + } + + $factoryMethodBody = ''; + + $allowedHttpMethod = $dic->getAllowedHttpMethod($method); + + if ($allowedHttpMethod) { + $factoryMethodBody .= 'if (isset($_SERVER["request_method"]) && strtolower($_SERVER["request_method"]) !== "' + . strtolower($allowedHttpMethod) .'") {' . PHP_EOL; + $factoryMethodBody .= ' throw new \RuntimeException("invalid http method " . $_SERVER["REQUEST_METHOD"] . " for ' + . $method->class . '::' . $method->name . '(), ' . $allowedHttpMethod . ' expected");' . PHP_EOL; + $factoryMethodBody .= '}' . PHP_EOL . PHP_EOL; + } + $constructorArgumentStringParts = array(); + + foreach ($arguments as $argumentName => $argument) { + if (is_object($argument)) { + $argument = get_class($argument); + $argumentFactory = $dic->getFullFactoryClassName($argument); + $factoryMethodBody .= '$' . $argumentName . ' = ' . $argumentFactory . '::getInstance();' . PHP_EOL; + + $this->processFileForClass($argument); + } else { + $factoryMethodBody .= '$' . $argumentName . ' = \'' . $argument . '\';' . PHP_EOL; + } + $constructorArgumentStringParts[] = '$' . $argumentName; + } + + $factoryMethodBody .= PHP_EOL . 'return $object->' . $method->name . '(' . implode(', ', $constructorArgumentStringParts) . ');'; + $factoryMethod->setBody($factoryMethodBody); + $factoryClass->setMethod($factoryMethod); + } + } + + $file = new Generator\FileGenerator(); + $file->setNamespace('rg\injection\generated'); + $file->setClass($factoryClass); + if ($proxyClass) { + $file->setClass($proxyClass); + } + if ($addFileDocBlock) { + $docblock = new Generator\DocblockGenerator('Generated by ' . get_class($this) . ' on ' . date('Y-m-d H:i:s')); + $file->setDocblock($docblock); + } + $file->setFilename(__DIR__ . '/generated/' . $factoryName . '.php'); + + return $file; + } + + /** + * @param string $proxyName + * @param string $fullClassName + * @param array $constructorArguments + * @param array $injectableArguments + * @param array $realConstructorArgumentStringParts + * @return \Zend\Code\Generator\ClassGenerator + */ + private function getProxyClass($proxyName, $fullClassName, $constructorArguments, $injectableArguments, $realConstructorArgumentStringParts) { + $proxyClass = new Generator\ClassGenerator($proxyName); + $proxyClass->setExtendedClass('\\' . $fullClassName); + $constructor = new Generator\MethodGenerator('__construct'); + foreach ($constructorArguments as $constructorArgument) { + $parameter = new Generator\ParameterGenerator($constructorArgument); + $constructor->setParameter($parameter); + } + $constructorBody = ''; + foreach ($injectableArguments as $injectableArgument) { + $constructorBody .= '$this->' . $injectableArgument . ' = $' . $injectableArgument . ';' . PHP_EOL; + } + $constructorBody .= 'parent::__construct(' . implode(', ', $realConstructorArgumentStringParts) . ');' . PHP_EOL; + $constructor->setBody($constructorBody); + $proxyClass->setMethod($constructor); + return $proxyClass; + } + + private function getStaticProxyClass($proxyName, $fullClassName, $constructorArguments, $injectableArguments, $realConstructorArgumentStringParts) { + $proxyClass = new Generator\ClassGenerator($proxyName); + $proxyClass->setExtendedClass('\\' . $fullClassName); + $constructor = new Generator\MethodGenerator('getInstance'); + $constructor->setStatic(true); + foreach ($constructorArguments as $constructorArgument) { + $parameter = new Generator\ParameterGenerator($constructorArgument); + $constructor->setParameter($parameter); + } + $constructorBody = '$instance = parent::getInstance(' . implode(', ', $realConstructorArgumentStringParts) . ');' . PHP_EOL;; + foreach ($injectableArguments as $injectableArgument) { + $constructorBody .= '$this->' . $injectableArgument . ' = $' . $injectableArgument . ';' . PHP_EOL; + } + $constructorBody .= 'return $instance;' . PHP_EOL; + $constructor->setBody($constructorBody); + $proxyClass->setMethod($constructor); + return $proxyClass; + } + +} \ No newline at end of file diff --git a/src/rg/injection/InjectionException.php b/src/rg/injection/InjectionException.php new file mode 100644 index 0000000..5270a0e --- /dev/null +++ b/src/rg/injection/InjectionException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injection; + +class InjectionException extends \Exception { + +} diff --git a/src/rg/injection/WritingFactoryGenerator.php b/src/rg/injection/WritingFactoryGenerator.php new file mode 100644 index 0000000..c48ed8a --- /dev/null +++ b/src/rg/injection/WritingFactoryGenerator.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injection; + +class WritingFactoryGenerator extends FactoryGenerator { + + /** + * @param string $fullClassName + */ + public function processFileForClass($fullClassName) { + $file = $this->generateFileForClass($fullClassName); + if ($file) { + $file->write(); + } + } + +} \ No newline at end of file diff --git a/test/rg/injection/ConfigurationTest.php b/test/rg/injection/ConfigurationTest.php new file mode 100644 index 0000000..e3829d5 --- /dev/null +++ b/test/rg/injection/ConfigurationTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injection; + +class ConfigurationTest extends \PHPUnit_Framework_TestCase { + + public function testLoadConfiguration() { + $config = new Configuration(__DIR__ . '/test_config.php'); + + $this->assertEquals('bar', $config->getClassConfig('foo')); + } + + public function testGetInitialConfig() { + $config = new Configuration(null); + + $this->assertEquals(array(), $config->getClassConfig('foo')); + } + + public function testSetConfig() { + $config = new Configuration(null); + + $config->setClassConfig('foo', array('bar')); + $this->assertEquals(array('bar'), $config->getClassConfig('foo')); + } +} \ No newline at end of file diff --git a/test/rg/injection/DependencyInjectionContainerTest.php b/test/rg/injection/DependencyInjectionContainerTest.php new file mode 100644 index 0000000..070a6e3 --- /dev/null +++ b/test/rg/injection/DependencyInjectionContainerTest.php @@ -0,0 +1,602 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injection; + +class DependencyInjectionContainerTest extends \PHPUnit_Framework_TestCase { + + public function testGetInstance() { + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + + $this->assertInstanceOf('rg\injection\DICTestClassOne', $instance); + + $this->assertInstanceOf('rg\injection\DICTestClassTwo', $instance->two); + $this->assertInstanceOf('rg\injection\DICTestClassThree', $instance->three); + $this->assertInstanceOf('rg\injection\DICTestClassThree', $instance->two->three); + $this->assertInstanceOf('rg\injection\DICTestClassThree', $instance->getFour()); + } + + public function testGetInstanceWithInvalidParameterInjectionThrowsException() { + $this->setExpectedException('rg\injection\InjectionException', 'Expected tag @var not found in doc comment.'); + + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $dic->getInstanceOfClass('rg\injection\DICTestClassNoParamTypeHint'); + } + + public function testGetInstanceWithPrivateParameterInjectionThrowsException() { + $this->setExpectedException('rg\injection\InjectionException', 'Property two must not be private for property injection.'); + + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $dic->getInstanceOfClass('rg\injection\DICTestClassPrivateProperty'); + } + + public function testGetInstanceWithParameterInjectionAndDoubledAnnotationTakesFirstOne() { + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassPropertyDoubledAnnotation'); + + $this->assertInstanceOf('rg\injection\DICTestClassPropertyDoubledAnnotation', $instance); + $this->assertInstanceOf('rg\injection\DICTestClassNoConstructor', $instance->two); + } + + public function testGetConfiguredInstance() { + $config = new Configuration(null); + $config->setClassConfig('rg\injection\DICTestAbstractClass', array( + 'class' => 'rg\injection\DICTestClassOneConfigured', + )); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestAbstractClass'); + + $this->assertInstanceOf('rg\injection\DICTestClassOneConfigured', $instance); + } + + public function testAbstractInstanceThrowsException() { + $this->setExpectedException('rg\injection\InjectionException', 'Can not instanciate abstract class rg\injection\DICTestAbstractClass'); + + $config = new Configuration(null); + $dic = new DependencyInjectionContainer($config); + + $dic->getInstanceOfClass('rg\injection\DICTestAbstractClass'); + } + + public function testGetDifferentInstancesWithTwoCalls() { + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $instanceOne = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + $instanceTwo = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + + $this->assertInstanceOf('rg\injection\DICTestClassOne', $instanceOne); + $this->assertInstanceOf('rg\injection\DICTestClassOne', $instanceTwo); + + $this->assertFalse($instanceOne === $instanceTwo); + } + + public function testDontGetSingletonInstance() { + $config = new Configuration(null); + $config->setClassConfig('rg\injection\DICTestClassOne', array( + 'singleton' => false + )); + + $dic = new DependencyInjectionContainer($config); + + $instanceOne = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + $instanceTwo = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + + $this->assertInstanceOf('rg\injection\DICTestClassOne', $instanceOne); + $this->assertInstanceOf('rg\injection\DICTestClassOne', $instanceTwo); + + $this->assertTrue($instanceOne !== $instanceTwo); + } + + public function testGetSingletonInstance() { + $config = new Configuration(null); + $config->setClassConfig('rg\injection\DICTestClassOne', array( + 'singleton' => true + )); + + $dic = new DependencyInjectionContainer($config); + + $instanceOne = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + $instanceTwo = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + + $this->assertInstanceOf('rg\injection\DICTestClassOne', $instanceOne); + $this->assertInstanceOf('rg\injection\DICTestClassOne', $instanceTwo); + + $this->assertTrue($instanceOne === $instanceTwo); + } + + public function testGetInstanceOfRealSingleton() { + $config = new Configuration(null); + $config->setClassConfig('rg\injection\DICTestSingleton', array( + 'singleton' => true + )); + $dic = new DependencyInjectionContainer($config); + $instance = $dic->getInstanceOfClass('rg\injection\DICTestSingleton'); + + $this->assertInstanceOf('rg\injection\DICTestSingleton', $instance); + $this->assertInstanceOf('rg\injection\DICTestClassNoConstructor', $instance->instance); + $this->assertInstanceOf('rg\injection\DICTestClassNoConstructor', $instance->injectedProperty); + $this->assertEquals('foo', $instance->foo); + } + + public function testGetInstanceWithoutConstructor() { + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassNoConstructor'); + + $this->assertInstanceOf('rg\injection\DICTestClassNoConstructor', $instance); + } + + public function testGetInstanceOfNotInjectableClassThrowsException() { + $this->setExpectedException('rg\injection\InjectionException', 'not injectable'); + + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $dic->getInstanceOfClass('rg\injection\DICTestClassNoInject'); + } + + public function testGetInstanceOfClassWithoNoTypeHintThrowsException() { + $this->setExpectedException('rg\injection\InjectionException', 'Invalid argument without class typehint one'); + + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $dic->getInstanceOfClass('rg\injection\DICTestClassNoTypeHint'); + } + + public function testGetInstanceWithConfiguredParameter() { + $config = new Configuration(null); + $config->setClassConfig('rg\injection\DICTestClassNoTypeHint', array( + 'params' => array( + 'one' => array( + 'value' => 'foo', + ), + 'two' => array( + 'value' => 123 + ) + ), + )); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassNoTypeHint'); + + $this->assertInstanceOf('rg\injection\DICTestClassNoTypeHint', $instance); + $this->assertEquals('foo', $instance->one); + $this->assertEquals(123, $instance->two); + } + + public function testGetInstanceWithConfiguredClassParameter() { + $config = new Configuration(null); + $config->setClassConfig('rg\injection\DICTestClassNoTypeHint', array( + 'params' => array( + 'one' => array( + 'value' => 'foo', + ), + 'two' => array( + 'class' => 'rg\injection\DICTestClassOne' + ) + ), + )); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassNoTypeHint'); + + $this->assertInstanceOf('rg\injection\DICTestClassNoTypeHint', $instance); + $this->assertEquals('foo', $instance->one); + $this->assertInstanceOf('rg\injection\DICTestClassOne', $instance->two); + } + + public function testGetInstanceWithConfiguredAndOptionalClassParameter() { + $config = new Configuration(null); + $config->setClassConfig('rg\injection\DICTestClassNoTypeHintOptionalArgument', array( + 'params' => array( + 'one' => array( + 'value' => 'foo', + ), + ), + )); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassNoTypeHintOptionalArgument', array( + )); + + $this->assertInstanceOf('rg\injection\DICTestClassNoTypeHintOptionalArgument', $instance); + $this->assertEquals('foo', $instance->one); + $this->assertEquals('bar', $instance->two); + } + + public function testGetInstanceWithConfiguredAndDefaultClassParameter() { + $config = new Configuration(null); + $config->setClassConfig('rg\injection\DICTestClassNoTypeHint', array( + 'params' => array( + 'one' => array( + 'value' => 'foo', + ), + ), + )); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassNoTypeHint', array( + 'two' => array( + 'class' => 'rg\injection\DICTestClassOne' + ) + )); + + $this->assertInstanceOf('rg\injection\DICTestClassNoTypeHint', $instance); + $this->assertEquals('foo', $instance->one); + $this->assertInstanceOf('rg\injection\DICTestClassOne', $instance->two); + } + + public function testGetInstanceWithWrongConfiguredParameterThrowsException() { + $this->setExpectedException('rg\injection\InjectionException', 'Invalid argument without class typehint two'); + $config = new Configuration(null); + $config->setClassConfig('rg\injection\DICTestClassNoTypeHint', array( + 'params' => array( + 'one' => array( + 'value' => 'foo', + ), + ), + )); + + $dic = new DependencyInjectionContainer($config); + + $dic->getInstanceOfClass('rg\injection\DICTestClassNoTypeHint'); + } + + public function testCallMethodOnObject() { + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + + $actual = $dic->callMethodOnObject($instance, 'getSomething'); + + $this->assertEquals('barfoo', $actual); + } + + public function testCallMethodWithoutParametersOnObject() { + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassTwo'); + + $actual = $dic->callMethodOnObject($instance, 'getSomething'); + + $this->assertEquals('bar', $actual); + } + + public function testCallNotInjectableMethodThrowsException() { + $this->setExpectedException('rg\injection\InjectionException', 'not injectable'); + + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + + $dic->callMethodOnObject($instance, 'getSomethingNotInjectible'); + } + + public function testCallMethodWithoutTypehintOnObjectThrowsException() { + $this->setExpectedException('rg\injection\InjectionException', 'not injectable'); + + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + + $dic->callMethodOnObject($instance, 'noTypeHint'); + } + + public function testCallUndefinedMethodThrowsException() { + $this->setExpectedException('rg\injection\InjectionException', 'Method undefined not found in rg\injection\DICTestClassOne'); + + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + + $dic->callMethodOnObject($instance, 'undefined'); + } + + public function testCallMagicMethodThrowsException() { + $this->setExpectedException('rg\injection\InjectionException', 'not allowed to call magic method'); + + $config = new Configuration(null); + + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestClassOne'); + + $dic->callMethodOnObject($instance, '__get'); + } + + public function testAnnotatedSingleton() { + $config = new Configuration(null); + $dic = new DependencyInjectionContainer($config); + + $instanceOne = $dic->getInstanceOfClass('rg\injection\DICTestAnnotatedSingleton'); + $instanceTwo = $dic->getInstanceOfClass('rg\injection\DICTestAnnotatedSingleton'); + + $this->assertInstanceOf('rg\injection\DICTestAnnotatedSingleton', $instanceOne); + $this->assertInstanceOf('rg\injection\DICTestAnnotatedSingleton', $instanceTwo); + $this->assertTrue($instanceOne === $instanceTwo); + } + + public function testAnnotatedImplementedBy() { + $config = new Configuration(null); + $dic = new DependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\DICTestAnnotatedInterface'); + + $this->assertInstanceOf('rg\injection\DICTestAnnotatedInterfaceImpl', $instance); + } + + public function testNamedAnnotation() { + $config = new Configuration(null); + $config->setClassConfig('rg\injection\DICTestNamed', array( + 'named' => array( + 'implOne' => 'rg\injection\DICTestAnnotatedInterfaceImplOne', + 'implTwo' => 'rg\injection\DICTestAnnotatedInterfaceImplTwo' + ) + )); + $dic = new DependencyInjectionContainer($config); + $instance = $dic->getInstanceOfClass('rg\injection\DICTestNamed'); + + $this->assertInstanceOf('rg\injection\DICTestAnnotatedInterfaceImplOne', $instance->one); + $this->assertInstanceOf('rg\injection\DICTestAnnotatedInterfaceImplTwo', $instance->two); + } +} + +class DICTestClassOne { + /** + * @var \rg\injection\DICTestClassTwo + */ + public $two; + /** + * @var \rg\injection\DICTestClassThree + */ + public $three; + + /** + * @inject + * @var \rg\injection\DICTestClassThree + */ + protected $four; + + /** + * @return DICTestClassThree + */ + public function getFour() { + return $this->four; + } + + /** + * @inject + * @param DICTestClassTwo $two + * @param DICTestClassThree $three + */ + public function __construct(DICTestClassTwo $two, DICTestClassThree $three) { + $this->two = $two; + $this->three = $three; + } + + /** + * @inject + * @param DICTestClassTwo $two + * @param DICTestClassThree $three + * @return string + */ + public function getSomething(DICTestClassTwo $two, DICTestClassThree $three) { + return $two->getSomething() . $three->getSomething(); + } + + public function getSomethingNotInjectible(DICTestClassTwo $two, DICTestClassThree $three) { + return $two->getSomething() . $three->getSomething(); + } + + public function noTypeHint($foo) { + + } +} + +class DICTestClassOneConfigured extends DICTestAbstractClass implements DICTestInterface { + +} + +class DICTestClassTwo { + /** + * @var \rg\injection\DICTestClassThree + */ + public $three; + /** + * @inject + * @param DICTestClassThree $three + */ + public function __construct(DICTestClassThree $three) { + $this->three = $three; + } + + public function getSomething() { + return 'bar'; + } +} + +class DICTestClassThree { + + public function __construct() { + + } + + public function getSomething() { + return 'foo'; + } +} + +class DICTestClassNoInject { + + public function __construct(DICTestClassThree $three) { + + } +} + +class DICTestClassNoTypeHint { + + public $one; + public $two; + + /** + * @inject + */ + public function __construct($one, $two) { + $this->one = $one; + $this->two = $two; + } +} + +class DICTestClassNoTypeHintOptionalArgument { + + public $one; + public $two; + + public function __construct($one, $two = 'bar') { + $this->one = $one; + $this->two = $two; + } +} + +class DICTestClassNoParamTypeHint { + /** + * @inject + */ + public $two; +} + +class DICTestClassPrivateProperty { + /** + * @inject + * @var DICTestClassNoConstructor + */ + private $two; +} + +class DICTestClassPropertyDoubledAnnotation { + /** + * @inject + * @var \rg\injection\DICTestClassNoConstructor + * @var \rg\injection\DICTestClassPrivateProperty + */ + public $two; +} + +class DICTestClassNoConstructor { +} + +abstract class DICTestAbstractClass { +} + +interface DICTestInterface { +} + +/** + * @implementedBy rg\injection\DICTestAnnotatedInterfaceImpl + */ +interface DICTestAnnotatedInterface { +} + +class DICTestAnnotatedInterfaceImpl implements DICTestAnnotatedInterface { + +} + +class DICTestAnnotatedInterfaceImplOne implements DICTestAnnotatedInterface { + +} +class DICTestAnnotatedInterfaceImplTwo implements DICTestAnnotatedInterface { + +} + +class DICTestNamed { + public $one; + /** + * @inject + * @var \rg\injection\DICTestAnnotatedInterface + * @named implTwo + */ + public $two; + + /** + * @inject + * @param DICTestAnnotatedInterface $one + * @named implOne $one + */ + public function __construct(DICTestAnnotatedInterface $one) { + $this->one = $one; + } +} + +class DICTestSingleton { + public $foo; + public $instance; + + /** + * @inject + * @var rg\injection\DICTestClassNoConstructor + */ + public $injectedProperty; + + private function __construct($foo, $instance) { + $this->foo = $foo; + $this->instance = $instance; + } + + /** + * @inject + * @static + * @param DICTestClassNoConstructor $instance + * @return Singleton + */ + public static function getInstance(DICTestClassNoConstructor $instance) { + return new DICTestSingleton('foo', $instance); + } +} + +/** + * @singleton + */ +class DICTestAnnotatedSingleton { +} \ No newline at end of file diff --git a/test/rg/injection/FactoryDependencyInjectionContainerTest.php b/test/rg/injection/FactoryDependencyInjectionContainerTest.php new file mode 100644 index 0000000..33ed9c8 --- /dev/null +++ b/test/rg/injection/FactoryDependencyInjectionContainerTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injection; + +class FactoryDependencyInjectionContainerTest extends \PHPUnit_Framework_TestCase { + + public function testInjectionWithoutFactory() { + $config = new Configuration(null); + + $dic = new FactoryDependencyInjectionContainer($config); + + $instance = $dic->getInstanceOfClass('rg\injection\FDICTestClassOne'); + + $this->assertInstanceOf('rg\injection\FDICTestClassOne', $instance); + + $this->assertInstanceOf('rg\injection\FDICTestClassTwo', $instance->two); + $this->assertInstanceOf('rg\injection\FDICTestClassThree', $instance->three); + $this->assertInstanceOf('rg\injection\FDICTestClassThree', $instance->two->three); + $this->assertInstanceOf('rg\injection\FDICTestClassThree', $instance->getFour()); + } + +} + +class FDICTestClassOne { + /** + * @var \rg\injection\FDICTestClassTwo + */ + public $two; + /** + * @var \rg\injection\FDICTestClassThree + */ + public $three; + + /** + * @inject + * @var \rg\injection\FDICTestClassThree + */ + protected $four; + + /** + * @return FDICTestClassThree + */ + public function getFour() { + return $this->four; + } + + /** + * @inject + * @param FDICTestClassTwo $two + * @param FDICTestClassThree $three + */ + public function __construct(FDICTestClassTwo $two, FDICTestClassThree $three) { + $this->two = $two; + $this->three = $three; + } + + /** + * @inject + * @param FDICTestClassTwo $two + * @param FDICTestClassThree $three + * @return string + */ + public function getSomething(FDICTestClassTwo $two, FDICTestClassThree $three) { + return $two->getSomething() . $three->getSomething(); + } + + public function getSomethingNotInjectible(FDICTestClassTwo $two, FDICTestClassThree $three) { + return $two->getSomething() . $three->getSomething(); + } + + public function noTypeHint($foo) { + + } +} + + +class FDICTestClassTwo { + /** + * @var \rg\injection\FDICTestClassThree + */ + public $three; + /** + * @inject + * @param FDICTestClassThree $three + */ + public function __construct(FDICTestClassThree $three) { + $this->three = $three; + } + + public function getSomething() { + return 'bar'; + } +} + +class FDICTestClassThree { + + public function __construct() { + + } + + public function getSomething() { + return 'foo'; + } +} \ No newline at end of file diff --git a/test/rg/injection/FactoryGeneratorTest.php b/test/rg/injection/FactoryGeneratorTest.php new file mode 100644 index 0000000..f7aef93 --- /dev/null +++ b/test/rg/injection/FactoryGeneratorTest.php @@ -0,0 +1,341 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injection; + +class FactoryGeneratorTest extends \PHPUnit_Framework_TestCase { + + public function testGenerateFactory() { + $config = new Configuration(null); + $config->setClassConfig('rg\injection\FGTestClassOne', array( + 'singleton' => true + )); + $config->setClassConfig('rg\injection\FGTestClassFour', array( + 'singleton' => true + )); + $config->setClassConfig('rg\injection\FGTestClassThree', array( + 'params' => array( + 'foo' => array( + 'value' => 'foo' + ), + 'four' => array( + 'class' => 'rg\injection\FGTestClassFour' + ) + ) + )); + $factoryGenerator = new TestingFactoryGenerator($config); + $factoryGenerator->processFileForClass('rg\injection\FGTestClassOne'); + + $expected = array ( +'rg\\injection\\FGTestClassSimple' => ' 'injectedProperty = $injectedProperty; + return $instance; + } + + +} + +', +'rg\\injection\\FGTestClassThree' => 'getSomething(); + } + + +} + +', + +'rg\\injection\\FGTestClassTwo' => 'getSomething(); + } + + +} + +', +'rg\\injection\\FGTestClassOne' => 'getFour(); + } + + public static function callGetSomething($object) + { + $two = rg\\injection\\generated\\RgInjectionFGTestClassTwoFactory::getInstance(); + $three = rg\\injection\\generated\\RgInjectionFGTestClassThreeFactory::getInstance(); + + return $object->getSomething($two, $three); + } + + public static function callMethodRestriction($object) + { + if (isset($_SERVER["request_method"]) && strtolower($_SERVER["request_method"]) !== "post") { + throw new \RuntimeException("invalid http method " . $_SERVER["REQUEST_METHOD"] . " for rg\injection\FGTestClassOne::methodRestriction(), POST expected"); + } + + + return $object->methodRestriction(); + } + + +} + +class RgInjectionFGTestClassOneProxy extends \\rg\\injection\\FGTestClassOne +{ + + public function __construct($two, $three, $four) + { + $this->four = $four; + parent::__construct($two, $three); + } + + +} + +'); + $this->assertEquals($expected, $factoryGenerator->files); + } + +} + +class TestingFactoryGenerator extends FactoryGenerator { + public $files = array(); + + public function processFileForClass($fullClassName) { + $file = $this->generateFileForClass($fullClassName, false); + if ($file) { + $this->files[$fullClassName] = $file->generate(); + } + } +} + +class FGTestClassOne { + /** + * @var \rg\injection\FGTestClassTwo + */ + public $two; + /** + * @var \rg\injection\FGTestClassThree + */ + public $three; + + /** + * @inject + * @var \rg\injection\FGTestClassThree + */ + protected $four; + + /** + * @return FGTestClassThree + */ + public function getFour() { + return $this->four; + } + + /** + * @inject + * @param FGTestClassTwo $two + * @param FGTestClassThree $three + */ + public function __construct(FGTestClassTwo $two, FGTestClassThree $three) { + $this->two = $two; + $this->three = $three; + } + + /** + * @inject + * @param FGTestClassTwo $two + * @param FGTestClassThree $three + * @return string + */ + public function getSomething(FGTestClassTwo $two, FGTestClassThree $three) { + return $two->getSomething() . $three->getSomething(); + } + + public function getSomethingNotInjectible(FGTestClassTwo $two, FGTestClassThree $three) { + return $two->getSomething() . $three->getSomething(); + } + + public function noTypeHint($foo) { + + } + + /** + * @method POST + */ + public function methodRestriction() { + + } +} + + +class FGTestClassTwo { + /** + * @var \rg\injection\FGTestClassThree + */ + public $three; + /** + * @inject + * @param FGTestClassThree $three + */ + public function __construct(FGTestClassThree $three) { + $this->three = $three; + } + + public function getSomething() { + return 'bar'; + } +} + +class FGTestClassThree { + + public function __construct($foo, $four) { + + } + + public function getSomething() { + return 'foo'; + } +} + +class FGTestClassFour { + + /** + * @inject + * @var rg\injection\FGTestClassSimple + */ + protected $injectedProperty; + + private function __construct() { + + } + + /** + * @inject + * @static + * @param FGTestClassSimple $simple + * @return FGTestClassFour + */ + public static function getInstance(FGTestClassSimple $simple) { + return new FGTestClassFour(); + } +} + +class FGTestClassSimple { + +} \ No newline at end of file diff --git a/test/rg/injection/test_config.php b/test/rg/injection/test_config.php new file mode 100644 index 0000000..17cd63a --- /dev/null +++ b/test/rg/injection/test_config.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +return array( + 'foo' => 'bar' +); \ No newline at end of file