diff --git a/.gitignore b/.gitignore index 8c59329..d3a03a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea /build/ /vendor/ /composer.phar diff --git a/composer.json b/composer.json index d3e045b..f40a75d 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,11 @@ "psr/log": "~1.0.0" }, "require-dev": { - "phpunit/phpunit": "~7.0.0" + "phpunit/phpunit": "~7.0.0", + "ocramius/proxy-manager": "^2.1.0" + }, + "suggest": { + "ocramius/proxy-manager": "For lazy loading" }, "autoload": { "psr-0": { diff --git a/src/rg/injektor/Configuration.php b/src/rg/injektor/Configuration.php index 701450a..99bdfa3 100644 --- a/src/rg/injektor/Configuration.php +++ b/src/rg/injektor/Configuration.php @@ -24,15 +24,36 @@ class Configuration { */ private $factoryPath; + /** + * @var bool + */ + private $lazyLoading; + + /** + * @var bool + */ + private $lazyServices; + + /** + * @var bool + */ + private $lazySingletons; + /** * @param string $configurationFilePath * @param string $factoryPath + * @param bool $lazyLoading + * @param bool $lazyServices + * @param bool $lazySingletons */ - public function __construct($configurationFilePath = null, $factoryPath = '') { + public function __construct($configurationFilePath = null, $factoryPath = '', $lazyLoading = false, $lazyServices = false, $lazySingletons = false) { if ($configurationFilePath) { $this->addConfigFile($configurationFilePath); } $this->factoryPath = $factoryPath; + $this->lazyLoading = $lazyLoading; + $this->lazyServices = $lazyServices; + $this->lazySingletons = $lazySingletons; } /** @@ -96,4 +117,24 @@ public function getFactoryPath() { return $this->factoryPath; } -} \ No newline at end of file + /** + * @return bool + */ + public function isLazyLoading() { + return $this->lazyLoading; + } + + /** + * @return bool + */ + public function isLazyServices() { + return $this->lazyServices; + } + + /** + * @return bool + */ + public function isLazySingletons() { + return $this->lazySingletons; + } +} diff --git a/src/rg/injektor/DependencyInjectionContainer.php b/src/rg/injektor/DependencyInjectionContainer.php index beece13..21ab232 100644 --- a/src/rg/injektor/DependencyInjectionContainer.php +++ b/src/rg/injektor/DependencyInjectionContainer.php @@ -10,6 +10,10 @@ namespace rg\injektor; use Doctrine\Common\Annotations\PhpParser; +use ProxyManager\Configuration as ProxyManagerConfiguration; +use ProxyManager\Factory\LazyLoadingValueHolderFactory; +use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; +use ProxyManager\Proxy\LazyLoadingInterface; use Psr\Log\LoggerInterface; use rg\injektor\annotations\Named; @@ -53,6 +57,16 @@ class DependencyInjectionContainer { */ private $logger; + /** + * @var bool + */ + private $supportsLazyLoading = false; + + /** + * @var LazyLoadingValueHolderFactory|null + */ + private $lazyProxyFactory; + /** * used for injection loop detection * @@ -71,6 +85,7 @@ public function __construct(Configuration $config = null) { } $this->annotationReader = new SimpleAnnotationReader(); + $this->supportsLazyLoading = class_exists('ProxyManager\Configuration'); } /** @@ -197,14 +212,24 @@ public function getInstanceOfClass($fullClassName, array $constructorArguments = $instance = $classReflection->getMethod('getInstance')->invokeArgs(null, $constructorArguments); } else { $constructorArguments = $this->getConstructorArguments($classReflection, $classConfig, $constructorArguments); - if ($constructorArguments) { - $instance = $classReflection->newInstanceArgs($constructorArguments); - } else { - $instance = $classReflection->newInstanceArgs(array()); - } - } + $instanceConstructor = function () use ($classReflection, $constructorArguments, $isConfiguredAsSingleton, $isConfiguredAsService, $singletonKey, $fullClassName) { + $instance = $classReflection->newInstanceArgs($constructorArguments ? $constructorArguments : []); - $this->log('Created instance [' . spl_object_hash($instance) . '] of class [' . get_class($instance) . ']'); + if ($isConfiguredAsSingleton) { + $this->log('Added singleton instance [' . spl_object_hash($instance) . '] of class [' . get_class($instance) . '], Singleton Key: [' . $singletonKey . ']'); + $this->instances[$singletonKey] = $instance; + } + if ($isConfiguredAsService) { + $this->log('Added service instance [' . spl_object_hash($instance) . '] of class [' . $fullClassName . ']'); + $this->instances[$fullClassName] = $instance; + } + + $instance = $this->injectProperties($classReflection, $instance); + + return $instance; + }; + $instance = $this->createNewInstance($classConfig, $classReflection, $instanceConstructor); + } if ($isConfiguredAsSingleton) { $this->log('Added singleton instance [' . spl_object_hash($instance) . '] of class [' . get_class($instance) . '], Singleton Key: [' . $singletonKey . ']'); @@ -217,6 +242,8 @@ public function getInstanceOfClass($fullClassName, array $constructorArguments = $instance = $this->injectProperties($classReflection, $instance); + $this->log('Created instance [' . spl_object_hash($instance) . '] of class [' . get_class($instance) . ']'); + if ($this->iterationDepth > 0) { $this->iterationDepth--; } @@ -224,6 +251,42 @@ public function getInstanceOfClass($fullClassName, array $constructorArguments = return $instance; } + /** + * @param array $classConfig + * @param \ReflectionClass $classReflection + * @param callable $instanceConstructor + * + * @return object + */ + private function createNewInstance(array $classConfig, \ReflectionClass $classReflection, $instanceConstructor) { + if ($this->supportsLazyLoading && $this->config->isLazyLoading() && $this->isConfiguredAsLazy($classConfig, $classReflection)) { + return $this->wrapInstanceWithLazyProxy($classReflection->getName(), $instanceConstructor); + } else { + return $instanceConstructor(); + } + } + + /** + * @param string $className + * @param callable $instanceConstructor + * + * @return \ProxyManager\Proxy\VirtualProxyInterface + */ + private function wrapInstanceWithLazyProxy($className, $instanceConstructor) { + $proxyParameters = [ + + ]; + return $this->getLazyProxyFactory()->createProxy( + $className, + function (&$wrappedObject, LazyLoadingInterface $proxy) use ($instanceConstructor) { + $proxy->setProxyInitializer(null); + $wrappedObject = $instanceConstructor(); + return true; + }, + $proxyParameters + ); + } + /** * @param string $fullClassName * @param array $constructorArguments @@ -295,6 +358,10 @@ private function getDefaultArguments($classConfig, $defaultConstructorArguments) * @throws InjectionException */ private function injectProperties($classReflection, $instance) { + if ($instance instanceof \ProxyManager\Proxy\LazyLoadingInterface) { + return $instance; // Not inject into lazy proxies + } + $properties = $this->getInjectableProperties($classReflection); foreach ($properties as $property) { $this->injectProperty($property, $instance); @@ -419,7 +486,15 @@ public function getClassReflection($fullClassName) { */ public function getProvidedConfiguredClass($classConfig, \ReflectionClass $classReflection, $name = null, $additionalArgumentsForProvider = array()) { if ($namedAnnotation = $this->getProviderClassName($classConfig, $classReflection, $name)) { - return $this->getRealClassInstanceFromProvider($namedAnnotation->getClassName(), $classReflection->name, array_merge($namedAnnotation->getParameters(), $additionalArgumentsForProvider)); + $instanceConstructor = function () use ($namedAnnotation, $classReflection, $additionalArgumentsForProvider) { + return $this->getRealClassInstanceFromProvider($namedAnnotation->getClassName(), $classReflection->name, array_merge($namedAnnotation->getParameters(), $additionalArgumentsForProvider)); + }; + + if ($this->supportsLazyLoading && $this->config->isLazyLoading() && $this->isConfiguredAsLazy($classConfig, $classReflection)) { + return $this->wrapInstanceWithLazyProxy($classReflection->name, $instanceConstructor); + } else { + return $instanceConstructor(); + } } return null; @@ -599,6 +674,51 @@ public function isConfiguredAsService(array $classConfig, \ReflectionClass $clas return strpos($classComment, '@service') !== false; } + /** + * @param array $classConfig + * @param \ReflectionClass $classReflection + * @return bool + */ + public function isConfiguredAsLazy(array $classConfig, \ReflectionClass $classReflection) { + // Force no lazy loading + if ($this->isConfiguredAsNoLazy($classConfig, $classReflection)) { + return false; + } + + // Lazy services + if ($this->config->isLazyServices() && $this->isConfiguredAsService($classConfig, $classReflection)) { + return true; + } + + // Lazy singletons + if ($this->config->isLazySingletons() && $this->isConfiguredAsSingleton($classConfig, $classReflection)) { + return true; + } + + if (isset($classConfig['lazy'])) { + return (bool) $classConfig['lazy']; + } + + $classComment = $classReflection->getDocComment(); + + return strpos($classComment, '@lazy') !== false; + } + + /** + * @param array $classConfig + * @param \ReflectionClass $classReflection + * @return bool + */ + public function isConfiguredAsNoLazy(array $classConfig, \ReflectionClass $classReflection) { + if (isset($classConfig['noLazy'])) { + return (bool) $classConfig['noLazy']; + } + + $classComment = $classReflection->getDocComment(); + + return strpos($classComment, '@noLazy') !== false; + } + /** * @param object $object * @param string $methodName @@ -805,6 +925,27 @@ private function getImplementingClassBecauseOfName($argumentClass, $classConfig, return $classConfig['named'][$name]; } + /** + * @return \ProxyManager\Configuration + */ + private function getLazyProxyFactoryConfiguration() { + $config = new ProxyManagerConfiguration(); + $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); + return $config; + } + + /** + * @return LazyLoadingValueHolderFactory|null + */ + private function getLazyProxyFactory() { + if ($this->supportsLazyLoading && !$this->lazyProxyFactory) { + $config = $this->getLazyProxyFactoryConfiguration(); + $this->lazyProxyFactory = new LazyLoadingValueHolderFactory($config); + } + + return $this->lazyProxyFactory; + } + /** * @param $docComment * @return bool @@ -822,4 +963,11 @@ protected function log($string) { } } + /** + * @return bool + */ + public function supportsLazyLoading() + { + return $this->supportsLazyLoading; + } } diff --git a/src/rg/injektor/FactoryDependencyInjectionContainer.php b/src/rg/injektor/FactoryDependencyInjectionContainer.php index 78c4a3d..d50fb75 100755 --- a/src/rg/injektor/FactoryDependencyInjectionContainer.php +++ b/src/rg/injektor/FactoryDependencyInjectionContainer.php @@ -79,6 +79,14 @@ public function getProxyClassName($fullClassName) { return self::$prefix . $this->getStrippedClassName($fullClassName) . 'Proxy'; } + /** + * @param $fullClassName + * @return string + */ + public function getLazyProxyClassName($fullClassName) { + return self::$prefix . $this->getStrippedClassName($fullClassName) . 'Lazy'; + } + /** * @param string $fullClassName * @return string @@ -114,5 +122,4 @@ protected function factoryClassExists($fullFactoryClassName, $factoryClassName) return false; } - } diff --git a/src/rg/injektor/generators/CreateInstanceMethod.php b/src/rg/injektor/generators/CreateInstanceMethod.php new file mode 100644 index 0000000..ae370ff --- /dev/null +++ b/src/rg/injektor/generators/CreateInstanceMethod.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injektor\generators; + +class CreateInstanceMethod extends \Zend\Code\Generator\MethodGenerator { + + public function __construct() { + parent::__construct('createInstance', [], self::FLAG_PRIVATE | self::FLAG_STATIC); + + $parameter = new \Zend\Code\Generator\ParameterGenerator('parameters', 'array', array()); + $this->setParameter($parameter); + } +} diff --git a/src/rg/injektor/generators/FileGenerator.php b/src/rg/injektor/generators/FileGenerator.php index 42fd2c3..69a6ec5 100644 --- a/src/rg/injektor/generators/FileGenerator.php +++ b/src/rg/injektor/generators/FileGenerator.php @@ -8,6 +8,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +use ProxyManager\Generator\ClassGenerator; +use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator; use Zend\Code\Generator; use rg\injektor\Configuration; use rg\injektor\FactoryDependencyInjectionContainer; @@ -89,6 +91,7 @@ public function __construct(FactoryGenerator $factoryGenerator, Configuration $c public function getGeneratedFile() { $classConfig = $this->config->getClassConfig($this->fullClassName); $factoryName = $this->dic->getFactoryClassName($this->fullClassName); + $lazyProxyClassName = null; $classReflection = $this->dic->getClassReflection($this->fullClassName); @@ -96,10 +99,14 @@ public function getGeneratedFile() { return null; } - $file = new Generator\FileGenerator(); - $factoryClass = new \rg\injektor\generators\FactoryClass($factoryName); - $instanceMethod = new \rg\injektor\generators\InstanceMethod($this->factoryGenerator); + $getInstanceMethod = new \rg\injektor\generators\GetInstanceMethod(); + $createInstanceMethod = new \rg\injektor\generators\CreateInstanceMethod(); + $loadDependenciesMethod = new \rg\injektor\generators\LoadDependenciesMethod(); + + $file = new Generator\FileGenerator(); + $file->setNamespace('rg\injektor\generated'); + $file->setFilename($this->factoryPath . DIRECTORY_SEPARATOR . $factoryName . '.php'); $arguments = array(); @@ -114,9 +121,15 @@ public function getGeneratedFile() { $isSingleton = $this->dic->isConfiguredAsSingleton($classConfig, $classReflection); $isService = $this->dic->isConfiguredAsService($classConfig, $classReflection); + $isLazy = $this->dic->supportsLazyLoading() && $this->config->isLazyLoading() && $this->dic->isConfiguredAsLazy($classConfig, $classReflection); - $body = '$i = 0;' . PHP_EOL; + $createInstanceBody = ''; + + if ($isLazy) { + $createInstanceBody .= 'self::loadDependencies();' . PHP_EOL; + } + $createInstanceBody .= '$i = 0;' . PHP_EOL; if ($isSingleton || $isService) { $defaultValue = new Generator\PropertyValueGenerator(array(), Generator\ValueGenerator::TYPE_ARRAY, Generator\ValueGenerator::OUTPUT_SINGLE_LINE); $property = new Generator\PropertyGenerator('instance', $defaultValue, Generator\PropertyGenerator::FLAG_PRIVATE); @@ -124,24 +137,12 @@ public function getGeneratedFile() { $factoryClass->addPropertyFromGenerator($property); } - if ($isSingleton) { - $body .= '$singletonKey = serialize($parameters) . "#" . getmypid();' . PHP_EOL; - $body .= 'if (isset(self::$instance[$singletonKey])) {' . PHP_EOL; - $body .= ' return self::$instance[$singletonKey];' . PHP_EOL; - $body .= '}' . PHP_EOL . PHP_EOL; - } - - if ($isService) { - $body .= 'if (self::$instance) {' . PHP_EOL; - $body .= ' return self::$instance;' . PHP_EOL; - $body .= '}' . PHP_EOL . PHP_EOL; - } - - $providerClassName = $this->dic->getProviderClassName($classConfig, new \ReflectionClass($this->fullClassName), null); + $fullClassNameRefection = new \ReflectionClass($this->fullClassName); + $providerClassName = $this->dic->getProviderClassName($classConfig, $fullClassNameRefection, null); if ($providerClassName && $providerClassName->getClassName()) { $argumentFactory = $this->dic->getFullFactoryClassName($providerClassName->getClassName()); $this->factoryGenerator->processFileForClass($providerClassName->getClassName()); - $body .= '$instance = \\' . $argumentFactory . '::getInstance(array())->get();' . PHP_EOL; + $createInstanceBody .= '$instance = \\' . $argumentFactory . '::getInstance(array())->get();' . PHP_EOL; $this->usedFactories[] = $argumentFactory; } else { // constructor method arguments @@ -156,7 +157,7 @@ public function getGeneratedFile() { } - $body .= 'if (!$parameters) {' . PHP_EOL; + $createInstanceBody .= 'if (!$parameters) {' . PHP_EOL; foreach ($arguments as $argument) { /** @var \ReflectionParameter $argument */ @@ -176,14 +177,14 @@ public function getGeneratedFile() { if ($injectionParameter->getFactoryName()) { $this->usedFactories[] = $injectionParameter->getFactoryName(); } - $body .= ' ' . $injectionParameter->getProcessingBody(); + $createInstanceBody .= ' ' . $injectionParameter->getProcessingBody(); } catch (\Exception $e) { - $body .= ' ' . $injectionParameter->getDefaultProcessingBody(); + $createInstanceBody .= ' ' . $injectionParameter->getDefaultProcessingBody(); } } - $body .= '}' . PHP_EOL; - $body .= 'else if (array_key_exists(0, $parameters)) {' . PHP_EOL; + $createInstanceBody .= '}' . PHP_EOL; + $createInstanceBody .= 'else if (array_key_exists(0, $parameters)) {' . PHP_EOL; foreach ($arguments as $argument) { /** @var \ReflectionParameter $argument */ @@ -203,14 +204,14 @@ public function getGeneratedFile() { if ($injectionParameter->getFactoryName()) { $this->usedFactories[] = $injectionParameter->getFactoryName(); } - $body .= ' ' . $injectionParameter->getProcessingBody(); + $createInstanceBody .= ' ' . $injectionParameter->getProcessingBody(); } catch (\Exception $e) { - $body .= ' ' . $injectionParameter->getDefaultProcessingBody(); + $createInstanceBody .= ' ' . $injectionParameter->getDefaultProcessingBody(); } } - $body .= '}' . PHP_EOL; - $body .= 'else {' . PHP_EOL; + $createInstanceBody .= '}' . PHP_EOL; + $createInstanceBody .= 'else {' . PHP_EOL; foreach ($arguments as $argument) { /** @var \ReflectionParameter $argument */ @@ -230,13 +231,13 @@ public function getGeneratedFile() { if ($injectionParameter->getFactoryName()) { $this->usedFactories[] = $injectionParameter->getFactoryName(); } - $body .= ' ' . $injectionParameter->getProcessingBody(); + $createInstanceBody .= ' ' . $injectionParameter->getProcessingBody(); } catch (\Exception $e) { - $body .= ' ' . $injectionParameter->getDefaultProcessingBody(); + $createInstanceBody .= ' ' . $injectionParameter->getDefaultProcessingBody(); } } - $body .= '}' . PHP_EOL; + $createInstanceBody .= '}' . PHP_EOL; } // Property injection @@ -246,36 +247,80 @@ public function getGeneratedFile() { $proxyName = $this->dic->getProxyClassName($this->fullClassName); if ($this->dic->isSingleton($classReflection)) { $file->setClass($this->createProxyClass($proxyName)); - $body .= PHP_EOL . '$instance = ' . $proxyName . '::getInstance(' . implode(', ', $this->constructorArgumentStringParts) . ');' . PHP_EOL; + $createInstanceBody .= PHP_EOL . '$instance = ' . $proxyName . '::getInstance(' . implode(', ', $this->constructorArgumentStringParts) . ');' . PHP_EOL; } else { $file->setClass($this->createProxyClass($proxyName)); - $body .= PHP_EOL . '$instance = new ' . $proxyName . '(' . implode(', ', $this->constructorArgumentStringParts) . ');' . PHP_EOL; + $createInstanceBody .= PHP_EOL . '$instance = new ' . $proxyName . '(' . implode(', ', $this->constructorArgumentStringParts) . ');' . PHP_EOL; } } else { if ($this->dic->isSingleton($classReflection)) { - $body .= PHP_EOL . '$instance = \\' . $this->fullClassName . '::getInstance(' . implode(', ', $this->constructorArgumentStringParts) . ');' . PHP_EOL; + $createInstanceBody .= PHP_EOL . '$instance = \\' . $this->fullClassName . '::getInstance(' . implode(', ', $this->constructorArgumentStringParts) . ');' . PHP_EOL; } else { - $body .= PHP_EOL . '$instance = new \\' . $this->fullClassName . '(' . implode(', ', $this->constructorArgumentStringParts) . ');' . PHP_EOL; + $createInstanceBody .= PHP_EOL . '$instance = new \\' . $this->fullClassName . '(' . implode(', ', $this->constructorArgumentStringParts) . ');' . PHP_EOL; } } } + // Make the newly created instance immediately available for property injection if ($isSingleton) { - $body .= 'self::$instance[$singletonKey] = $instance;' . PHP_EOL; + $createInstanceBody .= '$singletonKey = serialize($parameters) . "#" . getmypid();' . PHP_EOL; + $createInstanceBody .= 'self::$instance[$singletonKey] = $instance;' . PHP_EOL; } if ($isService) { - $body .= 'self::$instance = $instance;' . PHP_EOL; + $createInstanceBody .= 'self::$instance = $instance;' . PHP_EOL; } foreach ($this->injectableArguments as $injectableArgument) { - $body .= '$instance->propertyInjection' . $injectableArgument->getName() . '();' . PHP_EOL; + $createInstanceBody .= '$instance->propertyInjection' . $injectableArgument->getName() . '();' . PHP_EOL; + } + + $getInstanceBody = ''; + if ($isSingleton) { + $getInstanceBody .= '$singletonKey = serialize($parameters) . "#" . getmypid();' . PHP_EOL; + $getInstanceBody .= 'if (isset(self::$instance[$singletonKey])) {' . PHP_EOL; + $getInstanceBody .= ' return self::$instance[$singletonKey];' . PHP_EOL; + $getInstanceBody .= '}' . PHP_EOL . PHP_EOL; } - $body .= 'return $instance;' . PHP_EOL; + if ($isService) { + $getInstanceBody .= 'if (self::$instance) {' . PHP_EOL; + $getInstanceBody .= ' return self::$instance;' . PHP_EOL; + $getInstanceBody .= '}' . PHP_EOL . PHP_EOL; + } + + if ($isLazy) { + // Create + $lazyProxyClassName = $this->dic->getLazyProxyClassName($this->fullClassName); + $getInstanceBody .= '$instance = ' . $lazyProxyClassName . '::staticProxyConstructor(' . PHP_EOL; + $getInstanceBody .= ' function (&$wrappedObject, $proxy) use ($parameters) {' . PHP_EOL; + $getInstanceBody .= ' $proxy->setProxyInitializer(null);' . PHP_EOL; + $getInstanceBody .= ' $wrappedObject = self::createInstance($parameters);' . PHP_EOL; + $getInstanceBody .= ' return true;' . PHP_EOL; + $getInstanceBody .= ' }' . PHP_EOL; + $getInstanceBody .= ');' . PHP_EOL; + $getInstanceBody .= ''; + + // Store the service/singleton instance + if ($isSingleton) { + $getInstanceBody .= '$singletonKey = serialize($parameters) . "#" . getmypid();' . PHP_EOL; + $getInstanceBody .= 'self::$instance[$singletonKey] = $instance;' . PHP_EOL; + } + if ($isService) { + $getInstanceBody .= 'self::$instance = $instance;' . PHP_EOL; + } + + // Create instance method + $createInstanceBody .= 'return $instance;' . PHP_EOL; + $createInstanceMethod->setBody($createInstanceBody); + $factoryClass->addMethodFromGenerator($createInstanceMethod); + } else { + $getInstanceBody .= $createInstanceBody; + } + + $getInstanceBody .= 'return $instance;' . PHP_EOL; - $instanceMethod->setBody($body); - $instanceMethod->setStatic(true); - $factoryClass->addMethodFromGenerator($instanceMethod); + $getInstanceMethod->setBody($getInstanceBody); + $factoryClass->addMethodFromGenerator($getInstanceMethod); // Add Factory Method $methods = $classReflection->getMethods(); @@ -289,17 +334,35 @@ public function getGeneratedFile() { } } - // Generate File - - $file->setNamespace('rg\injektor\generated'); + // Dependency require statements $this->usedFactories = array_unique($this->usedFactories); - foreach ($this->usedFactories as &$usedFactory) { + $requiredFactoryFiles = array(); + foreach ($this->usedFactories as $usedFactory) { $usedFactory = str_replace('rg\injektor\generated\\', '', $usedFactory); - $usedFactory = $usedFactory . '.php'; + $requiredFactoryFiles[] = $usedFactory . '.php'; + } + if ($isLazy) { + // When the class is lazy, only load the dependencies when the real instance is created + $loadDependenciesBody = ''; + foreach ($requiredFactoryFiles as $usedFactory) { + $loadDependenciesBody .= 'require_once \'' . $usedFactory . '\';' . PHP_EOL; + } + $loadDependenciesMethod->setBody($loadDependenciesBody); + $factoryClass->addMethodFromGenerator($loadDependenciesMethod); + } else { + // When the class is not lazy loaded, we can get all the dependencies right away, because we'll need them for instance creation + $file->setRequiredFiles($requiredFactoryFiles); } - $file->setRequiredFiles($this->usedFactories); + $file->setClass($factoryClass); - $file->setFilename($this->factoryPath . DIRECTORY_SEPARATOR . $factoryName . '.php'); + + // Add lazy proxy class + if ($isLazy) { + $proxyGenerator = new LazyLoadingValueHolderGenerator(); + $generatedClass = new ClassGenerator($lazyProxyClassName); + $proxyGenerator->generate($fullClassNameRefection, $generatedClass); + $file->setClass($generatedClass); + } return $file; } diff --git a/src/rg/injektor/generators/GetInstanceMethod.php b/src/rg/injektor/generators/GetInstanceMethod.php new file mode 100644 index 0000000..c1bdbcd --- /dev/null +++ b/src/rg/injektor/generators/GetInstanceMethod.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injektor\generators; + +class GetInstanceMethod extends \Zend\Code\Generator\MethodGenerator { + + public function __construct() { + parent::__construct('getInstance', [], self::FLAG_PUBLIC | self::FLAG_STATIC); + + $parameter = new \Zend\Code\Generator\ParameterGenerator('parameters', 'array', array()); + $this->setParameter($parameter); + } +} diff --git a/src/rg/injektor/generators/InstanceMethod.php b/src/rg/injektor/generators/InstanceMethod.php deleted file mode 100644 index 9571fc0..0000000 --- a/src/rg/injektor/generators/InstanceMethod.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace rg\injektor\generators; - -class InstanceMethod extends \Zend\Code\Generator\MethodGenerator { - - /** - * @var \rg\injektor\generators\FactoryGenerator - */ - private $factoryGenerator; - - /** - * @param \rg\injektor\generators\FactoryGenerator $factoryGenerator - */ - public function __construct(FactoryGenerator $factoryGenerator) { - parent::__construct('getInstance'); - - $this->factoryGenerator = $factoryGenerator; - - $parameter = new \Zend\Code\Generator\ParameterGenerator('parameters', 'array', array()); - $this->setParameter($parameter); - } -} \ No newline at end of file diff --git a/src/rg/injektor/generators/LoadDependenciesMethod.php b/src/rg/injektor/generators/LoadDependenciesMethod.php new file mode 100644 index 0000000..81c83b8 --- /dev/null +++ b/src/rg/injektor/generators/LoadDependenciesMethod.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace rg\injektor\generators; + +class LoadDependenciesMethod extends \Zend\Code\Generator\MethodGenerator { + + public function __construct() { + parent::__construct('loadDependencies', [], self::FLAG_PRIVATE | self::FLAG_STATIC); + } +} diff --git a/test/rg/injektor/DependencyInjectionContainerTest.php b/test/rg/injektor/DependencyInjectionContainerTest.php index 8700574..9db66f6 100644 --- a/test/rg/injektor/DependencyInjectionContainerTest.php +++ b/test/rg/injektor/DependencyInjectionContainerTest.php @@ -202,9 +202,7 @@ public function testGetInstanceOfService() { public function testGetInstanceOfAnnotatedService() { $config = new Configuration(null, __DIR__ . '/_factories'); - $config->setClassConfig('rg\injektor\DICTestAnnotatedService', [ - 'service' => true - ]); + $config->setClassConfig('rg\injektor\DICTestAnnotatedService', []); $dic = $this->getContainer($config); $instance = $dic->getInstanceOfClass('rg\injektor\DICTestAnnotatedService', ['arg' => 123]); @@ -213,6 +211,69 @@ public function testGetInstanceOfAnnotatedService() { $this->assertSame($instance, $instanceTwo); } + public function testGetInstanceOfLazy() { + $config = new Configuration(null, __DIR__ . '/_factories', true); + + $config->setClassConfig('rg\injektor\DICTestLazy', [ + 'lazy' => true + ]); + $dic = $this->getContainer($config); + $instance = $dic->getInstanceOfClass('rg\injektor\DICTestLazy', ['arg' => 123]); + + $this->assertInstanceOf('rg\injektor\DICTestLazy', $instance); + $this->assertInstanceOf('ProxyManager\Proxy\LazyLoadingInterface', $instance); + + $this->assertEquals('success', $instance->someMethod(), 'Must be able to call someMethod() on proxy object'); + } + + public function testGetInstanceOfAnnotatedLazy() { + $config = new Configuration(null, __DIR__ . '/_factories', true); + + $config->setClassConfig('rg\injektor\DICTestAnnotatedLazy', []); + $dic = $this->getContainer($config); + $instance = $dic->getInstanceOfClass('rg\injektor\DICTestAnnotatedLazy', ['arg' => 123]); + + $this->assertInstanceOf('rg\injektor\DICTestAnnotatedLazy', $instance); + $this->assertInstanceOf('ProxyManager\Proxy\LazyLoadingInterface', $instance); + + $this->assertEquals('success', $instance->someMethod(), 'Must be able to call someMethod() on proxy object'); + } + + public function testGetInstanceOfLazyService() { + $config = new Configuration(null, __DIR__ . '/_factories', true); + + $config->setClassConfig('rg\injektor\DICTestLazyService', [ + 'lazy' => true, + 'service' => true, + ]); + $dic = $this->getContainer($config); + $instance = $dic->getInstanceOfClass('rg\injektor\DICTestLazyService', ['arg' => 123]); + $instance2 = $dic->getInstanceOfClass('rg\injektor\DICTestLazyService', ['arg' => 123]); + + $this->assertSame($instance, $instance2); + + $this->assertInstanceOf('rg\injektor\DICTestLazyService', $instance); + $this->assertInstanceOf('ProxyManager\Proxy\LazyLoadingInterface', $instance); + + $this->assertEquals('success', $instance->someMethod(), 'Must be able to call someMethod() on proxy object'); + } + + public function testGetInstanceOfAnnotatedLazyService() { + $config = new Configuration(null, __DIR__ . '/_factories', true); + + $config->setClassConfig('rg\injektor\DICTestAnnotatedLazyService', []); + $dic = $this->getContainer($config); + $instance = $dic->getInstanceOfClass('rg\injektor\DICTestAnnotatedLazyService', ['arg' => 123]); + $instance2 = $dic->getInstanceOfClass('rg\injektor\DICTestAnnotatedLazyService', ['arg' => 123]); + + $this->assertSame($instance, $instance2); + + $this->assertInstanceOf('rg\injektor\DICTestAnnotatedLazyService', $instance); + $this->assertInstanceOf('ProxyManager\Proxy\LazyLoadingInterface', $instance); + + $this->assertEquals('success', $instance->someMethod(), 'Must be able to call someMethod() on proxy object'); + } + public function testGetInstanceOfServiceWithDifferentArgumentsStillReturnSameInstance() { $config = new Configuration(null, __DIR__ . '/_factories'); diff --git a/test/rg/injektor/generators/FactoryGeneratorTest.php b/test/rg/injektor/generators/FactoryGeneratorTest.php index b4f5f5b..dc1467f 100644 --- a/test/rg/injektor/generators/FactoryGeneratorTest.php +++ b/test/rg/injektor/generators/FactoryGeneratorTest.php @@ -99,12 +99,12 @@ class rg_injektor_generators_FGTestClassFourFactory public static function getInstance(array \$parameters = []) { - \$i = 0; \$singletonKey = serialize(\$parameters) . "#" . getmypid(); if (isset(self::\$instance[\$singletonKey])) { return self::\$instance[\$singletonKey]; } + \$i = 0; if (!\$parameters) { \$simple = \\rg\injektor\generated\\rg_injektor_generators_FGTestClassSimpleFactory::getInstance(array ( )); @@ -119,6 +119,7 @@ public static function getInstance(array \$parameters = []) } \$instance = rg_injektor_generators_FGTestClassFourProxy::getInstance(\$simple); + \$singletonKey = serialize(\$parameters) . "#" . getmypid(); self::\$instance[\$singletonKey] = \$instance; \$instance->propertyInjectioninjectedProperty(); return \$instance; @@ -272,12 +273,12 @@ class rg_injektor_generators_FGTestClassOneFactory public static function getInstance(array \$parameters = []) { - \$i = 0; \$singletonKey = serialize(\$parameters) . "#" . getmypid(); if (isset(self::\$instance[\$singletonKey])) { return self::\$instance[\$singletonKey]; } + \$i = 0; if (!\$parameters) { \$two = \\rg\injektor\generated\\rg_injektor_generators_FGTestClassTwoFactory::getInstance(array ( )); @@ -298,6 +299,7 @@ public static function getInstance(array \$parameters = []) } \$instance = new rg_injektor_generators_FGTestClassOneProxy(\$two, \$three); + \$singletonKey = serialize(\$parameters) . "#" . getmypid(); self::\$instance[\$singletonKey] = \$instance; \$instance->propertyInjectionfour(); return \$instance; diff --git a/test/rg/injektor/test_classes.php b/test/rg/injektor/test_classes.php index d0c2063..20e2fd0 100644 --- a/test/rg/injektor/test_classes.php +++ b/test/rg/injektor/test_classes.php @@ -441,6 +441,49 @@ public function __construct($arg) { } } + class DICTestLazy { + public function __construct($arg) { + + } + public function someMethod() { + return 'success'; + } + } + + /** + * @lazy + */ + class DICTestAnnotatedLazy { + public function __construct($arg) { + + } + public function someMethod() { + return 'success'; + } + } + + class DICTestLazyService { + public function __construct($arg) { + + } + public function someMethod() { + return 'success'; + } + } + + /** + * @lazy + * @service + */ + class DICTestAnnotatedLazyService { + public function __construct($arg) { + + } + public function someMethod() { + return 'success'; + } + } + class DICTestProvidedInterfaceImpl1 implements DICTestProvidedInterface { }