Merge branch '2.7'

This commit is contained in:
Tobias Schultze 2015-02-21 15:11:30 +01:00
commit b05fce2857
35 changed files with 408 additions and 316 deletions

View File

@ -803,7 +803,7 @@ class FrameworkExtension extends Extension
if ('file' === $config['cache']) { if ('file' === $config['cache']) {
$cacheDir = $container->getParameterBag()->resolveValue($config['file_cache_dir']); $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)); throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $cacheDir));
} }

View File

@ -23,6 +23,12 @@ interface SecurityFactoryInterface
{ {
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint); 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 getPosition();
public function getKey(); public function getKey();

View File

@ -6,11 +6,11 @@
{% set link = collector.controller.file|file_link(collector.controller.line) %} {% set link = collector.controller.file|file_link(collector.controller.line) %}
{% if collector.controller.method %} {% if collector.controller.method %}
<span class="sf-toolbar-info-class sf-toolbar-info-with-next-pointer">{{ collector.controller.class|abbr_class }}</span> <span class="sf-toolbar-info-class sf-toolbar-info-with-next-pointer">{{ collector.controller.class|abbr_class }}</span>
<span class="sf-toolbar-info-method" onclick="{% if link %}window.location='{{link|e('js')}}';window.event.stopPropagation();return false;{% endif %}"> <span class="sf-toolbar-info-method"{% if link %} onclick="window.location='{{link|e('js')}}';window.event.stopPropagation();return false;"{% endif %}>
{{ collector.controller.method }} {{ collector.controller.method }}
</span> </span>
{% else %} {% else %}
<span class="sf-toolbar-info-class" onclick="{% if link %}window.location='{{link|e('js')}}';window.event.stopPropagation();return false;{% endif %}">{{ collector.controller.class|abbr_class }}</span> <span class="sf-toolbar-info-class"{% if link %} onclick="window.location='{{link|e('js')}}';window.event.stopPropagation();return false;"{% endif %}>{{ collector.controller.class|abbr_class }}</span>
{% endif %} {% endif %}
{% else %} {% else %}
<span class="sf-toolbar-info-class">{{ collector.controller }}</span> <span class="sf-toolbar-info-class">{{ collector.controller }}</span>

View File

@ -173,7 +173,7 @@
var addEventListener; var addEventListener;
if (document.addEventListener) { if (document.attachEvent) {
addEventListener = function (element, eventName, callback) { addEventListener = function (element, eventName, callback) {
element.attachEvent('on' + eventName, callback); element.attachEvent('on' + eventName, callback);
}; };

View File

@ -24,7 +24,7 @@
<div id="sfToolbarClearer-{{ token }}" style="clear: both; height: 38px;"></div> <div id="sfToolbarClearer-{{ token }}" style="clear: both; height: 38px;"></div>
{% endif %} {% endif %}
<div id="sfToolbarMainContent-{{ token }}" class="sf-toolbarreset" data-no-turbolink> <div id="sfToolbarMainContent-{{ token }}" class="sf-toolbarreset clear-fix" data-no-turbolink>
{% for name, template in templates %} {% for name, template in templates %}
{{ template.renderblock('toolbar', { {{ template.renderblock('toolbar', {
'collector': profile.getcollector(name), 'collector': profile.getcollector(name),

View File

@ -22,6 +22,11 @@ class EnumNodeDefinition extends ScalarNodeDefinition
{ {
private $values; private $values;
/**
* @param array $values
*
* @return EnumNodeDefinition|$this
*/
public function values(array $values) public function values(array $values)
{ {
$values = array_unique($values); $values = array_unique($values);

View File

@ -56,7 +56,7 @@ abstract class NodeDefinition implements NodeParentInterface
* *
* @param NodeParentInterface $parent The parent * @param NodeParentInterface $parent The parent
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function setParent(NodeParentInterface $parent) public function setParent(NodeParentInterface $parent)
{ {
@ -70,7 +70,7 @@ abstract class NodeDefinition implements NodeParentInterface
* *
* @param string $info The info text * @param string $info The info text
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function info($info) public function info($info)
{ {
@ -82,7 +82,7 @@ abstract class NodeDefinition implements NodeParentInterface
* *
* @param string|array $example * @param string|array $example
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function example($example) public function example($example)
{ {
@ -95,7 +95,7 @@ abstract class NodeDefinition implements NodeParentInterface
* @param string $key * @param string $key
* @param mixed $value * @param mixed $value
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function attribute($key, $value) public function attribute($key, $value)
{ {
@ -146,7 +146,7 @@ abstract class NodeDefinition implements NodeParentInterface
* *
* @param mixed $value The default value * @param mixed $value The default value
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function defaultValue($value) public function defaultValue($value)
{ {
@ -159,7 +159,7 @@ abstract class NodeDefinition implements NodeParentInterface
/** /**
* Sets the node as required. * Sets the node as required.
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function isRequired() public function isRequired()
{ {
@ -173,7 +173,7 @@ abstract class NodeDefinition implements NodeParentInterface
* *
* @param mixed $value * @param mixed $value
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function treatNullLike($value) public function treatNullLike($value)
{ {
@ -187,7 +187,7 @@ abstract class NodeDefinition implements NodeParentInterface
* *
* @param mixed $value * @param mixed $value
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function treatTrueLike($value) public function treatTrueLike($value)
{ {
@ -201,7 +201,7 @@ abstract class NodeDefinition implements NodeParentInterface
* *
* @param mixed $value * @param mixed $value
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function treatFalseLike($value) public function treatFalseLike($value)
{ {
@ -213,7 +213,7 @@ abstract class NodeDefinition implements NodeParentInterface
/** /**
* Sets null as the default value. * Sets null as the default value.
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function defaultNull() public function defaultNull()
{ {
@ -223,7 +223,7 @@ abstract class NodeDefinition implements NodeParentInterface
/** /**
* Sets true as the default value. * Sets true as the default value.
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function defaultTrue() public function defaultTrue()
{ {
@ -233,7 +233,7 @@ abstract class NodeDefinition implements NodeParentInterface
/** /**
* Sets false as the default value. * Sets false as the default value.
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function defaultFalse() public function defaultFalse()
{ {
@ -253,7 +253,7 @@ abstract class NodeDefinition implements NodeParentInterface
/** /**
* Denies the node value being empty. * Denies the node value being empty.
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function cannotBeEmpty() public function cannotBeEmpty()
{ {
@ -281,7 +281,7 @@ abstract class NodeDefinition implements NodeParentInterface
* *
* @param bool $deny Whether the overwriting is forbidden or not * @param bool $deny Whether the overwriting is forbidden or not
* *
* @return NodeDefinition * @return NodeDefinition|$this
*/ */
public function cannotBeOverwritten($deny = true) public function cannotBeOverwritten($deny = true)
{ {

View File

@ -170,6 +170,10 @@ class Command
/** /**
* Interacts with the user. * 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 InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance * @param OutputInterface $output An OutputInterface instance
*/ */

View File

@ -100,8 +100,10 @@ class ArrayInput extends Input
$values = (array) $values; $values = (array) $values;
foreach ($this->parameters as $k => $v) { foreach ($this->parameters as $k => $v) {
if (is_int($k) && in_array($v, $values)) { if (is_int($k)) {
return true; if (in_array($v, $values)) {
return true;
}
} elseif (in_array($k, $values)) { } elseif (in_array($k, $values)) {
return $v; return $v;
} }

View File

@ -38,6 +38,15 @@ class ArrayInputTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if an option is present in the passed parameters'); $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() public function testParseArguments()
{ {
$input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name')))); $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'))));

View File

@ -341,7 +341,7 @@ class InputDefinitionTest extends \PHPUnit_Framework_TestCase
new InputOption('foo7', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, '', array(1, 2)), new InputOption('foo7', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, '', array(1, 2)),
)); ));
$defaults = array( $defaults = array(
'foo1' => null, 'foo1' => false,
'foo2' => null, 'foo2' => null,
'foo3' => 'default', 'foo3' => 'default',
'foo4' => null, 'foo4' => null,
@ -349,7 +349,7 @@ class InputDefinitionTest extends \PHPUnit_Framework_TestCase
'foo6' => array(), 'foo6' => array(),
'foo7' => array(1, 2), '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() public function testGetSynopsis()

View File

@ -234,7 +234,7 @@ class Finder implements \IteratorAggregate, \Countable
* $finder->date('> now - 2 hours'); * $finder->date('> now - 2 hours');
* $finder->date('>= 2005-10-15'); * $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 * @return Finder The current Finder instance
* *

View File

@ -10,8 +10,8 @@ CHANGELOG
2.6.2 2.6.2
----- -----
* Added back the `model_timezone` and `view_timezone` options for `TimeType`, `DateType` * Added back the `model_timezone` and `view_timezone` options for `TimeType`, `DateType`
and `BirthdayType` and `BirthdayType`
2.6.0 2.6.0
----- -----

View File

@ -130,7 +130,7 @@ class File extends \SplFileInfo
protected function getTargetFile($directory, $name = null) protected function getTargetFile($directory, $name = null)
{ {
if (!is_dir($directory)) { 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)); throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
} }
} elseif (!is_writable($directory)) { } elseif (!is_writable($directory)) {

View File

@ -616,8 +616,8 @@ class Request
* The following header keys are supported: * The following header keys are supported:
* *
* * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp()) * * 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_HOST: defaults to X-Forwarded-Host (see getHost())
* * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getClientPort()) * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getPort())
* * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure()) * * 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. * Setting an empty value allows to disable the trusted header for the given key.

View File

@ -110,7 +110,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
if (false === $name) { if (false === $name) {
$name = strtr($file, '\\', '/'); $name = strtr($file, '\\', '/');
$name = substr($file, strrpos($file, '/') + 1); $name = substr($name, strrpos($name, '/') + 1);
} }
$this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt'); $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');

View File

@ -578,7 +578,7 @@ abstract class Kernel implements KernelInterface, TerminableInterface
{ {
foreach (array('cache' => $this->getCacheDir(), 'logs' => $this->getLogDir()) as $name => $dir) { foreach (array('cache' => $this->getCacheDir(), 'logs' => $this->getLogDir()) as $name => $dir) {
if (!is_dir($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)); throw new \RuntimeException(sprintf("Unable to create the %s directory (%s)\n", $name, $dir));
} }
} elseif (!is_writable($dir)) { } elseif (!is_writable($dir)) {

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\PropertyAccess; 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\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException; use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException; use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
@ -51,15 +51,8 @@ class PropertyAccessor implements PropertyAccessorInterface
*/ */
public function getValue($objectOrArray, $propertyPath) public function getValue($objectOrArray, $propertyPath)
{ {
if (is_string($propertyPath)) { if (!$propertyPath instanceof PropertyPathInterface) {
$propertyPath = new PropertyPath($propertyPath); $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); $propertyValues = & $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
@ -72,15 +65,8 @@ class PropertyAccessor implements PropertyAccessorInterface
*/ */
public function setValue(&$objectOrArray, $propertyPath, $value) public function setValue(&$objectOrArray, $propertyPath, $value)
{ {
if (is_string($propertyPath)) { if (!$propertyPath instanceof PropertyPathInterface) {
$propertyPath = new PropertyPath($propertyPath); $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); $propertyValues = & $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength() - 1);
@ -96,10 +82,6 @@ class PropertyAccessor implements PropertyAccessorInterface
$objectOrArray = & $propertyValues[$i][self::VALUE]; $objectOrArray = & $propertyValues[$i][self::VALUE];
if ($overwrite) { if ($overwrite) {
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
throw new UnexpectedTypeException($objectOrArray, $propertyPath, $i);
}
$property = $propertyPath->getElement($i); $property = $propertyPath->getElement($i);
if ($propertyPath->isIndex($i)) { if ($propertyPath->isIndex($i)) {
@ -119,24 +101,15 @@ class PropertyAccessor implements PropertyAccessorInterface
*/ */
public function isReadable($objectOrArray, $propertyPath) public function isReadable($objectOrArray, $propertyPath)
{ {
if (is_string($propertyPath)) { if (!$propertyPath instanceof PropertyPathInterface) {
$propertyPath = new PropertyPath($propertyPath); $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 { try {
$this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices); $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
return true; return true;
} catch (NoSuchIndexException $e) { } catch (AccessException $e) {
return false;
} catch (NoSuchPropertyException $e) {
return false; return false;
} catch (UnexpectedTypeException $e) { } catch (UnexpectedTypeException $e) {
return false; return false;
@ -148,15 +121,8 @@ class PropertyAccessor implements PropertyAccessorInterface
*/ */
public function isWritable($objectOrArray, $propertyPath) public function isWritable($objectOrArray, $propertyPath)
{ {
if (is_string($propertyPath)) { if (!$propertyPath instanceof PropertyPathInterface) {
$propertyPath = new PropertyPath($propertyPath); $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 { try {
@ -173,10 +139,6 @@ class PropertyAccessor implements PropertyAccessorInterface
$objectOrArray = $propertyValues[$i][self::VALUE]; $objectOrArray = $propertyValues[$i][self::VALUE];
if ($overwrite) { if ($overwrite) {
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
return false;
}
$property = $propertyPath->getElement($i); $property = $propertyPath->getElement($i);
if ($propertyPath->isIndex($i)) { if ($propertyPath->isIndex($i)) {
@ -194,9 +156,9 @@ class PropertyAccessor implements PropertyAccessorInterface
} }
return true; return true;
} catch (NoSuchIndexException $e) { } catch (AccessException $e) {
return false; return false;
} catch (NoSuchPropertyException $e) { } catch (UnexpectedTypeException $e) {
return false; return false;
} }
} }
@ -217,13 +179,13 @@ class PropertyAccessor implements PropertyAccessorInterface
*/ */
private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex, $ignoreInvalidIndices = true) private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex, $ignoreInvalidIndices = true)
{ {
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
throw new UnexpectedTypeException($objectOrArray, $propertyPath, 0);
}
$propertyValues = array(); $propertyValues = array();
for ($i = 0; $i < $lastIndex; ++$i) { for ($i = 0; $i < $lastIndex; ++$i) {
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
throw new UnexpectedTypeException($objectOrArray, $propertyPath, $i);
}
$property = $propertyPath->getElement($i); $property = $propertyPath->getElement($i);
$isIndex = $propertyPath->isIndex($i); $isIndex = $propertyPath->isIndex($i);
@ -264,6 +226,11 @@ class PropertyAccessor implements PropertyAccessorInterface
$objectOrArray = & $propertyValue[self::VALUE]; $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; $propertyValues[] = & $propertyValue;
} }

View File

@ -197,7 +197,7 @@ class StringUtil
} }
// Convert teeth to tooth, feet to foot // 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); return substr_replace($plural, 'oo', $pos, 2);
} }

View File

@ -29,6 +29,20 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
$this->propertyAccessor = new PropertyAccessor(); $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() public function getPathsWithMissingProperty()
{ {
return array( return array(
@ -138,39 +152,13 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
} }
/** /**
* @dataProvider getPathsWithUnexpectedType
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException * @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". * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on
*/ */
public function testGetValueThrowsExceptionIfNotObjectOrArray() public function testGetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $path)
{ {
$this->propertyAccessor->getValue('baz', 'foobar'); $this->propertyAccessor->getValue($objectOrArray, $path);
}
/**
* @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".
*/
public function testGetValueNestedExceptionMessage()
{
$this->propertyAccessor->getValue((object) array('foobar' => null), 'foobar.baz');
} }
/** /**
@ -260,47 +248,13 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
} }
/** /**
* @dataProvider getPathsWithUnexpectedType
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException * @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". * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on
*/ */
public function testSetValueThrowsExceptionIfNotObjectOrArray() public function testSetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $path)
{ {
$value = 'baz'; $this->propertyAccessor->setValue($objectOrArray, $path, '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" 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".
*/
public function testSetValueNestedExceptionMessage()
{
$value = (object) array('foobar' => null);
$this->propertyAccessor->setValue($value, 'foobar.baz', 'bam');
} }
public function testGetValueWhenArrayValueIsNull() public function testGetValueWhenArrayValueIsNull()
@ -362,19 +316,12 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty')); $this->assertTrue($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
} }
public function testIsReadableThrowsExceptionIfNotObjectOrArray() /**
* @dataProvider getPathsWithUnexpectedType
*/
public function testIsReadableReturnsFalseIfNotObjectOrArray($objectOrArray, $path)
{ {
$this->assertFalse($this->propertyAccessor->isReadable('baz', 'foobar')); $this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
}
public function testIsReadableThrowsExceptionIfNull()
{
$this->assertFalse($this->propertyAccessor->isReadable(null, 'foobar'));
}
public function testIsReadableThrowsExceptionIfEmpty()
{
$this->assertFalse($this->propertyAccessor->isReadable('', 'foobar'));
} }
/** /**
@ -430,19 +377,12 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty')); $this->assertTrue($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
} }
public function testNotObjectOrArrayIsNotWritable() /**
* @dataProvider getPathsWithUnexpectedType
*/
public function testIsWritableReturnsFalseIfNotObjectOrArray($objectOrArray, $path)
{ {
$this->assertFalse($this->propertyAccessor->isWritable('baz', 'foobar')); $this->assertFalse($this->propertyAccessor->isWritable($objectOrArray, $path));
}
public function testNullIsNotWritable()
{
$this->assertFalse($this->propertyAccessor->isWritable(null, 'foobar'));
}
public function testEmptyIsNotWritable()
{
$this->assertFalse($this->propertyAccessor->isWritable('', 'foobar'));
} }
public function getValidPropertyPaths() public function getValidPropertyPaths()

View File

@ -59,6 +59,7 @@ class StringUtilTest extends \PHPUnit_Framework_TestCase
array('data', array('daton', 'datum')), array('data', array('daton', 'datum')),
array('days', 'day'), array('days', 'day'),
array('discos', 'disco'), array('discos', 'disco'),
array('devices', array('devex', 'devix', 'device')),
array('drives', 'drive'), array('drives', 'drive'),
array('drivers', 'driver'), array('drivers', 'driver'),
array('dwarves', array('dwarf', 'dwarve', 'dwarff')), array('dwarves', array('dwarf', 'dwarve', 'dwarff')),
@ -67,6 +68,7 @@ class StringUtilTest extends \PHPUnit_Framework_TestCase
array('emphases', array('emphas', 'emphase', 'emphasis')), array('emphases', array('emphas', 'emphase', 'emphasis')),
array('faxes', 'fax'), array('faxes', 'fax'),
array('feet', 'foot'), array('feet', 'foot'),
array('feedback', 'feedback'),
array('foci', 'focus'), array('foci', 'focus'),
array('focuses', array('focus', 'focuse', 'focusis')), array('focuses', array('focus', 'focuse', 'focusis')),
array('formulae', 'formula'), array('formulae', 'formula'),

View File

@ -14,11 +14,25 @@ namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\MappingException; 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 <bschussek@gmail.com>
*/
abstract class AbstractLoader implements LoaderInterface 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 * @var array
*/ */
protected $namespaces = array(); protected $namespaces = array();
@ -26,6 +40,13 @@ abstract class AbstractLoader implements LoaderInterface
/** /**
* Adds a namespace alias. * 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 $alias The alias
* @param string $namespace The PHP namespace * @param string $namespace The PHP namespace
*/ */
@ -37,16 +58,19 @@ abstract class AbstractLoader implements LoaderInterface
/** /**
* Creates a new constraint instance for the given constraint name. * Creates a new constraint instance for the given constraint name.
* *
* @param string $name The constraint name. Either a constraint relative * @param string $name The constraint name. Either a constraint relative
* to the default constraint namespace, or a fully * to the default constraint namespace, or a fully
* qualified class name * qualified class name. Alternatively, the constraint
* @param mixed $options The constraint options * 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 * @return Constraint
* *
* @throws MappingException If the namespace prefix is undefined * @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)) { if (strpos($name, '\\') !== false && class_exists($name)) {
$className = (string) $name; $className = (string) $name;
@ -59,7 +83,7 @@ abstract class AbstractLoader implements LoaderInterface
$className = $this->namespaces[$prefix].$className; $className = $this->namespaces[$prefix].$className;
} else { } else {
$className = 'Symfony\\Component\\Validator\\Constraints\\'.$name; $className = self::DEFAULT_NAMESPACE.$name;
} }
return new $className($options); return new $className($options);

View File

@ -19,8 +19,16 @@ use Symfony\Component\Validator\Constraints\GroupSequenceProvider;
use Symfony\Component\Validator\Exception\MappingException; use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Loads validation metadata using a Doctrine annotation {@link Reader}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class AnnotationLoader implements LoaderInterface class AnnotationLoader implements LoaderInterface
{ {
/**
* @var Reader
*/
protected $reader; protected $reader;
public function __construct(Reader $reader) public function __construct(Reader $reader)
@ -35,7 +43,7 @@ class AnnotationLoader implements LoaderInterface
{ {
$reflClass = $metadata->getReflectionClass(); $reflClass = $metadata->getReflectionClass();
$className = $reflClass->name; $className = $reflClass->name;
$loaded = false; $success = false;
foreach ($this->reader->getClassAnnotations($reflClass) as $constraint) { foreach ($this->reader->getClassAnnotations($reflClass) as $constraint) {
if ($constraint instanceof GroupSequence) { if ($constraint instanceof GroupSequence) {
@ -46,7 +54,7 @@ class AnnotationLoader implements LoaderInterface
$metadata->addConstraint($constraint); $metadata->addConstraint($constraint);
} }
$loaded = true; $success = true;
} }
foreach ($reflClass->getProperties() as $property) { foreach ($reflClass->getProperties() as $property) {
@ -56,7 +64,7 @@ class AnnotationLoader implements LoaderInterface
$metadata->addPropertyConstraint($property->name, $constraint); $metadata->addPropertyConstraint($property->name, $constraint);
} }
$loaded = true; $success = true;
} }
} }
} }
@ -77,11 +85,11 @@ class AnnotationLoader implements LoaderInterface
} }
} }
$loaded = true; $success = true;
} }
} }
} }
return $loaded; return $success;
} }
} }

View File

@ -13,26 +13,42 @@ namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Exception\MappingException; use Symfony\Component\Validator\Exception\MappingException;
/**
* Base loader for loading validation metadata from a file.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see YamlFileLoader
* @see XmlFileLoader
*/
abstract class FileLoader extends AbstractLoader abstract class FileLoader extends AbstractLoader
{ {
/**
* The file to load.
*
* @var string
*/
protected $file; protected $file;
/** /**
* Constructor. * Creates a new loader.
* *
* @param string $file The mapping file to load * @param string $file The mapping file to load
* *
* @throws MappingException if the mapping file does not exist * @throws MappingException If the file does not exist or is not readable
* @throws MappingException if the mapping file is not readable
*/ */
public function __construct($file) public function __construct($file)
{ {
if (!is_file($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)) { 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; $this->file = $file;

View File

@ -12,21 +12,20 @@
namespace Symfony\Component\Validator\Mapping\Loader; namespace Symfony\Component\Validator\Mapping\Loader;
/** /**
* Creates mapping loaders for array of files. * Base loader for loading validation metadata from a list of files.
*
* Abstract class, used by
* *
* @author Bulat Shakirzyanov <mallluhuct@gmail.com> * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
* *
* @see YamlFileLoader * @see YamlFilesLoader
* @see XmlFileLoader * @see XmlFilesLoader
*/ */
abstract class FilesLoader extends LoaderChain 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) public function __construct(array $paths)
{ {
@ -34,15 +33,16 @@ abstract class FilesLoader extends LoaderChain
} }
/** /**
* 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) protected function getFileLoaders($paths)
{ {
$loaders = array(); $loaders = array();
foreach ($paths as $path) { foreach ($paths as $path) {
$loaders[] = $this->getFileLoaderInstance($path); $loaders[] = $this->getFileLoaderInstance($path);
} }
@ -51,11 +51,11 @@ abstract class FilesLoader extends LoaderChain
} }
/** /**
* 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);
} }

View File

@ -15,25 +15,25 @@ use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata; 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 * Pass the loaders when constructing the chain. Once
* constructor. When loadClassMetadata() is called, the same method is called * {@link loadClassMetadata()} is called, that method will be called on all
* in <em>all</em> of these loaders, regardless of whether any of them was * loaders in the chain.
* successful or not.
* *
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*/ */
class LoaderChain implements LoaderInterface class LoaderChain implements LoaderInterface
{ {
/**
* @var LoaderInterface[]
*/
protected $loaders; protected $loaders;
/** /**
* Accepts a list of LoaderInterface instances. * @param LoaderInterface[] $loaders The metadata loaders to use
* *
* @param LoaderInterface[] $loaders An array of LoaderInterface instances * @throws MappingException If any of the loaders has an invalid type
*
* @throws MappingException If any of the loaders does not implement LoaderInterface
*/ */
public function __construct(array $loaders) public function __construct(array $loaders)
{ {

View File

@ -13,14 +13,19 @@ namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Loads validation metadata into {@link ClassMetadata} instances.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface LoaderInterface 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); public function loadClassMetadata(ClassMetadata $metadata);
} }

View File

@ -14,10 +14,25 @@ namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Exception\MappingException; use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Loads validation metadata by calling a static method on the loaded class.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class StaticMethodLoader implements LoaderInterface class StaticMethodLoader implements LoaderInterface
{ {
/**
* The name of the method to call.
*
* @var string
*/
protected $methodName; protected $methodName;
/**
* Creates a new loader.
*
* @param string $methodName The name of the static method to call
*/
public function __construct($methodName = 'loadValidatorMetadata') public function __construct($methodName = 'loadValidatorMetadata')
{ {
$this->methodName = $methodName; $this->methodName = $methodName;

View File

@ -15,10 +15,15 @@ use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Validator\Exception\MappingException; use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Loads validation metadata from an XML file.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class XmlFileLoader extends FileLoader class XmlFileLoader extends FileLoader
{ {
/** /**
* An array of SimpleXMLElement instances. * The XML nodes of the mapping file.
* *
* @var \SimpleXMLElement[]|null * @var \SimpleXMLElement[]|null
*/ */
@ -30,9 +35,12 @@ class XmlFileLoader extends FileLoader
public function loadClassMetadata(ClassMetadata $metadata) public function loadClassMetadata(ClassMetadata $metadata)
{ {
if (null === $this->classes) { 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); $xml = $this->parseFile($this->file);
$this->classes = array();
foreach ($xml->namespace as $namespace) { foreach ($xml->namespace as $namespace) {
$this->addNamespaceAlias((string) $namespace['prefix'], trim((string) $namespace)); $this->addNamespaceAlias((string) $namespace['prefix'], trim((string) $namespace));
} }
@ -43,33 +51,9 @@ class XmlFileLoader extends FileLoader
} }
if (isset($this->classes[$metadata->getClassName()])) { if (isset($this->classes[$metadata->getClassName()])) {
$xml = $this->classes[$metadata->getClassName()]; $classDescription = $this->classes[$metadata->getClassName()];
foreach ($xml->{'group-sequence-provider'} as $provider) { $this->loadClassMetadataFromXml($metadata, $classDescription);
$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);
}
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);
}
}
return true; return true;
} }
@ -179,22 +163,57 @@ class XmlFileLoader extends FileLoader
} }
/** /**
* 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 { 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) { } catch (\Exception $e) {
throw new MappingException($e->getMessage(), $e->getCode(), $e); throw new MappingException($e->getMessage(), $e->getCode(), $e);
} }
return simplexml_import_dom($dom); 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);
}
}
}
} }

View File

@ -12,9 +12,10 @@
namespace Symfony\Component\Validator\Mapping\Loader; namespace Symfony\Component\Validator\Mapping\Loader;
/** /**
* Loads multiple xml mapping files. * Loads validation metadata from a list of XML files.
* *
* @author Bulat Shakirzyanov <mallluhuct@gmail.com> * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
* *
* @see FilesLoader * @see FilesLoader
*/ */

View File

@ -14,10 +14,13 @@ namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Yaml\Parser as YamlParser; use Symfony\Component\Yaml\Parser as YamlParser;
/**
* Loads validation metadata from a YAML file.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class YamlFileLoader extends FileLoader class YamlFileLoader extends FileLoader
{ {
private $yamlParser;
/** /**
* An array of YAML class descriptions. * An array of YAML class descriptions.
* *
@ -25,35 +28,30 @@ class YamlFileLoader extends FileLoader
*/ */
protected $classes = null; protected $classes = null;
/**
* Caches the used YAML parser.
*
* @var YamlParser
*/
private $yamlParser;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function loadClassMetadata(ClassMetadata $metadata) public function loadClassMetadata(ClassMetadata $metadata)
{ {
if (null === $this->classes) { 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) { if (null === $this->yamlParser) {
$this->yamlParser = new YamlParser(); $this->yamlParser = new YamlParser();
} }
$this->classes = $this->yamlParser->parse(file_get_contents($this->file)); // This method may throw an exception. Do not modify the class'
// state before it completes
// empty file if (false === ($classes = $this->parseFile($this->file))) {
if (null === $this->classes) {
return false; return false;
} }
// not an array $this->classes = $classes;
if (!is_array($this->classes)) {
throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $this->file));
}
if (isset($this->classes['namespaces'])) { if (isset($this->classes['namespaces'])) {
foreach ($this->classes['namespaces'] as $alias => $namespace) { foreach ($this->classes['namespaces'] as $alias => $namespace) {
@ -64,44 +62,10 @@ class YamlFileLoader extends FileLoader
} }
} }
// TODO validation
if (isset($this->classes[$metadata->getClassName()])) { if (isset($this->classes[$metadata->getClassName()])) {
$yaml = $this->classes[$metadata->getClassName()]; $classDescription = $this->classes[$metadata->getClassName()];
if (isset($yaml['group_sequence_provider'])) { $this->loadClassMetadataFromYaml($metadata, $classDescription);
$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);
}
}
}
}
return true; return true;
} }
@ -140,4 +104,76 @@ class YamlFileLoader extends FileLoader
return $values; 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);
}
}
}
}
}
} }

View File

@ -12,9 +12,10 @@
namespace Symfony\Component\Validator\Mapping\Loader; namespace Symfony\Component\Validator\Mapping\Loader;
/** /**
* Loads multiple yaml mapping files. * Loads validation metadata from a list of YAML files.
* *
* @author Bulat Shakirzyanov <mallluhuct@gmail.com> * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
* *
* @see FilesLoader * @see FilesLoader
*/ */

View File

@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Range; use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Constraints\Regex; use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Validator\Constraints\True; use Symfony\Component\Validator\Constraints\True;
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader; use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
@ -105,15 +106,28 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, $metadata); $this->assertEquals($expected, $metadata);
} }
/** public function testThrowExceptionIfDocTypeIsSet()
* @expectedException \Symfony\Component\Validator\Exception\MappingException
* @expectedExceptionMessage Document types are not allowed.
*/
public function testDocTypeIsNotAllowed()
{ {
$loader = new XmlFileLoader(__DIR__.'/withdoctype.xml'); $loader = new XmlFileLoader(__DIR__.'/withdoctype.xml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity'); $metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$this->setExpectedException('\Symfony\Component\Validator\Exception\MappingException');
$loader->loadClassMetadata($metadata); $loader->loadClassMetadata($metadata);
} }
/**
* @see https://github.com/symfony/symfony/pull/12158
*/
public function testDoNotModifyStateIfExceptionIsThrown()
{
$loader = new XmlFileLoader(__DIR__.'/withdoctype.xml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
try {
$loader->loadClassMetadata($metadata);
} catch (MappingException $e) {
$this->setExpectedException('\Symfony\Component\Validator\Exception\MappingException');
$loader->loadClassMetadata($metadata);
}
}
} }

View File

@ -33,16 +33,31 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($loader->loadClassMetadata($metadata)); $this->assertFalse($loader->loadClassMetadata($metadata));
} }
/**
* @expectedException \InvalidArgumentException
*/
public function testLoadClassMetadataThrowsExceptionIfNotAnArray() public function testLoadClassMetadataThrowsExceptionIfNotAnArray()
{ {
$loader = new YamlFileLoader(__DIR__.'/nonvalid-mapping.yml'); $loader = new YamlFileLoader(__DIR__.'/nonvalid-mapping.yml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity'); $metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$this->setExpectedException('\InvalidArgumentException');
$loader->loadClassMetadata($metadata); $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() public function testLoadClassMetadataReturnsTrueIfSuccessful()
{ {
$loader = new YamlFileLoader(__DIR__.'/constraint-mapping.yml'); $loader = new YamlFileLoader(__DIR__.'/constraint-mapping.yml');

View File

@ -0,0 +1,3 @@
composer.lock
phpunit.xml
vendor/