torrentpier-lts/library/Zend/Di/ServiceLocator/Generator.php

343 lines
12 KiB
PHP
Raw Normal View History

<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
2023-04-01 09:03:34 +03:00
* @copyright Copyright (c) 2005-2015 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;
}
}