diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 44c1a168f934..a070e19ae521 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -803,7 +803,7 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde if ('file' === $config['cache']) { $cacheDir = $container->getParameterBag()->resolveValue($config['file_cache_dir']); - if (!is_dir($cacheDir) && false === @mkdir($cacheDir, 0777, true)) { + if (!is_dir($cacheDir) && false === @mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $cacheDir)); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php index 5e8773b742c1..0d75129fb9a1 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php @@ -23,6 +23,12 @@ interface SecurityFactoryInterface { public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint); + /** + * Defines the position at which the provider is called. + * Possible values: pre_auth, form, http, and remember_me. + * + * @return string + */ public function getPosition(); public function getKey(); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig index 2281ada06850..ca1c937545ad 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig @@ -6,11 +6,11 @@ {% set link = collector.controller.file|file_link(collector.controller.line) %} {% if collector.controller.method %} {{ collector.controller.class|abbr_class }} - + {{ collector.controller.method }} {% else %} - {{ collector.controller.class|abbr_class }} + {{ collector.controller.class|abbr_class }} {% endif %} {% else %} {{ collector.controller }} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index 0db84ed771dc..b21c515c9201 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -173,7 +173,7 @@ var addEventListener; - if (document.addEventListener) { + if (document.attachEvent) { addEventListener = function (element, eventName, callback) { element.attachEvent('on' + eventName, callback); }; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.html.twig index 00384ec590ea..dbe3d0cdbf7b 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.html.twig @@ -24,7 +24,7 @@
{% endif %} -
+
{% 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/