+
{% for name, template in templates %}
{{ template.renderblock('toolbar', {
'collector': profile.getcollector(name),
diff --git a/src/Symfony/Component/Config/Definition/Builder/EnumNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/EnumNodeDefinition.php
index c84e389bbd17..dc25fcbd26f2 100644
--- a/src/Symfony/Component/Config/Definition/Builder/EnumNodeDefinition.php
+++ b/src/Symfony/Component/Config/Definition/Builder/EnumNodeDefinition.php
@@ -22,6 +22,11 @@ class EnumNodeDefinition extends ScalarNodeDefinition
{
private $values;
+ /**
+ * @param array $values
+ *
+ * @return EnumNodeDefinition|$this
+ */
public function values(array $values)
{
$values = array_unique($values);
diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php
index 31691767b3b4..f7f84bc07153 100644
--- a/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php
+++ b/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php
@@ -56,7 +56,7 @@ public function __construct($name, NodeParentInterface $parent = null)
*
* @param NodeParentInterface $parent The parent
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function setParent(NodeParentInterface $parent)
{
@@ -70,7 +70,7 @@ public function setParent(NodeParentInterface $parent)
*
* @param string $info The info text
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function info($info)
{
@@ -82,7 +82,7 @@ public function info($info)
*
* @param string|array $example
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function example($example)
{
@@ -95,7 +95,7 @@ public function example($example)
* @param string $key
* @param mixed $value
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function attribute($key, $value)
{
@@ -146,7 +146,7 @@ public function getNode($forceRootNode = false)
*
* @param mixed $value The default value
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function defaultValue($value)
{
@@ -159,7 +159,7 @@ public function defaultValue($value)
/**
* Sets the node as required.
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function isRequired()
{
@@ -173,7 +173,7 @@ public function isRequired()
*
* @param mixed $value
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function treatNullLike($value)
{
@@ -187,7 +187,7 @@ public function treatNullLike($value)
*
* @param mixed $value
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function treatTrueLike($value)
{
@@ -201,7 +201,7 @@ public function treatTrueLike($value)
*
* @param mixed $value
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function treatFalseLike($value)
{
@@ -213,7 +213,7 @@ public function treatFalseLike($value)
/**
* Sets null as the default value.
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function defaultNull()
{
@@ -223,7 +223,7 @@ public function defaultNull()
/**
* Sets true as the default value.
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function defaultTrue()
{
@@ -233,7 +233,7 @@ public function defaultTrue()
/**
* Sets false as the default value.
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function defaultFalse()
{
@@ -253,7 +253,7 @@ public function beforeNormalization()
/**
* Denies the node value being empty.
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function cannotBeEmpty()
{
@@ -281,7 +281,7 @@ public function validate()
*
* @param bool $deny Whether the overwriting is forbidden or not
*
- * @return NodeDefinition
+ * @return NodeDefinition|$this
*/
public function cannotBeOverwritten($deny = true)
{
diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php
index b834d984f83b..f80e675594ae 100644
--- a/src/Symfony/Component/Console/Command/Command.php
+++ b/src/Symfony/Component/Console/Command/Command.php
@@ -170,6 +170,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
/**
* Interacts with the user.
*
+ * This method is executed before the InputDefinition is validated.
+ * This means that this is the only place where the command can
+ * interactively ask for values of missing required arguments.
+ *
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
diff --git a/src/Symfony/Component/Console/Input/ArrayInput.php b/src/Symfony/Component/Console/Input/ArrayInput.php
index f3222bb55570..5743bb8af16b 100644
--- a/src/Symfony/Component/Console/Input/ArrayInput.php
+++ b/src/Symfony/Component/Console/Input/ArrayInput.php
@@ -100,8 +100,10 @@ public function getParameterOption($values, $default = false)
$values = (array) $values;
foreach ($this->parameters as $k => $v) {
- if (is_int($k) && in_array($v, $values)) {
- return true;
+ if (is_int($k)) {
+ if (in_array($v, $values)) {
+ return true;
+ }
} elseif (in_array($k, $values)) {
return $v;
}
diff --git a/src/Symfony/Component/Console/Tests/Input/ArrayInputTest.php b/src/Symfony/Component/Console/Tests/Input/ArrayInputTest.php
index 7cf28e8f4969..cc89083c6b1c 100644
--- a/src/Symfony/Component/Console/Tests/Input/ArrayInputTest.php
+++ b/src/Symfony/Component/Console/Tests/Input/ArrayInputTest.php
@@ -38,6 +38,15 @@ public function testHasParameterOption()
$this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if an option is present in the passed parameters');
}
+ public function testGetParameterOption()
+ {
+ $input = new ArrayInput(array('name' => 'Fabien', '--foo' => 'bar'));
+ $this->assertEquals('bar', $input->getParameterOption('--foo'), '->getParameterOption() returns the option of specified name');
+
+ $input = new ArrayInput(array('Fabien', '--foo' => 'bar'));
+ $this->assertEquals('bar', $input->getParameterOption('--foo'), '->getParameterOption() returns the option of specified name');
+ }
+
public function testParseArguments()
{
$input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'))));
diff --git a/src/Symfony/Component/Console/Tests/Input/InputDefinitionTest.php b/src/Symfony/Component/Console/Tests/Input/InputDefinitionTest.php
index 9e3e982d7d4f..88263049f295 100644
--- a/src/Symfony/Component/Console/Tests/Input/InputDefinitionTest.php
+++ b/src/Symfony/Component/Console/Tests/Input/InputDefinitionTest.php
@@ -341,7 +341,7 @@ public function testGetOptionDefaults()
new InputOption('foo7', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, '', array(1, 2)),
));
$defaults = array(
- 'foo1' => null,
+ 'foo1' => false,
'foo2' => null,
'foo3' => 'default',
'foo4' => null,
@@ -349,7 +349,7 @@ public function testGetOptionDefaults()
'foo6' => array(),
'foo7' => array(1, 2),
);
- $this->assertEquals($defaults, $definition->getOptionDefaults(), '->getOptionDefaults() returns the default values for all options');
+ $this->assertSame($defaults, $definition->getOptionDefaults(), '->getOptionDefaults() returns the default values for all options');
}
public function testGetSynopsis()
diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php
index 91a2992e22d2..671b00a5b2b9 100644
--- a/src/Symfony/Component/Finder/Finder.php
+++ b/src/Symfony/Component/Finder/Finder.php
@@ -234,7 +234,7 @@ public function depth($level)
* $finder->date('> now - 2 hours');
* $finder->date('>= 2005-10-15');
*
- * @param string $date A date rage string
+ * @param string $date A date range string
*
* @return Finder The current Finder instance
*
diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md
index 07c96077c2f7..ea959e1377b2 100644
--- a/src/Symfony/Component/Form/CHANGELOG.md
+++ b/src/Symfony/Component/Form/CHANGELOG.md
@@ -10,8 +10,8 @@ CHANGELOG
2.6.2
-----
-* Added back the `model_timezone` and `view_timezone` options for `TimeType`, `DateType`
- and `BirthdayType`
+ * Added back the `model_timezone` and `view_timezone` options for `TimeType`, `DateType`
+ and `BirthdayType`
2.6.0
-----
diff --git a/src/Symfony/Component/HttpFoundation/File/File.php b/src/Symfony/Component/HttpFoundation/File/File.php
index 0b637d1a3e67..dc0e2f7656a9 100644
--- a/src/Symfony/Component/HttpFoundation/File/File.php
+++ b/src/Symfony/Component/HttpFoundation/File/File.php
@@ -130,7 +130,7 @@ public function move($directory, $name = null)
protected function getTargetFile($directory, $name = null)
{
if (!is_dir($directory)) {
- if (false === @mkdir($directory, 0777, true)) {
+ if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
}
} elseif (!is_writable($directory)) {
diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php
index 85d7ffa65aae..e8afd6970451 100644
--- a/src/Symfony/Component/HttpFoundation/Request.php
+++ b/src/Symfony/Component/HttpFoundation/Request.php
@@ -616,8 +616,8 @@ public static function getTrustedHosts()
* The following header keys are supported:
*
* * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp())
- * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getClientHost())
- * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getClientPort())
+ * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getHost())
+ * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getPort())
* * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure())
*
* Setting an empty value allows to disable the trusted header for the given key.
diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php
index b4c76d2939a3..614b4387f96a 100644
--- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php
+++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php
@@ -110,7 +110,7 @@ public function dump(Data $data)
if (false === $name) {
$name = strtr($file, '\\', '/');
- $name = substr($file, strrpos($file, '/') + 1);
+ $name = substr($name, strrpos($name, '/') + 1);
}
$this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index 408b0ec093ad..0324c0fb3cca 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -578,7 +578,7 @@ protected function buildContainer()
{
foreach (array('cache' => $this->getCacheDir(), 'logs' => $this->getLogDir()) as $name => $dir) {
if (!is_dir($dir)) {
- if (false === @mkdir($dir, 0777, true)) {
+ if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
throw new \RuntimeException(sprintf("Unable to create the %s directory (%s)\n", $name, $dir));
}
} elseif (!is_writable($dir)) {
diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
index 74462fd3a2cc..8187c1dd3c98 100644
--- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
+++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
@@ -11,7 +11,7 @@
namespace Symfony\Component\PropertyAccess;
-use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
+use Symfony\Component\PropertyAccess\Exception\AccessException;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
@@ -51,15 +51,8 @@ public function __construct($magicCall = false, $throwExceptionOnInvalidIndex =
*/
public function getValue($objectOrArray, $propertyPath)
{
- if (is_string($propertyPath)) {
+ if (!$propertyPath instanceof PropertyPathInterface) {
$propertyPath = new PropertyPath($propertyPath);
- } elseif (!$propertyPath instanceof PropertyPathInterface) {
- throw new InvalidArgumentException(sprintf(
- 'The property path should be a string or an instance of '.
- '"Symfony\Component\PropertyAccess\PropertyPathInterface". '.
- 'Got: "%s"',
- is_object($propertyPath) ? get_class($propertyPath) : gettype($propertyPath)
- ));
}
$propertyValues = & $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
@@ -72,15 +65,8 @@ public function getValue($objectOrArray, $propertyPath)
*/
public function setValue(&$objectOrArray, $propertyPath, $value)
{
- if (is_string($propertyPath)) {
+ if (!$propertyPath instanceof PropertyPathInterface) {
$propertyPath = new PropertyPath($propertyPath);
- } elseif (!$propertyPath instanceof PropertyPathInterface) {
- throw new InvalidArgumentException(sprintf(
- 'The property path should be a string or an instance of '.
- '"Symfony\Component\PropertyAccess\PropertyPathInterface". '.
- 'Got: "%s"',
- is_object($propertyPath) ? get_class($propertyPath) : gettype($propertyPath)
- ));
}
$propertyValues = & $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength() - 1);
@@ -96,10 +82,6 @@ public function setValue(&$objectOrArray, $propertyPath, $value)
$objectOrArray = & $propertyValues[$i][self::VALUE];
if ($overwrite) {
- if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
- throw new UnexpectedTypeException($objectOrArray, $propertyPath, $i);
- }
-
$property = $propertyPath->getElement($i);
if ($propertyPath->isIndex($i)) {
@@ -119,24 +101,15 @@ public function setValue(&$objectOrArray, $propertyPath, $value)
*/
public function isReadable($objectOrArray, $propertyPath)
{
- if (is_string($propertyPath)) {
+ if (!$propertyPath instanceof PropertyPathInterface) {
$propertyPath = new PropertyPath($propertyPath);
- } elseif (!$propertyPath instanceof PropertyPathInterface) {
- throw new InvalidArgumentException(sprintf(
- 'The property path should be a string or an instance of '.
- '"Symfony\Component\PropertyAccess\PropertyPathInterface". '.
- 'Got: "%s"',
- is_object($propertyPath) ? get_class($propertyPath) : gettype($propertyPath)
- ));
}
try {
$this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
return true;
- } catch (NoSuchIndexException $e) {
- return false;
- } catch (NoSuchPropertyException $e) {
+ } catch (AccessException $e) {
return false;
} catch (UnexpectedTypeException $e) {
return false;
@@ -148,15 +121,8 @@ public function isReadable($objectOrArray, $propertyPath)
*/
public function isWritable($objectOrArray, $propertyPath)
{
- if (is_string($propertyPath)) {
+ if (!$propertyPath instanceof PropertyPathInterface) {
$propertyPath = new PropertyPath($propertyPath);
- } elseif (!$propertyPath instanceof PropertyPathInterface) {
- throw new InvalidArgumentException(sprintf(
- 'The property path should be a string or an instance of '.
- '"Symfony\Component\PropertyAccess\PropertyPathInterface". '.
- 'Got: "%s"',
- is_object($propertyPath) ? get_class($propertyPath) : gettype($propertyPath)
- ));
}
try {
@@ -173,10 +139,6 @@ public function isWritable($objectOrArray, $propertyPath)
$objectOrArray = $propertyValues[$i][self::VALUE];
if ($overwrite) {
- if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
- return false;
- }
-
$property = $propertyPath->getElement($i);
if ($propertyPath->isIndex($i)) {
@@ -194,9 +156,9 @@ public function isWritable($objectOrArray, $propertyPath)
}
return true;
- } catch (NoSuchIndexException $e) {
+ } catch (AccessException $e) {
return false;
- } catch (NoSuchPropertyException $e) {
+ } catch (UnexpectedTypeException $e) {
return false;
}
}
@@ -217,13 +179,13 @@ public function isWritable($objectOrArray, $propertyPath)
*/
private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex, $ignoreInvalidIndices = true)
{
+ if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
+ throw new UnexpectedTypeException($objectOrArray, $propertyPath, 0);
+ }
+
$propertyValues = array();
for ($i = 0; $i < $lastIndex; ++$i) {
- if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
- throw new UnexpectedTypeException($objectOrArray, $propertyPath, $i);
- }
-
$property = $propertyPath->getElement($i);
$isIndex = $propertyPath->isIndex($i);
@@ -264,6 +226,11 @@ private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $pr
$objectOrArray = & $propertyValue[self::VALUE];
+ // the final value of the path must not be validated
+ if ($i + 1 < $propertyPath->getLength() && !is_object($objectOrArray) && !is_array($objectOrArray)) {
+ throw new UnexpectedTypeException($objectOrArray, $propertyPath, $i+1);
+ }
+
$propertyValues[] = & $propertyValue;
}
diff --git a/src/Symfony/Component/PropertyAccess/StringUtil.php b/src/Symfony/Component/PropertyAccess/StringUtil.php
index 29da09755cd1..5fa1b1734f8b 100644
--- a/src/Symfony/Component/PropertyAccess/StringUtil.php
+++ b/src/Symfony/Component/PropertyAccess/StringUtil.php
@@ -197,7 +197,7 @@ public static function singularify($plural)
}
// Convert teeth to tooth, feet to foot
- if (false !== ($pos = strpos($plural, 'ee')) && strlen($plural) > 3) {
+ if (false !== ($pos = strpos($plural, 'ee')) && strlen($plural) > 3 && 'feedback' !== $plural) {
return substr_replace($plural, 'oo', $pos, 2);
}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
index 2d29e3f5f4d9..6b55d449ae09 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
@@ -29,6 +29,20 @@ protected function setUp()
$this->propertyAccessor = new PropertyAccessor();
}
+ public function getPathsWithUnexpectedType()
+ {
+ return array(
+ array('', 'foobar'),
+ array('foo', 'foobar'),
+ array(null, 'foobar'),
+ array(123, 'foobar'),
+ array((object) array('prop' => null), 'prop.foobar'),
+ array((object) array('prop' => (object) array('subProp' => null)), 'prop.subProp.foobar'),
+ array(array('index' => null), '[index][foobar]'),
+ array(array('index' => array('subIndex' => null)), '[index][subIndex][foobar]'),
+ );
+ }
+
public function getPathsWithMissingProperty()
{
return array(
@@ -138,39 +152,13 @@ public function testGetValueReadsMagicCallThatReturnsConstant()
}
/**
+ * @dataProvider getPathsWithUnexpectedType
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
- * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on, but it found type "string" while trying to traverse path "foobar" at property "foobar".
- */
- public function testGetValueThrowsExceptionIfNotObjectOrArray()
- {
- $this->propertyAccessor->getValue('baz', 'foobar');
- }
-
- /**
- * @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
- * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on, but it found type "NULL" while trying to traverse path "foobar" at property "foobar".
- */
- public function testGetValueThrowsExceptionIfNull()
- {
- $this->propertyAccessor->getValue(null, 'foobar');
- }
-
- /**
- * @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
- * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on, but it found type "string" while trying to traverse path "foobar" at property "foobar".
- */
- public function testGetValueThrowsExceptionIfEmpty()
- {
- $this->propertyAccessor->getValue('', 'foobar');
- }
-
- /**
- * @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
- * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on, but it found type "NULL" while trying to traverse path "foobar.baz" at property "baz".
+ * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on
*/
- public function testGetValueNestedExceptionMessage()
+ public function testGetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $path)
{
- $this->propertyAccessor->getValue((object) array('foobar' => null), 'foobar.baz');
+ $this->propertyAccessor->getValue($objectOrArray, $path);
}
/**
@@ -260,47 +248,13 @@ public function testSetValueUpdatesMagicCallIfEnabled()
}
/**
+ * @dataProvider getPathsWithUnexpectedType
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
- * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on, but it found type "string" while trying to traverse path "foobar" at property "foobar".
- */
- public function testSetValueThrowsExceptionIfNotObjectOrArray()
- {
- $value = 'baz';
-
- $this->propertyAccessor->setValue($value, 'foobar', 'bam');
- }
-
- /**
- * @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
- * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on, but it found type "NULL" while trying to traverse path "foobar" at property "foobar".
- */
- public function testSetValueThrowsExceptionIfNull()
- {
- $value = null;
-
- $this->propertyAccessor->setValue($value, 'foobar', 'bam');
- }
-
- /**
- * @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
- * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on, but it found type "string" while trying to traverse path "foobar" at property "foobar".
- */
- public function testSetValueThrowsExceptionIfEmpty()
- {
- $value = '';
-
- $this->propertyAccessor->setValue($value, 'foobar', 'bam');
- }
-
- /**
- * @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
- * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on, but it found type "NULL" while trying to traverse path "foobar.baz" at property "baz".
+ * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on
*/
- public function testSetValueNestedExceptionMessage()
+ public function testSetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $path)
{
- $value = (object) array('foobar' => null);
-
- $this->propertyAccessor->setValue($value, 'foobar.baz', 'bam');
+ $this->propertyAccessor->setValue($objectOrArray, $path, 'value');
}
public function testGetValueWhenArrayValueIsNull()
@@ -362,19 +316,12 @@ public function testIsReadableRecognizesMagicCallIfEnabled()
$this->assertTrue($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
- public function testIsReadableThrowsExceptionIfNotObjectOrArray()
- {
- $this->assertFalse($this->propertyAccessor->isReadable('baz', 'foobar'));
- }
-
- public function testIsReadableThrowsExceptionIfNull()
- {
- $this->assertFalse($this->propertyAccessor->isReadable(null, 'foobar'));
- }
-
- public function testIsReadableThrowsExceptionIfEmpty()
+ /**
+ * @dataProvider getPathsWithUnexpectedType
+ */
+ public function testIsReadableReturnsFalseIfNotObjectOrArray($objectOrArray, $path)
{
- $this->assertFalse($this->propertyAccessor->isReadable('', 'foobar'));
+ $this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
}
/**
@@ -430,19 +377,12 @@ public function testIsWritableRecognizesMagicCallIfEnabled()
$this->assertTrue($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
- public function testNotObjectOrArrayIsNotWritable()
- {
- $this->assertFalse($this->propertyAccessor->isWritable('baz', 'foobar'));
- }
-
- public function testNullIsNotWritable()
- {
- $this->assertFalse($this->propertyAccessor->isWritable(null, 'foobar'));
- }
-
- public function testEmptyIsNotWritable()
+ /**
+ * @dataProvider getPathsWithUnexpectedType
+ */
+ public function testIsWritableReturnsFalseIfNotObjectOrArray($objectOrArray, $path)
{
- $this->assertFalse($this->propertyAccessor->isWritable('', 'foobar'));
+ $this->assertFalse($this->propertyAccessor->isWritable($objectOrArray, $path));
}
public function getValidPropertyPaths()
diff --git a/src/Symfony/Component/PropertyAccess/Tests/StringUtilTest.php b/src/Symfony/Component/PropertyAccess/Tests/StringUtilTest.php
index a9c07162fe6a..7c6376dbd6a1 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/StringUtilTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/StringUtilTest.php
@@ -59,6 +59,7 @@ public function singularifyProvider()
array('data', array('daton', 'datum')),
array('days', 'day'),
array('discos', 'disco'),
+ array('devices', array('devex', 'devix', 'device')),
array('drives', 'drive'),
array('drivers', 'driver'),
array('dwarves', array('dwarf', 'dwarve', 'dwarff')),
@@ -67,6 +68,7 @@ public function singularifyProvider()
array('emphases', array('emphas', 'emphase', 'emphasis')),
array('faxes', 'fax'),
array('feet', 'foot'),
+ array('feedback', 'feedback'),
array('foci', 'focus'),
array('focuses', array('focus', 'focuse', 'focusis')),
array('formulae', 'formula'),
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php
index 14b3f9b3b2fd..321f1ea439b6 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php
@@ -14,11 +14,25 @@
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\MappingException;
+/**
+ * Base loader for validation metadata.
+ *
+ * This loader supports the loading of constraints from Symfony's default
+ * namespace (see {@link DEFAULT_NAMESPACE}) using the short class names of
+ * those constraints. Constraints can also be loaded using their fully
+ * qualified class names. At last, namespace aliases can be defined to load
+ * constraints with the syntax "alias:ShortName".
+ *
+ * @author Bernhard Schussek
+ */
abstract class AbstractLoader implements LoaderInterface
{
/**
- * Contains all known namespaces indexed by their prefix.
- *
+ * The namespace to load constraints from by default.
+ */
+ const DEFAULT_NAMESPACE = '\\Symfony\\Component\\Validator\\Constraints\\';
+
+ /**
* @var array
*/
protected $namespaces = array();
@@ -26,6 +40,13 @@ abstract class AbstractLoader implements LoaderInterface
/**
* Adds a namespace alias.
*
+ * The namespace alias can be used to reference constraints from specific
+ * namespaces in {@link newConstraint()}:
+ *
+ * $this->addNamespaceAlias('mynamespace', '\\Acme\\Package\\Constraints\\');
+ *
+ * $constraint = $this->newConstraint('mynamespace:NotNull');
+ *
* @param string $alias The alias
* @param string $namespace The PHP namespace
*/
@@ -37,16 +58,19 @@ protected function addNamespaceAlias($alias, $namespace)
/**
* Creates a new constraint instance for the given constraint name.
*
- * @param string $name The constraint name. Either a constraint relative
- * to the default constraint namespace, or a fully
- * qualified class name
- * @param mixed $options The constraint options
+ * @param string $name The constraint name. Either a constraint relative
+ * to the default constraint namespace, or a fully
+ * qualified class name. Alternatively, the constraint
+ * may be preceded by a namespace alias and a colon.
+ * The namespace alias must have been defined using
+ * {@link addNamespaceAlias()}.
+ * @param mixed $options The constraint options
*
* @return Constraint
*
* @throws MappingException If the namespace prefix is undefined
*/
- protected function newConstraint($name, $options)
+ protected function newConstraint($name, $options = null)
{
if (strpos($name, '\\') !== false && class_exists($name)) {
$className = (string) $name;
@@ -59,7 +83,7 @@ protected function newConstraint($name, $options)
$className = $this->namespaces[$prefix].$className;
} else {
- $className = 'Symfony\\Component\\Validator\\Constraints\\'.$name;
+ $className = self::DEFAULT_NAMESPACE.$name;
}
return new $className($options);
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php
index 2a1db47d6fcd..af0bf5239a39 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php
@@ -19,8 +19,16 @@
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
+/**
+ * Loads validation metadata using a Doctrine annotation {@link Reader}.
+ *
+ * @author Bernhard Schussek
+ */
class AnnotationLoader implements LoaderInterface
{
+ /**
+ * @var Reader
+ */
protected $reader;
public function __construct(Reader $reader)
@@ -35,7 +43,7 @@ public function loadClassMetadata(ClassMetadata $metadata)
{
$reflClass = $metadata->getReflectionClass();
$className = $reflClass->name;
- $loaded = false;
+ $success = false;
foreach ($this->reader->getClassAnnotations($reflClass) as $constraint) {
if ($constraint instanceof GroupSequence) {
@@ -46,7 +54,7 @@ public function loadClassMetadata(ClassMetadata $metadata)
$metadata->addConstraint($constraint);
}
- $loaded = true;
+ $success = true;
}
foreach ($reflClass->getProperties() as $property) {
@@ -56,7 +64,7 @@ public function loadClassMetadata(ClassMetadata $metadata)
$metadata->addPropertyConstraint($property->name, $constraint);
}
- $loaded = true;
+ $success = true;
}
}
}
@@ -77,11 +85,11 @@ public function loadClassMetadata(ClassMetadata $metadata)
}
}
- $loaded = true;
+ $success = true;
}
}
}
- return $loaded;
+ return $success;
}
}
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/FileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/FileLoader.php
index 2b8c6c04c0fa..326bbdfe75c7 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/FileLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/FileLoader.php
@@ -13,26 +13,42 @@
use Symfony\Component\Validator\Exception\MappingException;
+/**
+ * Base loader for loading validation metadata from a file.
+ *
+ * @author Bernhard Schussek
+ *
+ * @see YamlFileLoader
+ * @see XmlFileLoader
+ */
abstract class FileLoader extends AbstractLoader
{
+ /**
+ * The file to load.
+ *
+ * @var string
+ */
protected $file;
/**
- * Constructor.
+ * Creates a new loader.
*
* @param string $file The mapping file to load
*
- * @throws MappingException if the mapping file does not exist
- * @throws MappingException if the mapping file is not readable
+ * @throws MappingException If the file does not exist or is not readable
*/
public function __construct($file)
{
if (!is_file($file)) {
- throw new MappingException(sprintf('The mapping file %s does not exist', $file));
+ throw new MappingException(sprintf('The mapping file "%s" does not exist', $file));
}
if (!is_readable($file)) {
- throw new MappingException(sprintf('The mapping file %s is not readable', $file));
+ throw new MappingException(sprintf('The mapping file "%s" is not readable', $file));
+ }
+
+ if (!stream_is_local($this->file)) {
+ throw new MappingException(sprintf('The mapping file "%s" is not a local file', $file));
}
$this->file = $file;
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/FilesLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/FilesLoader.php
index a631093296e5..571c7e7abb24 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/FilesLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/FilesLoader.php
@@ -12,21 +12,20 @@
namespace Symfony\Component\Validator\Mapping\Loader;
/**
- * Creates mapping loaders for array of files.
- *
- * Abstract class, used by
+ * Base loader for loading validation metadata from a list of files.
*
* @author Bulat Shakirzyanov
+ * @author Bernhard Schussek
*
- * @see YamlFileLoader
- * @see XmlFileLoader
+ * @see YamlFilesLoader
+ * @see XmlFilesLoader
*/
abstract class FilesLoader extends LoaderChain
{
/**
- * Array of mapping files.
+ * Creates a new loader.
*
- * @param array $paths Array of file paths
+ * @param array $paths An array of file paths
*/
public function __construct(array $paths)
{
@@ -34,15 +33,16 @@ public function __construct(array $paths)
}
/**
- * Array of mapping files.
+ * Returns an array of file loaders for the given file paths.
*
- * @param array $paths Array of file paths
+ * @param array $paths An array of file paths
*
- * @return LoaderInterface[] Array of metadata loaders
+ * @return LoaderInterface[] The metadata loaders
*/
protected function getFileLoaders($paths)
{
$loaders = array();
+
foreach ($paths as $path) {
$loaders[] = $this->getFileLoaderInstance($path);
}
@@ -51,11 +51,11 @@ protected function getFileLoaders($paths)
}
/**
- * Takes mapping file path.
+ * Creates a loader for the given file path.
*
- * @param string $file
+ * @param string $path The file path
*
- * @return LoaderInterface
+ * @return LoaderInterface The created loader
*/
- abstract protected function getFileLoaderInstance($file);
+ abstract protected function getFileLoaderInstance($path);
}
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/LoaderChain.php b/src/Symfony/Component/Validator/Mapping/Loader/LoaderChain.php
index c3cacdb4ba48..970d9068d8ff 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/LoaderChain.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/LoaderChain.php
@@ -15,25 +15,25 @@
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
- * Calls multiple LoaderInterface instances in a chain.
+ * Loads validation metadata from multiple {@link LoaderInterface} instances.
*
- * This class accepts multiple instances of LoaderInterface to be passed to the
- * constructor. When loadClassMetadata() is called, the same method is called
- * in all of these loaders, regardless of whether any of them was
- * successful or not.
+ * Pass the loaders when constructing the chain. Once
+ * {@link loadClassMetadata()} is called, that method will be called on all
+ * loaders in the chain.
*
* @author Bernhard Schussek
*/
class LoaderChain implements LoaderInterface
{
+ /**
+ * @var LoaderInterface[]
+ */
protected $loaders;
/**
- * Accepts a list of LoaderInterface instances.
- *
- * @param LoaderInterface[] $loaders An array of LoaderInterface instances
+ * @param LoaderInterface[] $loaders The metadata loaders to use
*
- * @throws MappingException If any of the loaders does not implement LoaderInterface
+ * @throws MappingException If any of the loaders has an invalid type
*/
public function __construct(array $loaders)
{
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/LoaderInterface.php b/src/Symfony/Component/Validator/Mapping/Loader/LoaderInterface.php
index 43358ad1d92f..5dadc82eb809 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/LoaderInterface.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/LoaderInterface.php
@@ -13,14 +13,19 @@
use Symfony\Component\Validator\Mapping\ClassMetadata;
+/**
+ * Loads validation metadata into {@link ClassMetadata} instances.
+ *
+ * @author Bernhard Schussek
+ */
interface LoaderInterface
{
/**
- * Load a Class Metadata.
+ * Loads validation metadata into a {@link ClassMetadata} instance.
*
- * @param ClassMetadata $metadata A metadata
+ * @param ClassMetadata $metadata The metadata to load
*
- * @return bool
+ * @return bool Whether the loader succeeded
*/
public function loadClassMetadata(ClassMetadata $metadata);
}
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/StaticMethodLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/StaticMethodLoader.php
index 54dcc57cc767..4ff22573acc3 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/StaticMethodLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/StaticMethodLoader.php
@@ -14,10 +14,25 @@
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
+/**
+ * Loads validation metadata by calling a static method on the loaded class.
+ *
+ * @author Bernhard Schussek
+ */
class StaticMethodLoader implements LoaderInterface
{
+ /**
+ * The name of the method to call.
+ *
+ * @var string
+ */
protected $methodName;
+ /**
+ * Creates a new loader.
+ *
+ * @param string $methodName The name of the static method to call
+ */
public function __construct($methodName = 'loadValidatorMetadata')
{
$this->methodName = $methodName;
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php
index 5d63f4018437..2961b0080ee3 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php
@@ -15,10 +15,15 @@
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
+/**
+ * Loads validation metadata from an XML file.
+ *
+ * @author Bernhard Schussek
+ */
class XmlFileLoader extends FileLoader
{
/**
- * An array of SimpleXMLElement instances.
+ * The XML nodes of the mapping file.
*
* @var \SimpleXMLElement[]|null
*/
@@ -30,9 +35,12 @@ class XmlFileLoader extends FileLoader
public function loadClassMetadata(ClassMetadata $metadata)
{
if (null === $this->classes) {
- $this->classes = array();
+ // This method may throw an exception. Do not modify the class'
+ // state before it completes
$xml = $this->parseFile($this->file);
+ $this->classes = array();
+
foreach ($xml->namespace as $namespace) {
$this->addNamespaceAlias((string) $namespace['prefix'], trim((string) $namespace));
}
@@ -43,33 +51,9 @@ public function loadClassMetadata(ClassMetadata $metadata)
}
if (isset($this->classes[$metadata->getClassName()])) {
- $xml = $this->classes[$metadata->getClassName()];
-
- foreach ($xml->{'group-sequence-provider'} as $provider) {
- $metadata->setGroupSequenceProvider(true);
- }
-
- foreach ($xml->{'group-sequence'} as $groupSequence) {
- if (count($groupSequence->value) > 0) {
- $metadata->setGroupSequence($this->parseValues($groupSequence[0]->value));
- }
- }
-
- foreach ($this->parseConstraints($xml->constraint) as $constraint) {
- $metadata->addConstraint($constraint);
- }
+ $classDescription = $this->classes[$metadata->getClassName()];
- foreach ($xml->property as $property) {
- foreach ($this->parseConstraints($property->constraint) as $constraint) {
- $metadata->addPropertyConstraint((string) $property['name'], $constraint);
- }
- }
-
- foreach ($xml->getter as $getter) {
- foreach ($this->parseConstraints($getter->constraint) as $constraint) {
- $metadata->addGetterConstraint((string) $getter['property'], $constraint);
- }
- }
+ $this->loadClassMetadataFromXml($metadata, $classDescription);
return true;
}
@@ -179,22 +163,57 @@ protected function parseOptions(\SimpleXMLElement $nodes)
}
/**
- * Parse a XML File.
+ * Loads the XML class descriptions from the given file.
*
- * @param string $file Path of file
+ * @param string $path The path of the XML file
*
- * @return \SimpleXMLElement
+ * @return \SimpleXMLElement The class descriptions
*
- * @throws MappingException
+ * @throws MappingException If the file could not be loaded
*/
- protected function parseFile($file)
+ protected function parseFile($path)
{
try {
- $dom = XmlUtils::loadFile($file, __DIR__.'/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd');
+ $dom = XmlUtils::loadFile($path, __DIR__.'/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd');
} catch (\Exception $e) {
throw new MappingException($e->getMessage(), $e->getCode(), $e);
}
return simplexml_import_dom($dom);
}
+
+ /**
+ * Loads the validation metadata from the given XML class description.
+ *
+ * @param ClassMetadata $metadata The metadata to load
+ * @param array $classDescription The XML class description
+ */
+ private function loadClassMetadataFromXml(ClassMetadata $metadata, $classDescription)
+ {
+ foreach ($classDescription->{'group-sequence-provider'} as $_) {
+ $metadata->setGroupSequenceProvider(true);
+ }
+
+ foreach ($classDescription->{'group-sequence'} as $groupSequence) {
+ if (count($groupSequence->value) > 0) {
+ $metadata->setGroupSequence($this->parseValues($groupSequence[0]->value));
+ }
+ }
+
+ foreach ($this->parseConstraints($classDescription->constraint) as $constraint) {
+ $metadata->addConstraint($constraint);
+ }
+
+ foreach ($classDescription->property as $property) {
+ foreach ($this->parseConstraints($property->constraint) as $constraint) {
+ $metadata->addPropertyConstraint((string) $property['name'], $constraint);
+ }
+ }
+
+ foreach ($classDescription->getter as $getter) {
+ foreach ($this->parseConstraints($getter->constraint) as $constraint) {
+ $metadata->addGetterConstraint((string) $getter['property'], $constraint);
+ }
+ }
+ }
}
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/XmlFilesLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/XmlFilesLoader.php
index 3b7043feafc9..6017c3f44d23 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/XmlFilesLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/XmlFilesLoader.php
@@ -12,9 +12,10 @@
namespace Symfony\Component\Validator\Mapping\Loader;
/**
- * Loads multiple xml mapping files.
+ * Loads validation metadata from a list of XML files.
*
* @author Bulat Shakirzyanov
+ * @author Bernhard Schussek
*
* @see FilesLoader
*/
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php
index 2737ccadb369..e293a6eb3817 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php
@@ -14,10 +14,13 @@
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Yaml\Parser as YamlParser;
+/**
+ * Loads validation metadata from a YAML file.
+ *
+ * @author Bernhard Schussek
+ */
class YamlFileLoader extends FileLoader
{
- private $yamlParser;
-
/**
* An array of YAML class descriptions.
*
@@ -25,35 +28,30 @@ class YamlFileLoader extends FileLoader
*/
protected $classes = null;
+ /**
+ * Caches the used YAML parser.
+ *
+ * @var YamlParser
+ */
+ private $yamlParser;
+
/**
* {@inheritdoc}
*/
public function loadClassMetadata(ClassMetadata $metadata)
{
if (null === $this->classes) {
- if (!stream_is_local($this->file)) {
- throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $this->file));
- }
-
- if (!file_exists($this->file)) {
- throw new \InvalidArgumentException(sprintf('File "%s" not found.', $this->file));
- }
-
if (null === $this->yamlParser) {
$this->yamlParser = new YamlParser();
}
- $this->classes = $this->yamlParser->parse(file_get_contents($this->file));
-
- // empty file
- if (null === $this->classes) {
+ // This method may throw an exception. Do not modify the class'
+ // state before it completes
+ if (false === ($classes = $this->parseFile($this->file))) {
return false;
}
- // not an array
- if (!is_array($this->classes)) {
- throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $this->file));
- }
+ $this->classes = $classes;
if (isset($this->classes['namespaces'])) {
foreach ($this->classes['namespaces'] as $alias => $namespace) {
@@ -64,44 +62,10 @@ public function loadClassMetadata(ClassMetadata $metadata)
}
}
- // TODO validation
-
if (isset($this->classes[$metadata->getClassName()])) {
- $yaml = $this->classes[$metadata->getClassName()];
+ $classDescription = $this->classes[$metadata->getClassName()];
- if (isset($yaml['group_sequence_provider'])) {
- $metadata->setGroupSequenceProvider((bool) $yaml['group_sequence_provider']);
- }
-
- if (isset($yaml['group_sequence'])) {
- $metadata->setGroupSequence($yaml['group_sequence']);
- }
-
- if (isset($yaml['constraints']) && is_array($yaml['constraints'])) {
- foreach ($this->parseNodes($yaml['constraints']) as $constraint) {
- $metadata->addConstraint($constraint);
- }
- }
-
- if (isset($yaml['properties']) && is_array($yaml['properties'])) {
- foreach ($yaml['properties'] as $property => $constraints) {
- if (null !== $constraints) {
- foreach ($this->parseNodes($constraints) as $constraint) {
- $metadata->addPropertyConstraint($property, $constraint);
- }
- }
- }
- }
-
- if (isset($yaml['getters']) && is_array($yaml['getters'])) {
- foreach ($yaml['getters'] as $getter => $constraints) {
- if (null !== $constraints) {
- foreach ($this->parseNodes($constraints) as $constraint) {
- $metadata->addGetterConstraint($getter, $constraint);
- }
- }
- }
- }
+ $this->loadClassMetadataFromYaml($metadata, $classDescription);
return true;
}
@@ -140,4 +104,76 @@ protected function parseNodes(array $nodes)
return $values;
}
+
+ /**
+ * Loads the YAML class descriptions from the given file.
+ *
+ * @param string $path The path of the YAML file
+ *
+ * @return array|null The class descriptions or null, if the file was empty
+ *
+ * @throws \InvalidArgumentException If the file could not be loaded or did
+ * not contain a YAML array
+ */
+ private function parseFile($path)
+ {
+ $classes = $this->yamlParser->parse(file_get_contents($path));
+
+ // empty file
+ if (null === $classes) {
+ return;
+ }
+
+ // not an array
+ if (!is_array($classes)) {
+ throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $this->file));
+ }
+
+ return $classes;
+ }
+
+ /**
+ * Loads the validation metadata from the given YAML class description.
+ *
+ * @param ClassMetadata $metadata The metadata to load
+ * @param array $classDescription The YAML class description
+ */
+ private function loadClassMetadataFromYaml(ClassMetadata $metadata, array $classDescription)
+ {
+ if (isset($classDescription['group_sequence_provider'])) {
+ $metadata->setGroupSequenceProvider(
+ (bool) $classDescription['group_sequence_provider']
+ );
+ }
+
+ if (isset($classDescription['group_sequence'])) {
+ $metadata->setGroupSequence($classDescription['group_sequence']);
+ }
+
+ if (isset($classDescription['constraints']) && is_array($classDescription['constraints'])) {
+ foreach ($this->parseNodes($classDescription['constraints']) as $constraint) {
+ $metadata->addConstraint($constraint);
+ }
+ }
+
+ if (isset($classDescription['properties']) && is_array($classDescription['properties'])) {
+ foreach ($classDescription['properties'] as $property => $constraints) {
+ if (null !== $constraints) {
+ foreach ($this->parseNodes($constraints) as $constraint) {
+ $metadata->addPropertyConstraint($property, $constraint);
+ }
+ }
+ }
+ }
+
+ if (isset($classDescription['getters']) && is_array($classDescription['getters'])) {
+ foreach ($classDescription['getters'] as $getter => $constraints) {
+ if (null !== $constraints) {
+ foreach ($this->parseNodes($constraints) as $constraint) {
+ $metadata->addGetterConstraint($getter, $constraint);
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/YamlFilesLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/YamlFilesLoader.php
index e01def2f673c..235856f5b980 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/YamlFilesLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/YamlFilesLoader.php
@@ -12,9 +12,10 @@
namespace Symfony\Component\Validator\Mapping\Loader;
/**
- * Loads multiple yaml mapping files.
+ * Loads validation metadata from a list of YAML files.
*
* @author Bulat Shakirzyanov
+ * @author Bernhard Schussek
*
* @see FilesLoader
*/
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php
index e2b27f0bb610..8ab2065ae5fa 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php
@@ -19,6 +19,7 @@
use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Validator\Constraints\True;
+use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
@@ -105,15 +106,28 @@ public function testLoadGroupSequenceProvider()
$this->assertEquals($expected, $metadata);
}
+ public function testThrowExceptionIfDocTypeIsSet()
+ {
+ $loader = new XmlFileLoader(__DIR__.'/withdoctype.xml');
+ $metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
+
+ $this->setExpectedException('\Symfony\Component\Validator\Exception\MappingException');
+ $loader->loadClassMetadata($metadata);
+ }
+
/**
- * @expectedException \Symfony\Component\Validator\Exception\MappingException
- * @expectedExceptionMessage Document types are not allowed.
+ * @see https://github.com/symfony/symfony/pull/12158
*/
- public function testDocTypeIsNotAllowed()
+ public function testDoNotModifyStateIfExceptionIsThrown()
{
$loader = new XmlFileLoader(__DIR__.'/withdoctype.xml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
- $loader->loadClassMetadata($metadata);
+ try {
+ $loader->loadClassMetadata($metadata);
+ } catch (MappingException $e) {
+ $this->setExpectedException('\Symfony\Component\Validator\Exception\MappingException');
+ $loader->loadClassMetadata($metadata);
+ }
}
}
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php
index aeccf0c2836a..806a2b049be8 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php
@@ -33,16 +33,31 @@ public function testLoadClassMetadataReturnsFalseIfEmpty()
$this->assertFalse($loader->loadClassMetadata($metadata));
}
- /**
- * @expectedException \InvalidArgumentException
- */
public function testLoadClassMetadataThrowsExceptionIfNotAnArray()
{
$loader = new YamlFileLoader(__DIR__.'/nonvalid-mapping.yml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
+
+ $this->setExpectedException('\InvalidArgumentException');
$loader->loadClassMetadata($metadata);
}
+ /**
+ * @see https://github.com/symfony/symfony/pull/12158
+ */
+ public function testDoNotModifyStateIfExceptionIsThrown()
+ {
+ $loader = new YamlFileLoader(__DIR__.'/nonvalid-mapping.yml');
+ $metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
+ try {
+ $loader->loadClassMetadata($metadata);
+ } catch (\InvalidArgumentException $e) {
+ // Call again. Again an exception should be thrown
+ $this->setExpectedException('\InvalidArgumentException');
+ $loader->loadClassMetadata($metadata);
+ }
+ }
+
public function testLoadClassMetadataReturnsTrueIfSuccessful()
{
$loader = new YamlFileLoader(__DIR__.'/constraint-mapping.yml');
diff --git a/src/Symfony/Component/VarDumper/.gitignore b/src/Symfony/Component/VarDumper/.gitignore
new file mode 100644
index 000000000000..5414c2c655e7
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+phpunit.xml
+vendor/