mirror of
https://github.com/torrentpier/torrentpier-lts.git
synced 2025-03-01 15:21:02 +03:00
343 lines
12 KiB
PHP
343 lines
12 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* Zend Framework (http://framework.zend.com/)
|
||
|
*
|
||
|
* @link http://github.com/zendframework/zf2 for the canonical source repository
|
||
|
* @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
|
||
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
||
|
*/
|
||
|
|
||
|
namespace Zend\Di\ServiceLocator;
|
||
|
|
||
|
use Zend\Code\Generator\ClassGenerator;
|
||
|
use Zend\Code\Generator\FileGenerator;
|
||
|
use Zend\Code\Generator\MethodGenerator;
|
||
|
use Zend\Code\Generator\ParameterGenerator;
|
||
|
use Zend\Di\Di;
|
||
|
use Zend\Di\Exception;
|
||
|
|
||
|
/**
|
||
|
* Generator that creates the body of a service locator that can emulate the logic of the given Zend\Di\Di instance
|
||
|
* without class definitions
|
||
|
*/
|
||
|
class Generator
|
||
|
{
|
||
|
protected $containerClass = 'ApplicationContext';
|
||
|
|
||
|
/** @var DependencyInjectorProxy */
|
||
|
protected $injector;
|
||
|
|
||
|
/**
|
||
|
* @var null|string
|
||
|
*/
|
||
|
protected $namespace;
|
||
|
|
||
|
/**
|
||
|
* Constructor
|
||
|
*
|
||
|
* Requires a DependencyInjection manager on which to operate.
|
||
|
*
|
||
|
* @param Di $injector
|
||
|
*/
|
||
|
public function __construct(Di $injector)
|
||
|
{
|
||
|
$this->injector = new DependencyInjectorProxy($injector);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the class name for the generated service locator container
|
||
|
*
|
||
|
* @param string $name
|
||
|
* @return Generator
|
||
|
*/
|
||
|
public function setContainerClass($name)
|
||
|
{
|
||
|
$this->containerClass = $name;
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the namespace to use for the generated class file
|
||
|
*
|
||
|
* @param string $namespace
|
||
|
* @return Generator
|
||
|
*/
|
||
|
public function setNamespace($namespace)
|
||
|
{
|
||
|
$this->namespace = $namespace;
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Construct, configure, and return a PHP class file code generation object
|
||
|
*
|
||
|
* Creates a Zend\Code\Generator\FileGenerator object that has
|
||
|
* created the specified class and service locator methods.
|
||
|
*
|
||
|
* @param null|string $filename
|
||
|
* @throws \Zend\Di\Exception\RuntimeException
|
||
|
* @return FileGenerator
|
||
|
*/
|
||
|
public function getCodeGenerator($filename = null)
|
||
|
{
|
||
|
$injector = $this->injector;
|
||
|
$im = $injector->instanceManager();
|
||
|
$indent = ' ';
|
||
|
$aliases = $this->reduceAliases($im->getAliases());
|
||
|
$caseStatements = array();
|
||
|
$getters = array();
|
||
|
$definitions = $injector->definitions();
|
||
|
|
||
|
$fetched = array_unique(array_merge($definitions->getClasses(), $im->getAliases()));
|
||
|
|
||
|
foreach ($fetched as $name) {
|
||
|
$getter = $this->normalizeAlias($name);
|
||
|
$meta = $injector->get($name);
|
||
|
$params = $meta->getParams();
|
||
|
|
||
|
// Build parameter list for instantiation
|
||
|
foreach ($params as $key => $param) {
|
||
|
if (null === $param || is_scalar($param) || is_array($param)) {
|
||
|
$string = var_export($param, 1);
|
||
|
if (strstr($string, '::__set_state(')) {
|
||
|
throw new Exception\RuntimeException('Arguments in definitions may not contain objects');
|
||
|
}
|
||
|
$params[$key] = $string;
|
||
|
} elseif ($param instanceof GeneratorInstance) {
|
||
|
/* @var $param GeneratorInstance */
|
||
|
$params[$key] = sprintf('$this->%s()', $this->normalizeAlias($param->getName()));
|
||
|
} else {
|
||
|
$message = sprintf('Unable to use object arguments when building containers. Encountered with "%s", parameter of type "%s"', $name, get_class($param));
|
||
|
throw new Exception\RuntimeException($message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Strip null arguments from the end of the params list
|
||
|
$reverseParams = array_reverse($params, true);
|
||
|
foreach ($reverseParams as $key => $param) {
|
||
|
if ('NULL' === $param) {
|
||
|
unset($params[$key]);
|
||
|
continue;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Create instantiation code
|
||
|
$constructor = $meta->getConstructor();
|
||
|
if ('__construct' != $constructor) {
|
||
|
// Constructor callback
|
||
|
$callback = var_export($constructor, 1);
|
||
|
if (strstr($callback, '::__set_state(')) {
|
||
|
throw new Exception\RuntimeException('Unable to build containers that use callbacks requiring object instances');
|
||
|
}
|
||
|
if (count($params)) {
|
||
|
$creation = sprintf('$object = call_user_func(%s, %s);', $callback, implode(', ', $params));
|
||
|
} else {
|
||
|
$creation = sprintf('$object = call_user_func(%s);', $callback);
|
||
|
}
|
||
|
} else {
|
||
|
// Normal instantiation
|
||
|
$className = '\\' . ltrim($name, '\\');
|
||
|
$creation = sprintf('$object = new %s(%s);', $className, implode(', ', $params));
|
||
|
}
|
||
|
|
||
|
// Create method call code
|
||
|
$methods = '';
|
||
|
foreach ($meta->getMethods() as $methodData) {
|
||
|
if (!isset($methodData['name']) && !isset($methodData['method'])) {
|
||
|
continue;
|
||
|
}
|
||
|
$methodName = isset($methodData['name']) ? $methodData['name'] : $methodData['method'];
|
||
|
$methodParams = $methodData['params'];
|
||
|
|
||
|
// Create method parameter representation
|
||
|
foreach ($methodParams as $key => $param) {
|
||
|
if (null === $param || is_scalar($param) || is_array($param)) {
|
||
|
$string = var_export($param, 1);
|
||
|
if (strstr($string, '::__set_state(')) {
|
||
|
throw new Exception\RuntimeException('Arguments in definitions may not contain objects');
|
||
|
}
|
||
|
$methodParams[$key] = $string;
|
||
|
} elseif ($param instanceof GeneratorInstance) {
|
||
|
$methodParams[$key] = sprintf('$this->%s()', $this->normalizeAlias($param->getName()));
|
||
|
} else {
|
||
|
$message = sprintf('Unable to use object arguments when generating method calls. Encountered with class "%s", method "%s", parameter of type "%s"', $name, $methodName, get_class($param));
|
||
|
throw new Exception\RuntimeException($message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Strip null arguments from the end of the params list
|
||
|
$reverseParams = array_reverse($methodParams, true);
|
||
|
foreach ($reverseParams as $key => $param) {
|
||
|
if ('NULL' === $param) {
|
||
|
unset($methodParams[$key]);
|
||
|
continue;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$methods .= sprintf("\$object->%s(%s);\n", $methodName, implode(', ', $methodParams));
|
||
|
}
|
||
|
|
||
|
// Generate caching statement
|
||
|
$storage = '';
|
||
|
if ($im->hasSharedInstance($name, $params)) {
|
||
|
$storage = sprintf("\$this->services['%s'] = \$object;\n", $name);
|
||
|
}
|
||
|
|
||
|
// Start creating getter
|
||
|
$getterBody = '';
|
||
|
|
||
|
// Create fetch of stored service
|
||
|
if ($im->hasSharedInstance($name, $params)) {
|
||
|
$getterBody .= sprintf("if (isset(\$this->services['%s'])) {\n", $name);
|
||
|
$getterBody .= sprintf("%sreturn \$this->services['%s'];\n}\n\n", $indent, $name);
|
||
|
}
|
||
|
|
||
|
// 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->setBody($getterBody);
|
||
|
$getters[] = $getterDef;
|
||
|
|
||
|
// Get cases for case statements
|
||
|
$cases = array($name);
|
||
|
if (isset($aliases[$name])) {
|
||
|
$cases = array_merge($aliases[$name], $cases);
|
||
|
}
|
||
|
|
||
|
// Build case statement and store
|
||
|
$statement = '';
|
||
|
foreach ($cases as $value) {
|
||
|
$statement .= sprintf("%scase '%s':\n", $indent, $value);
|
||
|
}
|
||
|
$statement .= sprintf("%sreturn \$this->%s();\n", str_repeat($indent, 2), $getter);
|
||
|
|
||
|
$caseStatements[] = $statement;
|
||
|
}
|
||
|
|
||
|
// Build switch statement
|
||
|
$switch = sprintf("switch (%s) {\n%s\n", '$name', implode("\n", $caseStatements));
|
||
|
$switch .= sprintf("%sdefault:\n%sreturn parent::get(%s, %s);\n", $indent, str_repeat($indent, 2), '$name', '$params');
|
||
|
$switch .= "}\n\n";
|
||
|
|
||
|
// Build get() method
|
||
|
$nameParam = new ParameterGenerator();
|
||
|
$nameParam->setName('name');
|
||
|
$paramsParam = new ParameterGenerator();
|
||
|
$paramsParam->setName('params')
|
||
|
->setType('array')
|
||
|
->setDefaultValue(array());
|
||
|
|
||
|
$get = new MethodGenerator();
|
||
|
$get->setName('get');
|
||
|
$get->setParameters(array(
|
||
|
$nameParam,
|
||
|
$paramsParam,
|
||
|
));
|
||
|
$get->setBody($switch);
|
||
|
|
||
|
// Create getters for aliases
|
||
|
$aliasMethods = array();
|
||
|
foreach ($aliases as $class => $classAliases) {
|
||
|
foreach ($classAliases as $alias) {
|
||
|
$aliasMethods[] = $this->getCodeGenMethodFromAlias($alias, $class);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create class code generation object
|
||
|
$container = new ClassGenerator();
|
||
|
$container->setName($this->containerClass)
|
||
|
->setExtendedClass('ServiceLocator')
|
||
|
->addMethodFromGenerator($get)
|
||
|
->addMethods($getters)
|
||
|
->addMethods($aliasMethods);
|
||
|
|
||
|
// Create PHP file code generation object
|
||
|
$classFile = new FileGenerator();
|
||
|
$classFile->setUse('Zend\Di\ServiceLocator')
|
||
|
->setClass($container);
|
||
|
|
||
|
if (null !== $this->namespace) {
|
||
|
$classFile->setNamespace($this->namespace);
|
||
|
}
|
||
|
|
||
|
if (null !== $filename) {
|
||
|
$classFile->setFilename($filename);
|
||
|
}
|
||
|
|
||
|
return $classFile;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reduces aliases
|
||
|
*
|
||
|
* Takes alias list and reduces it to a 2-dimensional array of
|
||
|
* class names pointing to an array of aliases that resolve to
|
||
|
* it.
|
||
|
*
|
||
|
* @param array $aliasList
|
||
|
* @return array
|
||
|
*/
|
||
|
protected function reduceAliases(array $aliasList)
|
||
|
{
|
||
|
$reduced = array();
|
||
|
$aliases = array_keys($aliasList);
|
||
|
foreach ($aliasList as $alias => $service) {
|
||
|
if (in_array($service, $aliases)) {
|
||
|
do {
|
||
|
$service = $aliasList[$service];
|
||
|
} while (in_array($service, $aliases));
|
||
|
}
|
||
|
if (!isset($reduced[$service])) {
|
||
|
$reduced[$service] = array();
|
||
|
}
|
||
|
$reduced[$service][] = $alias;
|
||
|
}
|
||
|
|
||
|
return $reduced;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create a PhpMethod code generation object named after a given alias
|
||
|
*
|
||
|
* @param string $alias
|
||
|
* @param string $class Class to which alias refers
|
||
|
* @return MethodGenerator
|
||
|
*/
|
||
|
protected function getCodeGenMethodFromAlias($alias, $class)
|
||
|
{
|
||
|
$alias = $this->normalizeAlias($alias);
|
||
|
$method = new MethodGenerator();
|
||
|
$method->setName($alias);
|
||
|
$method->setBody(sprintf('return $this->get(\'%s\');', $class));
|
||
|
|
||
|
return $method;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Normalize an alias to a getter method name
|
||
|
*
|
||
|
* @param string $alias
|
||
|
* @return string
|
||
|
*/
|
||
|
protected function normalizeAlias($alias)
|
||
|
{
|
||
|
$normalized = preg_replace('/[^a-zA-Z0-9]/', ' ', $alias);
|
||
|
$normalized = 'get' . str_replace(' ', '', ucwords($normalized));
|
||
|
|
||
|
return $normalized;
|
||
|
}
|
||
|
}
|