Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\DependencyInjection\ParameterBag; Chris@0: Chris@17: use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; Chris@0: use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; Chris@0: use Symfony\Component\DependencyInjection\Exception\RuntimeException; Chris@0: Chris@0: /** Chris@0: * Holds parameters. Chris@0: * Chris@0: * @author Fabien Potencier Chris@0: */ Chris@0: class ParameterBag implements ParameterBagInterface Chris@0: { Chris@17: protected $parameters = []; Chris@0: protected $resolved = false; Chris@0: Chris@17: private $normalizedNames = []; Chris@14: Chris@0: /** Chris@0: * @param array $parameters An array of parameters Chris@0: */ Chris@17: public function __construct(array $parameters = []) Chris@0: { Chris@0: $this->add($parameters); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Clears all parameters. Chris@0: */ Chris@0: public function clear() Chris@0: { Chris@17: $this->parameters = []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds parameters to the service container parameters. Chris@0: * Chris@0: * @param array $parameters An array of parameters Chris@0: */ Chris@0: public function add(array $parameters) Chris@0: { Chris@0: foreach ($parameters as $key => $value) { Chris@14: $this->set($key, $value); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function all() Chris@0: { Chris@0: return $this->parameters; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function get($name) Chris@0: { Chris@14: $name = $this->normalizeName($name); Chris@0: Chris@18: if (!\array_key_exists($name, $this->parameters)) { Chris@0: if (!$name) { Chris@0: throw new ParameterNotFoundException($name); Chris@0: } Chris@0: Chris@17: $alternatives = []; Chris@0: foreach ($this->parameters as $key => $parameterValue) { Chris@0: $lev = levenshtein($name, $key); Chris@17: if ($lev <= \strlen($name) / 3 || false !== strpos($key, $name)) { Chris@0: $alternatives[] = $key; Chris@0: } Chris@0: } Chris@0: Chris@0: $nonNestedAlternative = null; Chris@17: if (!\count($alternatives) && false !== strpos($name, '.')) { Chris@0: $namePartsLength = array_map('strlen', explode('.', $name)); Chris@0: $key = substr($name, 0, -1 * (1 + array_pop($namePartsLength))); Chris@17: while (\count($namePartsLength)) { Chris@0: if ($this->has($key)) { Chris@17: if (\is_array($this->get($key))) { Chris@0: $nonNestedAlternative = $key; Chris@0: } Chris@0: break; Chris@0: } Chris@0: Chris@0: $key = substr($key, 0, -1 * (1 + array_pop($namePartsLength))); Chris@0: } Chris@0: } Chris@0: Chris@0: throw new ParameterNotFoundException($name, null, null, null, $alternatives, $nonNestedAlternative); Chris@0: } Chris@0: Chris@0: return $this->parameters[$name]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets a service container parameter. Chris@0: * Chris@0: * @param string $name The parameter name Chris@0: * @param mixed $value The parameter value Chris@0: */ Chris@0: public function set($name, $value) Chris@0: { Chris@14: $this->parameters[$this->normalizeName($name)] = $value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function has($name) Chris@0: { Chris@18: return \array_key_exists($this->normalizeName($name), $this->parameters); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Removes a parameter. Chris@0: * Chris@0: * @param string $name The parameter name Chris@0: */ Chris@0: public function remove($name) Chris@0: { Chris@14: unset($this->parameters[$this->normalizeName($name)]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function resolve() Chris@0: { Chris@0: if ($this->resolved) { Chris@0: return; Chris@0: } Chris@0: Chris@17: $parameters = []; Chris@0: foreach ($this->parameters as $key => $value) { Chris@0: try { Chris@0: $value = $this->resolveValue($value); Chris@0: $parameters[$key] = $this->unescapeValue($value); Chris@0: } catch (ParameterNotFoundException $e) { Chris@0: $e->setSourceKey($key); Chris@0: Chris@0: throw $e; Chris@0: } Chris@0: } Chris@0: Chris@0: $this->parameters = $parameters; Chris@0: $this->resolved = true; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Replaces parameter placeholders (%name%) by their values. Chris@0: * Chris@0: * @param mixed $value A value Chris@0: * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) Chris@0: * Chris@0: * @return mixed The resolved value Chris@0: * Chris@0: * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist Chris@0: * @throws ParameterCircularReferenceException if a circular reference if detected Chris@14: * @throws RuntimeException when a given parameter has a type problem Chris@0: */ Chris@17: public function resolveValue($value, array $resolving = []) Chris@0: { Chris@14: if (\is_array($value)) { Chris@17: $args = []; Chris@0: foreach ($value as $k => $v) { Chris@14: $args[\is_string($k) ? $this->resolveValue($k, $resolving) : $k] = $this->resolveValue($v, $resolving); Chris@0: } Chris@0: Chris@0: return $args; Chris@0: } Chris@0: Chris@14: if (!\is_string($value) || 2 > \strlen($value)) { Chris@0: return $value; Chris@0: } Chris@0: Chris@0: return $this->resolveString($value, $resolving); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Resolves parameters inside a string. Chris@0: * Chris@0: * @param string $value The string to resolve Chris@0: * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) Chris@0: * Chris@0: * @return string The resolved string Chris@0: * Chris@0: * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist Chris@0: * @throws ParameterCircularReferenceException if a circular reference if detected Chris@14: * @throws RuntimeException when a given parameter has a type problem Chris@0: */ Chris@17: public function resolveString($value, array $resolving = []) Chris@0: { Chris@0: // we do this to deal with non string values (Boolean, integer, ...) Chris@0: // as the preg_replace_callback throw an exception when trying Chris@0: // a non-string in a parameter value Chris@0: if (preg_match('/^%([^%\s]+)%$/', $value, $match)) { Chris@0: $key = $match[1]; Chris@14: $lcKey = strtolower($key); // strtolower() to be removed in 4.0 Chris@0: Chris@0: if (isset($resolving[$lcKey])) { Chris@0: throw new ParameterCircularReferenceException(array_keys($resolving)); Chris@0: } Chris@0: Chris@0: $resolving[$lcKey] = true; Chris@0: Chris@0: return $this->resolved ? $this->get($key) : $this->resolveValue($this->get($key), $resolving); Chris@0: } Chris@0: Chris@0: return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($resolving, $value) { Chris@0: // skip %% Chris@0: if (!isset($match[1])) { Chris@0: return '%%'; Chris@0: } Chris@0: Chris@0: $key = $match[1]; Chris@14: $lcKey = strtolower($key); // strtolower() to be removed in 4.0 Chris@0: if (isset($resolving[$lcKey])) { Chris@0: throw new ParameterCircularReferenceException(array_keys($resolving)); Chris@0: } Chris@0: Chris@0: $resolved = $this->get($key); Chris@0: Chris@17: if (!\is_string($resolved) && !is_numeric($resolved)) { Chris@17: throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "%s" of type %s inside string value "%s".', $key, \gettype($resolved), $value)); Chris@0: } Chris@0: Chris@0: $resolved = (string) $resolved; Chris@0: $resolving[$lcKey] = true; Chris@0: Chris@0: return $this->isResolved() ? $resolved : $this->resolveString($resolved, $resolving); Chris@0: }, $value); Chris@0: } Chris@0: Chris@0: public function isResolved() Chris@0: { Chris@0: return $this->resolved; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function escapeValue($value) Chris@0: { Chris@17: if (\is_string($value)) { Chris@0: return str_replace('%', '%%', $value); Chris@0: } Chris@0: Chris@17: if (\is_array($value)) { Chris@17: $result = []; Chris@0: foreach ($value as $k => $v) { Chris@0: $result[$k] = $this->escapeValue($v); Chris@0: } Chris@0: Chris@0: return $result; Chris@0: } Chris@0: Chris@0: return $value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function unescapeValue($value) Chris@0: { Chris@17: if (\is_string($value)) { Chris@0: return str_replace('%%', '%', $value); Chris@0: } Chris@0: Chris@17: if (\is_array($value)) { Chris@17: $result = []; Chris@0: foreach ($value as $k => $v) { Chris@0: $result[$k] = $this->unescapeValue($v); Chris@0: } Chris@0: Chris@0: return $result; Chris@0: } Chris@0: Chris@0: return $value; Chris@0: } Chris@14: Chris@14: private function normalizeName($name) Chris@14: { Chris@14: if (isset($this->normalizedNames[$normalizedName = strtolower($name)])) { Chris@14: $normalizedName = $this->normalizedNames[$normalizedName]; Chris@14: if ((string) $name !== $normalizedName) { Chris@14: @trigger_error(sprintf('Parameter names will be made case sensitive in Symfony 4.0. Using "%s" instead of "%s" is deprecated since Symfony 3.4.', $name, $normalizedName), E_USER_DEPRECATED); Chris@14: } Chris@14: } else { Chris@14: $normalizedName = $this->normalizedNames[$normalizedName] = (string) $name; Chris@14: } Chris@14: Chris@14: return $normalizedName; Chris@14: } Chris@0: }