diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7e3f718 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2006-2012 Marco Pivetta + +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 NON INFRINGEMENT. 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. \ No newline at end of file diff --git a/Module.php b/Module.php new file mode 100644 index 0000000..6f79bba --- /dev/null +++ b/Module.php @@ -0,0 +1,2 @@ +=5.3.3", + "zendframework/zendframework": ">=2.0.0beta4" + }, + "autoload": { + "psr-0": { + "OcraDiCompiler": "src" + } + } +} diff --git a/src/OcraDiCompiler/Dumper.php b/src/OcraDiCompiler/Dumper.php new file mode 100644 index 0000000..ba93ad3 --- /dev/null +++ b/src/OcraDiCompiler/Dumper.php @@ -0,0 +1,118 @@ + + */ +class Dumper +{ + /** + * @var Di + */ + protected $di; + + /** + * @var DependencyInjectorProxy + */ + protected $proxy; + + /** + * @param Di $di + */ + public function __construct(Di $di) + { + $this->di = $di; + $this->proxy = new DependencyInjectorProxy($di); + } + + /** + * @return array + */ + public function getAliases() + { + return $this->di->instanceManager()->getAliases(); + } + + /** + * @return array + */ + public function getInitialInstanceDefinitions() + { + $im = $this->di->instanceManager(); + $classes = $im->getClasses(); + $aliases = array_keys($im->getAliases()); + return array_unique(array_merge($classes, $aliases)); + } + + /** + * @return array + */ + public function getAllInjectedDefinitions() + { + return $this->getInjectedDefinitions($this->getInitialInstanceDefinitions()); + } + + /** + * @param string|array $name name or names of the instances to get + * + * @return GeneratorInstance[] all definitions discovered recursively + */ + public function getInjectedDefinitions($name) + { + $names = (array) $name; + $visited = array(); + + foreach ($names as $name) { + //var_dump(array(__METHOD__ . ':' . __LINE__ => $name)); + $this->doGetInjectedDefinitions($name, $visited); + } + + return $visited; + } + + /** + * @param string $name of the instances to get + * @param array $visited the array where discovered instance definitions will be stored + */ + protected function doGetInjectedDefinitions($name, array &$visited) + { + if (isset($visited[$name])) { + return; + } + + $visited[$name] = $this->proxy->get($name); + + foreach ($visited[$name]->getParams() as $param) { + if ($param instanceof GeneratorInstance) { + /* @var $param GeneratorInstance */ + $this->doGetInjectedDefinitions($param->getName(), $visited); + } + } + } +} diff --git a/src/OcraDiCompiler/Exception/InvalidArgumentException.php b/src/OcraDiCompiler/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..583a30a --- /dev/null +++ b/src/OcraDiCompiler/Exception/InvalidArgumentException.php @@ -0,0 +1,31 @@ + + * @license MIT + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/src/OcraDiCompiler/Generator/DiProxyGenerator.php b/src/OcraDiCompiler/Generator/DiProxyGenerator.php new file mode 100644 index 0000000..2e7a9c0 --- /dev/null +++ b/src/OcraDiCompiler/Generator/DiProxyGenerator.php @@ -0,0 +1,404 @@ + + * @license MIT + */ +class DiProxyGenerator +{ + /** + * @var Dumper + */ + protected $dumper; + + /** + * @var string + */ + protected $containerClass = 'CompiledDiContainer'; + + /** + * @var string + */ + protected $namespace; + + /** + * @var string + */ + protected $filename; + + /** + * @var ClassGenerator + */ + protected $classGenerator; + + /** + * @var FileGenerator + */ + protected $fileGenerator; + + /** + * Constructor + * + * @param Dumper $dumper + */ + public function __construct(Dumper $dumper) + { + $this->dumper = $dumper; + $this->classGenerator = new ClassGenerator(); + $this->fileGenerator = new FileGenerator(); + } + + /** + * Set the class name for the generated service locator container + * + * @param string $name + * @return DiProxyGenerator + */ + public function setContainerClass($name) + { + $this->containerClass = $name; + return $this; + } + + /** + * Get the class name for the generated service locator container + * + * @return string + */ + public function getContainerClass() + { + return $this->containerClass; + } + + /** + * Set the namespace to use for the generated class file + * + * @param string $namespace + * @return DiProxyGenerator + */ + public function setNamespace($namespace) + { + $this->namespace = $namespace; + return $this; + } + + /** + * Get the namespace to use for the generated class file + * + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * Set filename to use for the generated class file + * + * @param string $filename + * @return DiProxyGenerator + */ + public function setFilename($filename) + { + $this->filename = $filename; + return $this; + } + + /** + * Get filename to use for the generated class file + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + + /** + * Get class generator + * + * @return \Zend\Code\Generator\ClassGenerator + */ + public function getClassGenerator() + { + return $this->classGenerator; + } + + /** + * Get file generator + * + * @return FileGenerator + */ + public function getFileGenerator() + { + return $this->fileGenerator; + } + + /** + * Compiles a Di Definitions to a in a service locator, that extends Zend\Di\Di and writes it to disk + * + * It uses Zend\Code\Generator\FileGenerator + * + * @return FileGenerator + * @throws InvalidArgumentException + */ + public function compile() + { + $indent = ' '; + $caseStatements = array(); + $getters = array(); + $instances = $this->dumper->getAllInjectedDefinitions(); + + /* @var $instance GeneratorInstance */ + foreach ($instances as $name => $instance) { + $getter = $this->normalizeAlias($name); + $constructor = $instance->getConstructor(); + $instantiatorParams = $this->buildParams($instance->getParams()); + + if ('__construct' !== $constructor) { + // Constructor callback + if (is_callable($constructor)) { + $callback = $constructor; + + if (is_array($callback)) { + $class = (is_object($callback[0])) ? get_class($callback[0]) : $callback[0]; + $method = $callback[1]; + } elseif (is_string($callback) && strpos($callback, '::') !== false) { + list($class, $method) = explode('::', $callback, 2); + } + + $callback = var_export(array($class, $method), true); + + if (count($instantiatorParams)) { + $creation = sprintf('$object = call_user_func(%s, %s);', $callback, implode(', ', $instantiatorParams)); + } else { + $creation = sprintf('$object = call_user_func(%s);', $callback); + } + } else if (is_string($constructor) && strpos($constructor, '->') !== false) { + list($class, $method) = explode('->', $constructor, 2); + + if (!class_exists($class)) { + throw new InvalidArgumentException('No class found: ' . $class); + } + + $factoryGetter = $this->normalizeAlias($class); + + if (count($instantiatorParams)) { + $creation = sprintf('$object = $this->' . $factoryGetter . '()->%s(%s);', $method, implode(', ', $instantiatorParams)); + } else { + $creation = sprintf('$object = $this->' . $factoryGetter . '()->%s();', $method); + } + } else { + throw new InvalidArgumentException('Invalid instantiator supplied for class: ' . $name); + } + } else { + $className = '\\' . trim($this->reduceAlias($name), '\\'); + + if (count($instantiatorParams)) { + $creation = sprintf('$object = new %s(%s);', $className, implode(', ', $instantiatorParams)); + } else { + $creation = sprintf('$object = new %s();', $className); + } + } + + + // Create method call code + if ($instance->isShared()) { + $creation .= "\n\nif (\$isShared) {\n" . $indent + . '$this->instanceManager->addSharedInstance($object, \'' . $instance->getName() . '\');' + . "\n}\n"; + } + $methods = ''; + + foreach ($instance->getMethods() as $methodData) { + $methodName = $methodData['method']; + $methodParams = $methodData['params']; + // Create method parameter representation + $params = $this->buildParams($methodParams); + + if (count($params)) { + $methods .= sprintf("\$object->%s(%s);\n", $methodName, implode(', ', $params)); + } + } + + $storage = ''; + + // Start creating getter + $getterBody = ''; + + // Creation and method calls + $getterBody .= sprintf("%s\n", $creation); + $getterBody .= $methods; + + // Stored service + $getterBody .= $storage; + + // End getter body + $getterBody .= "return \$object;\n"; + + $getterDef = new MethodGenerator(); + $getterDef->setName($getter); + $getterDef->setParameter('isShared'); + $getterDef->setVisibility(MethodGenerator::VISIBILITY_PROTECTED); + $getterDef->setBody($getterBody); + $getters[] = $getterDef; + + // Build case statement and store + $statement = ''; + $statement .= sprintf("%scase '%s':\n", $indent, $name); + $statement .= sprintf("%sreturn \$this->%s(%s);\n", str_repeat($indent, 2), $getter, '$isShared'); + + $caseStatements[] = $statement; + } + + // Build switch statement + $switch = sprintf( + "if (%s) {\n%sreturn parent::newInstance(%s, %s, %s);\n}\n", + '$params', + $indent, + '$name', + '$params', + '$isShared' + ); + $switch .= sprintf( + "switch (%s) {\n%s\n", '$name', implode("\n", $caseStatements) + ); + $switch .= sprintf( + "%sdefault:\n%sreturn parent::newInstance(%s, %s, %s);\n", + $indent, + str_repeat($indent, 2), + '$name', + '$params', + '$isShared' + ); + $switch .= "}\n\n"; + + // Build newInstance() method + $nameParam = new ParameterGenerator(); + $nameParam->setName('name'); + + $paramsParam = new ParameterGenerator(); + $paramsParam + ->setName('params') + ->setType('array') + ->setDefaultValue(array()); + + $isSharedParam = new ParameterGenerator(); + $isSharedParam + ->setName('isShared') + ->setDefaultValue(true); + + $get = new MethodGenerator(); + $get->setName('newInstance'); + $get->setParameters(array( + $nameParam, + $paramsParam, + $isSharedParam, + )); + $get->setBody($switch); + + // Create class code generation object + $container = $this->getClassGenerator(); + $container + ->setName($this->containerClass) + ->setExtendedClass('Di') + ->addMethods(array($get)) + ->addMEthods($getters); + + // Create PHP file code generation object + $classFile = $this->getFileGenerator(); + + $classFile->setClass($container); + $classFile->setUse('Zend\Di\Di'); + + if (null !== $this->namespace) { + $classFile->setNamespace($this->namespace); + } + + return $classFile; + } + + /** + * Reduce aliases + * + * @param string $name + * @return string + */ + protected function reduceAlias($name) + { + $aliases = $this->dumper->getAliases(); + + if (isset($aliases[$name])) { + return $this->reduceAlias($aliases[$name]); + } + + return $name; + } + + /** + * Normalize an alias to a new instance method name + * + * @param string $alias + * @return string + */ + protected function normalizeAlias($alias) + { + $normalized = preg_replace('/[^a-zA-Z0-9]/', ' ', $alias); + $normalized = 'new' . str_replace(' ', '', ucwords($normalized)); + return $normalized; + } + + /** + * Generates parameter strings to be used as injections, replacing reference parameters with their respective + * getters + * + * @param array $params + * @return array + */ + protected function buildParams(array $params) + { + $normalizedParameters = array(); + + foreach ($params as $parameter) { + if ($parameter instanceof GeneratorInstance) { + /* @var $parameter GeneratorInstance */ + $normalizedParameters[] = sprintf('$this->get(%s)', '\'' . $parameter->getName() . '\''); + } else { + $normalizedParameters[] = var_export($parameter, true); + } + } + + return $normalizedParameters; + } +} diff --git a/src/OcraDiCompiler/Module.php b/src/OcraDiCompiler/Module.php new file mode 100644 index 0000000..be0a913 --- /dev/null +++ b/src/OcraDiCompiler/Module.php @@ -0,0 +1,100 @@ + + * @license MIT + */ +class Module implements BootstrapListenerInterface, ServiceProviderInterface, AutoloaderProviderInterface +{ + /** + * {@inheritDoc} + */ + public function onBootstrap(Event $e) + { + // @todo replace Di factory and return early + + /* @var $application \Zend\Mvc\ApplicationInterface */ + $application = $e->getTarget(); + $sm = $application->getServiceManager(); + if ($sm->has('Di')) { + $di = $sm->get('Di'); + + if (!$di instanceof Di) { + // @todo throw? + return; + } + + $this->compileDi($di); + } + } + + /** + * {@inheritDoc} + */ + public function getServiceConfiguration() + { + return array( + // @todo custom factory for Di + ); + } + + /** + * {@inheritDoc} + */ + public function getAutoloaderConfig() + { + return array( + 'Zend\Loader\StandardAutoloader' => array( + StandardAutoloader::LOAD_NS => array( + __NAMESPACE__ => __DIR__, + ), + ), + ); + } + + /** + * Writes Di definitions to a file + * + * @param Di $di + */ + protected function compileDi(Di $di) + { + $generator = new DiProxyGenerator(new Dumper($di)); + $fileGenerator = $generator->compile(); + $fileGenerator->setFilename('data/CompiledDi.php'); + $fileGenerator->write(); + } +} \ No newline at end of file