This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
symfony/src/Symfony/Components/DependencyInjection/Builder.php

525 lines
15 KiB
PHP
Raw Normal View History

2010-01-04 14:26:20 +00:00
<?php
namespace Symfony\Components\DependencyInjection;
/*
* This file is part of the Symfony framework.
2010-01-04 14:26:20 +00:00
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Builder is a DI container that provides an interface to build the services.
*
* @package Symfony
* @subpackage Components_DependencyInjection
2010-01-04 14:26:20 +00:00
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Builder extends Container implements AnnotatedContainerInterface
2010-01-04 14:26:20 +00:00
{
protected $definitions = array();
protected $aliases = array();
protected $loading = array();
/**
* Sets a service.
*
* @param string $id The service identifier
* @param object $service The service instance
*/
public function setService($id, $service)
2010-01-04 14:26:20 +00:00
{
unset($this->definitions[$id]);
unset($this->aliases[$id]);
parent::setService($id, $service);
2010-01-04 14:26:20 +00:00
}
/**
* Returns true if the given service is defined.
*
* @param string $id The service identifier
*
* @return Boolean true if the service is defined, false otherwise
*/
public function hasService($id)
{
return isset($this->definitions[$id]) || isset($this->aliases[$id]) || parent::hasService($id);
}
/**
* Gets a service.
*
* @param string $id The service identifier
* @param int $invalidBehavior The behavior when the service does not exist
*
* @return object The associated service
*
* @throws \InvalidArgumentException if the service is not defined
* @throws \LogicException if the service has a circular reference to itself
*
* @see Reference
*/
public function getService($id, $invalidBehavior = Container::EXCEPTION_ON_INVALID_REFERENCE)
2010-01-04 14:26:20 +00:00
{
try
2010-01-04 14:26:20 +00:00
{
return parent::getService($id, Container::EXCEPTION_ON_INVALID_REFERENCE);
2010-01-04 14:26:20 +00:00
}
catch (\InvalidArgumentException $e)
{
if (isset($this->loading[$id]))
{
throw new \LogicException(sprintf('The service "%s" has a circular reference to itself.', $id));
}
if (!$this->hasDefinition($id) && isset($this->aliases[$id]))
{
return $this->getService($this->aliases[$id]);
}
try
{
$definition = $this->getDefinition($id);
}
catch (\InvalidArgumentException $e)
{
if (Container::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior)
{
return null;
}
2010-01-04 14:26:20 +00:00
throw $e;
}
2010-01-04 14:26:20 +00:00
$this->loading[$id] = true;
2010-01-04 14:26:20 +00:00
$service = $this->createService($definition, $id);
2010-01-04 14:26:20 +00:00
unset($this->loading[$id]);
2010-01-04 14:26:20 +00:00
return $service;
}
2010-01-04 14:26:20 +00:00
}
/**
* Merges a BuilderConfiguration with the current Builder configuration.
*
* Service definitions overrides the current defined ones.
*
* But for parameters, they are overridden by the current ones. It allows
* the parameters passed to the container constructor to have precedence
* over the loaded ones.
*
* $container = new Builder(array('foo' => 'bar'));
* $loader = new LoaderXXX($container);
* $loader->load('resource_name');
* $container->register('foo', new stdClass());
*
* In the above example, even if the loaded resource defines a foo
* parameter, the value will still be 'bar' as defined in the builder
* constructor.
*/
public function merge(BuilderConfiguration $configuration = null)
2010-01-04 14:26:20 +00:00
{
if (null === $configuration)
{
return;
}
2010-01-04 14:26:20 +00:00
$this->addDefinitions($configuration->getDefinitions());
$this->addAliases($configuration->getAliases());
2010-01-04 14:26:20 +00:00
$currentParameters = $this->getParameters();
foreach ($configuration->getParameters() as $key => $value)
{
$this->setParameter($key, $value);
}
$this->addParameters($currentParameters);
foreach ($this->parameters as $key => $value)
{
$this->parameters[$key] = self::resolveValue($value, $this->parameters);
}
2010-01-04 14:26:20 +00:00
}
/**
* Gets all service ids.
*
* @return array An array of all defined service ids
*/
public function getServiceIds()
2010-01-04 14:26:20 +00:00
{
return array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliases), parent::getServiceIds()));
2010-01-04 14:26:20 +00:00
}
/**
* Adds the service aliases.
*
* @param array $aliases An array of aliases
*/
public function addAliases(array $aliases)
{
foreach ($aliases as $alias => $id)
{
$this->setAlias($alias, $id);
}
}
/**
* Sets the service aliases.
*
* @param array $definitions An array of service definitions
*/
public function setAliases(array $aliases)
{
$this->aliases = array();
$this->addAliases($aliases);
}
/**
* Sets an alias for an existing service.
*
* @param string $alias The alias to create
* @param string $id The service to alias
*/
public function setAlias($alias, $id)
2010-01-04 14:26:20 +00:00
{
unset($this->definitions[$alias]);
$this->aliases[$alias] = $id;
2010-01-04 14:26:20 +00:00
}
/**
* Returns true if an alias exists under the given identifier.
*
* @param string $id The service identifier
*
* @return Boolean true if the alias exists, false otherwise
*/
public function hasAlias($id)
2010-01-04 14:26:20 +00:00
{
return array_key_exists($id, $this->aliases);
2010-01-04 14:26:20 +00:00
}
/**
* Gets all defined aliases.
*
* @return array An array of aliases
*/
public function getAliases()
2010-01-04 14:26:20 +00:00
{
return $this->aliases;
2010-01-04 14:26:20 +00:00
}
/**
* Gets an alias.
*
* @param string $id The service identifier
*
* @return string The aliased service identifier
*
* @throws \InvalidArgumentException if the alias does not exist
*/
public function getAlias($id)
{
if (!$this->hasAlias($id))
{
throw new \InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id));
}
2010-01-04 14:26:20 +00:00
return $this->aliases[$id];
}
2010-01-04 14:26:20 +00:00
/**
* Registers a service definition.
*
* This methods allows for simple registration of service definition
* with a fluid interface.
*
* @param string $id The service identifier
* @param string $class The service class
*
* @return Definition A Definition instance
*/
public function register($id, $class)
2010-01-04 14:26:20 +00:00
{
return $this->setDefinition($id, new Definition($class));
2010-01-04 14:26:20 +00:00
}
/**
* Adds the service definitions.
*
* @param Definition[] $definitions An array of service definitions
*/
public function addDefinitions(array $definitions)
2010-01-04 14:26:20 +00:00
{
foreach ($definitions as $id => $definition)
{
$this->setDefinition($id, $definition);
}
2010-01-04 14:26:20 +00:00
}
/**
* Sets the service definitions.
*
* @param array $definitions An array of service definitions
*/
public function setDefinitions(array $definitions)
{
$this->definitions = array();
$this->addDefinitions($definitions);
}
/**
* Gets all service definitions.
*
* @return array An array of Definition instances
*/
public function getDefinitions()
2010-01-04 14:26:20 +00:00
{
return $this->definitions;
}
2010-01-04 14:26:20 +00:00
/**
* Sets a service definition.
*
* @param string $id The service identifier
* @param Definition $definition A Definition instance
*/
public function setDefinition($id, Definition $definition)
{
unset($this->aliases[$id]);
2010-01-04 14:26:20 +00:00
return $this->definitions[$id] = $definition;
2010-01-04 14:26:20 +00:00
}
/**
* Returns true if a service definition exists under the given identifier.
*
* @param string $id The service identifier
*
* @return Boolean true if the service definition exists, false otherwise
*/
public function hasDefinition($id)
2010-01-04 14:26:20 +00:00
{
return array_key_exists($id, $this->definitions);
2010-01-04 14:26:20 +00:00
}
/**
* Gets a service definition.
*
* @param string $id The service identifier
*
* @return Definition A Definition instance
*
* @throws \InvalidArgumentException if the service definition does not exist
*/
public function getDefinition($id)
2010-01-04 14:26:20 +00:00
{
if (!$this->hasDefinition($id))
{
throw new \InvalidArgumentException(sprintf('The service definition "%s" does not exist.', $id));
}
2010-01-04 14:26:20 +00:00
return $this->definitions[$id];
2010-01-04 14:26:20 +00:00
}
/**
* Creates a service for a service definition.
*
* @param Definition $definition A service definition instance
* @param string $id The service identifier
*
* @return object The service described by the service definition
*
* @throws \InvalidArgumentException When configure callable is not callable
*/
protected function createService(Definition $definition, $id)
2010-01-04 14:26:20 +00:00
{
if (null !== $definition->getFile())
2010-01-04 14:26:20 +00:00
{
require_once self::resolveValue($definition->getFile(), $this->parameters);
2010-01-04 14:26:20 +00:00
}
$r = new \ReflectionClass(self::resolveValue($definition->getClass(), $this->parameters));
$arguments = $this->resolveServices(self::resolveValue($definition->getArguments(), $this->parameters));
if (null !== $definition->getConstructor())
{
$service = call_user_func_array(array(self::resolveValue($definition->getClass(), $this->parameters), $definition->getConstructor()), $arguments);
}
else
2010-01-04 14:26:20 +00:00
{
$service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
}
2010-01-04 14:26:20 +00:00
if ($definition->isShared())
{
$this->services[$id] = $service;
}
2010-01-04 14:26:20 +00:00
foreach ($definition->getMethodCalls() as $call)
{
$services = self::getServiceConditionals($call[1]);
$ok = true;
foreach ($services as $s)
{
if (!$this->hasService($s))
{
$ok = false;
break;
}
}
if ($ok)
{
call_user_func_array(array($service, $call[0]), $this->resolveServices(self::resolveValue($call[1], $this->parameters)));
}
}
if ($callable = $definition->getConfigurator())
{
if (is_array($callable) && is_object($callable[0]) && $callable[0] instanceof Reference)
{
$callable[0] = $this->getService((string) $callable[0]);
}
elseif (is_array($callable))
{
$callable[0] = self::resolveValue($callable[0], $this->parameters);
}
if (!is_callable($callable))
{
throw new \InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service)));
}
call_user_func($callable, $service);
}
return $service;
2010-01-04 14:26:20 +00:00
}
/**
* Replaces parameter placeholders (%name%) by their values.
*
* @param mixed $value A value
*
* @return mixed The same value with all placeholders replaced by their values
*
* @throws \RuntimeException if a placeholder references a parameter that does not exist
*/
static public function resolveValue($value, $parameters)
2010-01-04 14:26:20 +00:00
{
if (is_array($value))
{
$args = array();
foreach ($value as $k => $v)
{
$args[self::resolveValue($k, $parameters)] = self::resolveValue($v, $parameters);
}
$value = $args;
}
else if (is_string($value))
{
if (preg_match('/^%([^%]+)%$/', $value, $match))
{
// we do this to deal with non string values (boolean, integer, ...)
// the preg_replace_callback converts them to strings
if (!array_key_exists($name = strtolower($match[1]), $parameters))
{
throw new \RuntimeException(sprintf('The parameter "%s" must be defined.', $name));
}
$value = $parameters[$name];
}
else
{
$replaceParameter = function ($match) use ($parameters, $value)
{
if (!array_key_exists($name = strtolower($match[2]), $parameters))
{
throw new \RuntimeException(sprintf('The parameter "%s" must be defined (used in the following expression: "%s").', $name, $value));
}
return $parameters[$name];
};
$value = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', $replaceParameter, $value));
}
}
return $value;
2010-01-04 14:26:20 +00:00
}
/**
* Replaces service references by the real service instance.
*
* @param mixed $value A value
*
* @return mixed The same value with all service references replaced by the real service instances
*/
public function resolveServices($value)
2010-01-04 14:26:20 +00:00
{
if (is_array($value))
{
foreach ($value as &$v)
{
$v = $this->resolveServices($v);
}
}
else if (is_object($value) && $value instanceof Reference)
{
$value = $this->getService((string) $value, $value->getInvalidBehavior());
}
return $value;
2010-01-04 14:26:20 +00:00
}
/**
* Returns service ids for a given annotation.
*
* @param string $name The annotation name
*
* @return array An array of annotations
*/
public function findAnnotatedServiceIds($name)
{
$annotations = array();
foreach ($this->getDefinitions() as $id => $definition)
{
if ($definition->getAnnotation($name))
{
$annotations[$id] = $definition->getAnnotation($name);
}
}
return $annotations;
}
static public function getServiceConditionals($value)
{
$services = array();
if (is_array($value))
{
foreach ($value as $v)
{
$services = array_unique(array_merge($services, self::getServiceConditionals($v)));
}
}
elseif (is_object($value) && $value instanceof Reference && $value->getInvalidBehavior() === Container::IGNORE_ON_INVALID_REFERENCE)
{
$services[] = (string) $value;
}
2010-01-04 14:26:20 +00:00
return $services;
2010-01-04 14:26:20 +00:00
}
}