Skip to content

Commit

Permalink
[DependencyInjection] added a check for circular references in parame…
Browse files Browse the repository at this point in the history
…ter definitions
  • Loading branch information
fabpot committed May 25, 2011
1 parent 456eb53 commit 2438a73
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 12 deletions.
@@ -0,0 +1,34 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Exception;

/**
* This exception is thrown when a circular reference in a parameter is detected.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ParameterCircularReferenceException extends RuntimeException
{
private $parameters;

public function __construct($parameters)
{
parent::__construct(sprintf('Circular reference detected for parameter "%s" ("%s" > "%s").', $parameters[0], implode('" > "', $parameters), $parameters[0]));

$this->parameters = $parameters;
}

public function getParameters()
{
return $this->parameters;
}
}
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\ParameterBag;

use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;

/**
*
Expand Down Expand Up @@ -125,18 +126,20 @@ public function resolve()
* Replaces parameter placeholders (%name%) by their values.
*
* @param mixed $value A value
* @param array $resolving An array of keys that are being resolved (used internally to detect circular references)
*
* @return mixed The resolved value
*
* @throws ParameterNotFoundException if a placeholder references a parameter that does not exist
* @throws ParameterCircularReferenceException if a circular reference if detected
* @throws \LogicException when a given parameter has a type problem.
*/
public function resolveValue($value)
public function resolveValue($value, array $resolving = array())
{
if (is_array($value)) {
$args = array();
foreach ($value as $k => $v) {
$args[$this->resolveValue($k)] = $this->resolveValue($v);
$args[$this->resolveValue($k, $resolving)] = $this->resolveValue($v, $resolving);
}

return $args;
Expand All @@ -146,36 +149,55 @@ public function resolveValue($value)
return $value;
}

return $this->resolveString($value);
return $this->resolveString($value, $resolving);
}

/**
* Resolves parameters inside a string
*
* @param string $value The string to resolve
* @param string $value The string to resolve
* @param array $resolving An array of keys that are being resolved (used internally to detect circular references)
*
* @return string The resolved string
*
* @throws ParameterNotFoundException if a placeholder references a parameter that does not exist
* @throws ParameterCircularReferenceException if a circular reference if detected
* @throws \LogicException when a given parameter has a type problem.
*/
public function resolveString($value)
public function resolveString($value, array $resolving = array())
{
// we do this to deal with non string values (Boolean, integer, ...)
// as the preg_replace_callback throw an exception when trying
// a non-string in a parameter value
if (preg_match('/^%([^%]+)%$/', $value, $match)) {
// we do this to deal with non string values (Boolean, integer, ...)
// as the preg_replace_callback throw an exception when trying
// a non-string in a parameter value
return $this->resolveValue($this->get(strtolower($match[1])));
$key = strtolower($match[1]);

if (isset($resolving[$key])) {
throw new ParameterCircularReferenceException(array_keys($resolving));
}

$resolving[$key] = true;

return $this->resolveValue($this->get($key), $resolving);
}

$self = $this;
return str_replace('%%', '%', preg_replace_callback('/(?<!%)%([^%]+)%/', function ($match) use ($self) {
$resolved = $self->get(strtolower($match[1]));
return str_replace('%%', '%', preg_replace_callback('/(?<!%)%([^%]+)%/', function ($match) use ($self, $resolving) {
$key = strtolower($match[1]);

if (isset($resolving[$key])) {
throw new ParameterCircularReferenceException(array_keys($resolving));
}

$resolved = $self->get($key);

if (!is_string($resolved)) {
throw new \LogicException('A parameter cannot contain a non-string parameter.');
}

return $self->resolveString($resolved);
$resolving[$key] = true;

return $self->resolveString($resolved, $resolving);
}, $value));
}
}
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;

class ParameterBagTest extends \PHPUnit_Framework_TestCase
{
Expand Down Expand Up @@ -120,6 +121,22 @@ public function testResolveValue()
} catch (\LogicException $e) {
$this->assertEquals('A parameter cannot contain a non-string parameter.', $e->getMessage(), '->resolveValue() throws a LogicException when a parameter embeds another non-string parameter');
}

$bag = new ParameterBag(array('foo' => '%bar%', 'bar' => '%foobar%', 'foobar' => '%foo%'));
try {
$bag->resolveValue('%foo%');
$this->fail('->resolveValue() throws a ParameterCircularReferenceException when a parameter has a circular reference');
} catch (ParameterCircularReferenceException $e) {
$this->assertEquals('Circular reference detected for parameter "foo" ("foo" > "bar" > "foobar" > "foo").', $e->getMessage(), '->resolveValue() throws a ParameterCircularReferenceException when a parameter has a circular reference');
}

$bag = new ParameterBag(array('foo' => 'a %bar%', 'bar' => 'a %foobar%', 'foobar' => 'a %foo%'));
try {
$bag->resolveValue('%foo%');
$this->fail('->resolveValue() throws a ParameterCircularReferenceException when a parameter has a circular reference');
} catch (ParameterCircularReferenceException $e) {
$this->assertEquals('Circular reference detected for parameter "foo" ("foo" > "bar" > "foobar" > "foo").', $e->getMessage(), '->resolveValue() throws a ParameterCircularReferenceException when a parameter has a circular reference');
}
}

/**
Expand Down

0 comments on commit 2438a73

Please sign in to comment.