%PDF- %PDF-
Direktori : /www/old2/_music/diplomka/diplomka/src/API/libs/Nette/DI/ |
Current File : /www/old2/_music/diplomka/diplomka/src/API/libs/Nette/DI/ContainerBuilder.php |
<?php /** * This file is part of the Nette Framework (http://nette.org) * * Copyright (c) 2004 David Grudl (http://davidgrudl.com) * * For the full copyright and license information, please view * the file license.txt that was distributed with this source code. */ namespace Nette\DI; use Nette, Nette\Utils\Validators, Nette\Utils\Strings, Nette\Reflection, Nette\Utils\PhpGenerator\Helpers as PhpHelpers, Nette\Utils\PhpGenerator\PhpLiteral; /** * Basic container builder. * * @author David Grudl * @property-read ServiceDefinition[] $definitions * @property-read array $dependencies */ class ContainerBuilder extends Nette\Object { const CREATED_SERVICE = 'self', THIS_CONTAINER = 'container'; /** @var array %param% will be expanded */ public $parameters = array(); /** @var ServiceDefinition[] */ private $definitions = array(); /** @var array for auto-wiring */ private $classes; /** @var array of file names */ private $dependencies = array(); /** * Adds new service definition. The expressions %param% and @service will be expanded. * @param string * @return ServiceDefinition */ public function addDefinition($name) { if (!is_string($name) || !$name) { // builder is not ready for falsy names such as '0' throw new Nette\InvalidArgumentException("Service name must be a non-empty string, " . gettype($name) . " given."); } elseif (isset($this->definitions[$name])) { throw new Nette\InvalidStateException("Service '$name' has already been added."); } return $this->definitions[$name] = new ServiceDefinition; } /** * Removes the specified service definition. * @param string * @return void */ public function removeDefinition($name) { unset($this->definitions[$name]); } /** * Gets the service definition. * @param string * @return ServiceDefinition */ public function getDefinition($name) { if (!isset($this->definitions[$name])) { throw new MissingServiceException("Service '$name' not found."); } return $this->definitions[$name]; } /** * Gets all service definitions. * @return array */ public function getDefinitions() { return $this->definitions; } /** * Does the service definition exist? * @param string * @return bool */ public function hasDefinition($name) { return isset($this->definitions[$name]); } /********************* class resolving ****************d*g**/ /** * Resolves service name by type. * @param string class or interface * @return string service name or NULL * @throws ServiceCreationException */ public function getByType($class) { $lower = ltrim(strtolower($class), '\\'); if (!isset($this->classes[$lower])) { return; } elseif (count($this->classes[$lower]) === 1) { return $this->classes[$lower][0]; } else { throw new ServiceCreationException("Multiple services of type $class found: " . implode(', ', $this->classes[$lower])); } } /** * Gets the service objects of the specified tag. * @param string * @return array of [service name => tag attributes] */ public function findByTag($tag) { $found = array(); foreach ($this->definitions as $name => $def) { if (isset($def->tags[$tag]) && $def->shared) { $found[$name] = $def->tags[$tag]; } } return $found; } /** * Creates a list of arguments using autowiring. * @return array */ public function autowireArguments($class, $method, array $arguments) { $rc = Reflection\ClassType::from($class); if (!$rc->hasMethod($method)) { if (!Nette\Utils\Validators::isList($arguments)) { throw new ServiceCreationException("Unable to pass specified arguments to $class::$method()."); } return $arguments; } $rm = $rc->getMethod($method); if ($rm->isAbstract() || !$rm->isPublic()) { throw new ServiceCreationException("$rm is not callable."); } $this->addDependency($rm->getFileName()); return Helpers::autowireArguments($rm, $arguments, $this); } /** * Generates $dependencies, $classes and expands and normalize class names. * @return array */ public function prepareClassList() { // complete class-factory pairs; expand classes foreach ($this->definitions as $name => $def) { if ($def->class === self::CREATED_SERVICE || ($def->factory && $def->factory->entity === self::CREATED_SERVICE)) { $def->class = $name; $def->internal = TRUE; if ($def->factory && $def->factory->entity === self::CREATED_SERVICE) { $def->factory->entity = $def->class; } unset($this->definitions[$name]); $this->definitions['_anonymous_' . str_replace('\\', '_', strtolower(trim($name, '\\')))] = $def; } if ($def->class) { $def->class = $this->expand($def->class); if (!$def->factory) { $def->factory = new Statement($def->class); } } elseif (!$def->factory) { throw new ServiceCreationException("Class and factory are missing in service '$name' definition."); } } // check if services are instantiable foreach ($this->definitions as $name => $def) { $factory = $this->normalizeEntity($this->expand($def->factory->entity)); if (is_string($factory) && preg_match('#^[\w\\\\]+\z#', $factory) && $factory !== self::CREATED_SERVICE) { if (!class_exists($factory) || !Reflection\ClassType::from($factory)->isInstantiable()) { throw new Nette\InvalidStateException("Class $factory used in service '$name' has not been found or is not instantiable."); } } } // complete classes $this->classes = FALSE; foreach ($this->definitions as $name => $def) { $this->resolveClass($name); } // build auto-wiring list $this->classes = array(); foreach ($this->definitions as $name => $def) { if (!$def->class) { continue; } if (!class_exists($def->class) && !interface_exists($def->class)) { throw new Nette\InvalidStateException("Class $def->class has not been found."); } $def->class = Reflection\ClassType::from($def->class)->getName(); if ($def->autowired) { foreach (class_parents($def->class) + class_implements($def->class) + array($def->class) as $parent) { $this->classes[strtolower($parent)][] = (string) $name; } } } foreach ($this->classes as $class => $foo) { $this->addDependency(Reflection\ClassType::from($class)->getFileName()); } } private function resolveClass($name, $recursive = array()) { if (isset($recursive[$name])) { throw new Nette\InvalidArgumentException('Circular reference detected for services: ' . implode(', ', array_keys($recursive)) . '.'); } $recursive[$name] = TRUE; $def = $this->definitions[$name]; $factory = $this->normalizeEntity($this->expand($def->factory->entity)); if ($def->class) { return $def->class; } elseif (is_array($factory)) { // method calling if ($service = $this->getServiceName($factory[0])) { if (Strings::contains($service, '\\')) { // @\Class throw new ServiceCreationException("Unable resolve class name for service '$name'."); } $factory[0] = $this->resolveClass($service, $recursive); if (!$factory[0]) { return; } } $factory = new Nette\Callback($factory); if (!$factory->isCallable()) { throw new Nette\InvalidStateException("Factory '$factory' is not callable."); } try { $reflection = $factory->toReflection(); $def->class = preg_replace('#[|\s].*#', '', $reflection->getAnnotation('return')); if ($def->class && !class_exists($def->class) && $def->class[0] !== '\\' && $reflection instanceof \ReflectionMethod) { $def->class = $reflection->getDeclaringClass()->getNamespaceName() . '\\' . $def->class; } } catch (\ReflectionException $e) { } } elseif ($service = $this->getServiceName($factory)) { // alias or factory if (Strings::contains($service, '\\')) { // @\Class $def->autowired = FALSE; return $def->class = $service; } if ($this->definitions[$service]->shared) { $def->autowired = FALSE; } return $def->class = $this->resolveClass($service, $recursive); } else { return $def->class = $factory; // class name } } /** * Adds a file to the list of dependencies. * @return ContainerBuilder provides a fluent interface */ public function addDependency($file) { $this->dependencies[$file] = TRUE; return $this; } /** * Returns the list of dependent files. * @return array */ public function getDependencies() { unset($this->dependencies[FALSE]); return array_keys($this->dependencies); } /********************* code generator ****************d*g**/ /** * Generates PHP class. * @return Nette\Utils\PhpGenerator\ClassType */ public function generateClass($parentClass = 'Nette\DI\Container') { unset($this->definitions[self::THIS_CONTAINER]); $this->addDefinition(self::THIS_CONTAINER)->setClass($parentClass); $this->prepareClassList(); $class = new Nette\Utils\PhpGenerator\ClassType('Container'); $class->addExtend($parentClass); $class->addMethod('__construct') ->addBody('parent::__construct(?);', array($this->expand($this->parameters))); $classes = $class->addProperty('classes', array()); foreach ($this->classes as $name => $foo) { try { $classes->value[$name] = $this->getByType($name); } catch (ServiceCreationException $e) { $classes->value[$name] = new PhpLiteral('FALSE, //' . strstr($e->getMessage(), ':')); } } $definitions = $this->definitions; ksort($definitions); $meta = $class->addProperty('meta', array()); foreach ($definitions as $name => $def) { if ($def->shared) { foreach ($this->expand($def->tags) as $tag => $value) { $meta->value[$name][Container::TAGS][$tag] = $value; } } } foreach ($definitions as $name => $def) { try { $name = (string) $name; $type = $def->class ?: 'object'; $methodName = Container::getMethodName($name, $def->shared); if (!PhpHelpers::isIdentifier($methodName)) { throw new ServiceCreationException('Name contains invalid characters.'); } if ($def->shared && !$def->internal && PhpHelpers::isIdentifier($name)) { $class->addDocument("@property $type \$$name"); } $method = $class->addMethod($methodName) ->addDocument("@return $type") ->setVisibility($def->shared || $def->internal ? 'protected' : 'public') ->setBody($name === self::THIS_CONTAINER ? 'return $this;' : $this->generateService($name)); foreach ($this->expand($def->parameters) as $k => $v) { $tmp = explode(' ', is_int($k) ? $v : $k); $param = is_int($k) ? $method->addParameter(end($tmp)) : $method->addParameter(end($tmp), $v); if (isset($tmp[1])) { $param->setTypeHint($tmp[0]); } } } catch (\Exception $e) { throw new ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e); } } return $class; } /** * Generates body of service method. * @return string */ private function generateService($name) { $def = $this->definitions[$name]; $parameters = $this->parameters; foreach ($this->expand($def->parameters) as $k => $v) { $v = explode(' ', is_int($k) ? $v : $k); $parameters[end($v)] = new PhpLiteral('$' . end($v)); } $code = '$service = ' . $this->formatStatement(Helpers::expand($def->factory, $parameters, TRUE)) . ";\n"; $entity = $this->normalizeEntity($def->factory->entity); if ($def->class && $def->class !== $entity && !$this->getServiceName($entity)) { $code .= PhpHelpers::formatArgs("if (!\$service instanceof $def->class) {\n" . "\tthrow new Nette\\UnexpectedValueException(?);\n}\n", array("Unable to create service '$name', value returned by factory is not $def->class type.") ); } foreach ((array) $def->setup as $setup) { $setup = Helpers::expand($setup, $parameters, TRUE); if (is_string($setup->entity) && strpbrk($setup->entity, ':@?') === FALSE) { // auto-prepend @self $setup->entity = array("@$name", $setup->entity); } $code .= $this->formatStatement($setup, $name) . ";\n"; } return $code .= 'return $service;'; } /** * Formats PHP code for class instantiating, function calling or property setting in PHP. * @return string * @internal */ public function formatStatement(Statement $statement, $self = NULL) { $entity = $this->normalizeEntity($statement->entity); $arguments = $statement->arguments; if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal return $this->formatPhp($entity, $arguments, $self); } elseif ($service = $this->getServiceName($entity)) { // factory calling or service retrieving if ($this->definitions[$service]->shared) { if ($arguments) { throw new ServiceCreationException("Unable to call service '$entity'."); } return $this->formatPhp('$this->getService(?)', array($service)); } $params = array(); foreach ($this->definitions[$service]->parameters as $k => $v) { $params[] = preg_replace('#\w+\z#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . PhpHelpers::dump($v)); } $rm = new Reflection\GlobalFunction(create_function(implode(', ', $params), '')); $arguments = Helpers::autowireArguments($rm, $arguments, $this); return $this->formatPhp('$this->?(?*)', array(Container::getMethodName($service, FALSE), $arguments), $self); } elseif ($entity === 'not') { // operator return $this->formatPhp('!?', array($arguments[0])); } elseif (is_string($entity)) { // class name if ($constructor = Reflection\ClassType::from($entity)->getConstructor()) { $this->addDependency($constructor->getFileName()); $arguments = Helpers::autowireArguments($constructor, $arguments, $this); } elseif ($arguments) { throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor."); } return $this->formatPhp("new $entity" . ($arguments ? '(?*)' : ''), array($arguments), $self); } elseif (!Validators::isList($entity) || count($entity) !== 2) { throw new Nette\InvalidStateException("Expected class, method or property, " . PhpHelpers::dump($entity) . " given."); } elseif ($entity[0] === '') { // globalFunc return $this->formatPhp("$entity[1](?*)", array($arguments), $self); } elseif (Strings::contains($entity[1], '$')) { // property setter Validators::assert($arguments, 'list:1', "setup arguments for '" . Nette\Callback::create($entity) . "'"); if ($this->getServiceName($entity[0], $self)) { return $this->formatPhp('?->? = ?', array($entity[0], substr($entity[1], 1), $arguments[0]), $self); } else { return $this->formatPhp($entity[0] . '::$? = ?', array(substr($entity[1], 1), $arguments[0]), $self); } } elseif ($service = $this->getServiceName($entity[0], $self)) { // service method if ($this->definitions[$service]->class) { $arguments = $this->autowireArguments($this->definitions[$service]->class, $entity[1], $arguments); } return $this->formatPhp('?->?(?*)', array($entity[0], $entity[1], $arguments), $self); } else { // static method $arguments = $this->autowireArguments($entity[0], $entity[1], $arguments); return $this->formatPhp("$entity[0]::$entity[1](?*)", array($arguments), $self); } } /** * Formats PHP statement. * @return string */ public function formatPhp($statement, $args, $self = NULL) { $that = $this; array_walk_recursive($args, function(&$val) use ($self, $that) { list($val) = $that->normalizeEntity(array($val)); if ($val instanceof Statement) { $val = new PhpLiteral($that->formatStatement($val, $self)); } elseif ($val === '@' . ContainerBuilder::THIS_CONTAINER) { $val = new PhpLiteral('$this'); } elseif ($service = $that->getServiceName($val, $self)) { $val = $service === $self ? '$service' : $that->formatStatement(new Statement($val)); $val = new PhpLiteral($val); } }); return PhpHelpers::formatArgs($statement, $args); } /** * Expands %placeholders% in strings (recursive). * @return mixed */ public function expand($value) { return Helpers::expand($value, $this->parameters, TRUE); } /** @internal */ public function normalizeEntity($entity) { if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) { // Class::method -> [Class, method] $entity = explode('::', $entity); } if (is_array($entity) && $entity[0] instanceof ServiceDefinition) { // [ServiceDefinition, ...] -> [@serviceName, ...] $tmp = array_keys($this->definitions, $entity[0], TRUE); $entity[0] = "@$tmp[0]"; } elseif ($entity instanceof ServiceDefinition) { // ServiceDefinition -> @serviceName $tmp = array_keys($this->definitions, $entity, TRUE); $entity = "@$tmp[0]"; } elseif (is_array($entity) && $entity[0] === $this) { // [$this, ...] -> [@container, ...] $entity[0] = '@' . ContainerBuilder::THIS_CONTAINER; } return $entity; // Class, @service, [Class, member], [@service, member], [, globalFunc] } /** * Converts @service or @\Class -> service name and checks its existence. * @return string of FALSE, if argument is not service name */ public function getServiceName($arg, $self = NULL) { if (!is_string($arg) || !preg_match('#^@[\w\\\\.].*\z#', $arg)) { return FALSE; } $service = substr($arg, 1); if ($service === self::CREATED_SERVICE) { $service = $self; } if (Strings::contains($service, '\\')) { if ($this->classes === FALSE) { // may be disabled by prepareClassList return $service; } $res = $this->getByType($service); if (!$res) { throw new ServiceCreationException("Reference to missing service of type $service."); } return $res; } if (!isset($this->definitions[$service])) { throw new ServiceCreationException("Reference to missing service '$service'."); } return $service; } }