Merge branch '2.7'
This commit is contained in:
commit
b05fce2857
|
@ -803,7 +803,7 @@ class FrameworkExtension extends Extension
|
|||
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
{% set link = collector.controller.file|file_link(collector.controller.line) %}
|
||||
{% 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-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 }}
|
||||
</span>
|
||||
{% 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 %}
|
||||
{% else %}
|
||||
<span class="sf-toolbar-info-class">{{ collector.controller }}</span>
|
||||
|
|
|
@ -173,7 +173,7 @@
|
|||
|
||||
var addEventListener;
|
||||
|
||||
if (document.addEventListener) {
|
||||
if (document.attachEvent) {
|
||||
addEventListener = function (element, eventName, callback) {
|
||||
element.attachEvent('on' + eventName, callback);
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<div id="sfToolbarClearer-{{ token }}" style="clear: both; height: 38px;"></div>
|
||||
{% 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 %}
|
||||
{{ template.renderblock('toolbar', {
|
||||
'collector': profile.getcollector(name),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -56,7 +56,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
*
|
||||
* @param NodeParentInterface $parent The parent
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function setParent(NodeParentInterface $parent)
|
||||
{
|
||||
|
@ -70,7 +70,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
*
|
||||
* @param string $info The info text
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function info($info)
|
||||
{
|
||||
|
@ -82,7 +82,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
*
|
||||
* @param string|array $example
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function example($example)
|
||||
{
|
||||
|
@ -95,7 +95,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
* @param string $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function attribute($key, $value)
|
||||
{
|
||||
|
@ -146,7 +146,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
*
|
||||
* @param mixed $value The default value
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function defaultValue($value)
|
||||
{
|
||||
|
@ -159,7 +159,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
/**
|
||||
* Sets the node as required.
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function isRequired()
|
||||
{
|
||||
|
@ -173,7 +173,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function treatNullLike($value)
|
||||
{
|
||||
|
@ -187,7 +187,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function treatTrueLike($value)
|
||||
{
|
||||
|
@ -201,7 +201,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function treatFalseLike($value)
|
||||
{
|
||||
|
@ -213,7 +213,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
/**
|
||||
* Sets null as the default value.
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function defaultNull()
|
||||
{
|
||||
|
@ -223,7 +223,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
/**
|
||||
* Sets true as the default value.
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function defaultTrue()
|
||||
{
|
||||
|
@ -233,7 +233,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
/**
|
||||
* Sets false as the default value.
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function defaultFalse()
|
||||
{
|
||||
|
@ -253,7 +253,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
/**
|
||||
* Denies the node value being empty.
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function cannotBeEmpty()
|
||||
{
|
||||
|
@ -281,7 +281,7 @@ abstract class NodeDefinition implements NodeParentInterface
|
|||
*
|
||||
* @param bool $deny Whether the overwriting is forbidden or not
|
||||
*
|
||||
* @return NodeDefinition
|
||||
* @return NodeDefinition|$this
|
||||
*/
|
||||
public function cannotBeOverwritten($deny = true)
|
||||
{
|
||||
|
|
|
@ -170,6 +170,10 @@ class Command
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
|
|
|
@ -100,8 +100,10 @@ class ArrayInput extends Input
|
|||
$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;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
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'))));
|
||||
|
|
|
@ -341,7 +341,7 @@ class InputDefinitionTest extends \PHPUnit_Framework_TestCase
|
|||
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 @@ class InputDefinitionTest extends \PHPUnit_Framework_TestCase
|
|||
'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()
|
||||
|
|
|
@ -234,7 +234,7 @@ class Finder implements \IteratorAggregate, \Countable
|
|||
* $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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
-----
|
||||
|
|
|
@ -130,7 +130,7 @@ class File extends \SplFileInfo
|
|||
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)) {
|
||||
|
|
|
@ -616,8 +616,8 @@ class Request
|
|||
* 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.
|
||||
|
|
|
@ -110,7 +110,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
|
|||
|
||||
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');
|
||||
|
|
|
@ -578,7 +578,7 @@ abstract class Kernel implements KernelInterface, TerminableInterface
|
|||
{
|
||||
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)) {
|
||||
|
|
|
@ -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 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
*/
|
||||
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 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
*/
|
||||
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 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
$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 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
*/
|
||||
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 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
*/
|
||||
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 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
$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 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
}
|
||||
|
||||
return true;
|
||||
} catch (NoSuchIndexException $e) {
|
||||
} catch (AccessException $e) {
|
||||
return false;
|
||||
} catch (NoSuchPropertyException $e) {
|
||||
} catch (UnexpectedTypeException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -217,13 +179,13 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
*/
|
||||
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 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
|
||||
$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;
|
||||
}
|
||||
|
||||
|
|
|
@ -197,7 +197,7 @@ class StringUtil
|
|||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,20 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||
$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 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* @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".
|
||||
* @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');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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');
|
||||
$this->propertyAccessor->getValue($objectOrArray, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -260,47 +248,13 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* @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".
|
||||
* @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($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');
|
||||
$this->propertyAccessor->setValue($objectOrArray, $path, 'value');
|
||||
}
|
||||
|
||||
public function testGetValueWhenArrayValueIsNull()
|
||||
|
@ -362,19 +316,12 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||
$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'));
|
||||
}
|
||||
|
||||
public function testIsReadableThrowsExceptionIfNull()
|
||||
{
|
||||
$this->assertFalse($this->propertyAccessor->isReadable(null, 'foobar'));
|
||||
}
|
||||
|
||||
public function testIsReadableThrowsExceptionIfEmpty()
|
||||
{
|
||||
$this->assertFalse($this->propertyAccessor->isReadable('', 'foobar'));
|
||||
$this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -430,19 +377,12 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||
$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'));
|
||||
}
|
||||
|
||||
public function testNullIsNotWritable()
|
||||
{
|
||||
$this->assertFalse($this->propertyAccessor->isWritable(null, 'foobar'));
|
||||
}
|
||||
|
||||
public function testEmptyIsNotWritable()
|
||||
{
|
||||
$this->assertFalse($this->propertyAccessor->isWritable('', 'foobar'));
|
||||
$this->assertFalse($this->propertyAccessor->isWritable($objectOrArray, $path));
|
||||
}
|
||||
|
||||
public function getValidPropertyPaths()
|
||||
|
|
|
@ -59,6 +59,7 @@ class StringUtilTest extends \PHPUnit_Framework_TestCase
|
|||
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 @@ class StringUtilTest extends \PHPUnit_Framework_TestCase
|
|||
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'),
|
||||
|
|
|
@ -14,11 +14,25 @@ namespace Symfony\Component\Validator\Mapping\Loader;
|
|||
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 <bschussek@gmail.com>
|
||||
*/
|
||||
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 @@ abstract class AbstractLoader implements LoaderInterface
|
|||
/**
|
||||
* 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 @@ abstract class AbstractLoader implements LoaderInterface
|
|||
|
||||
$className = $this->namespaces[$prefix].$className;
|
||||
} else {
|
||||
$className = 'Symfony\\Component\\Validator\\Constraints\\'.$name;
|
||||
$className = self::DEFAULT_NAMESPACE.$name;
|
||||
}
|
||||
|
||||
return new $className($options);
|
||||
|
|
|
@ -19,8 +19,16 @@ use Symfony\Component\Validator\Constraints\GroupSequenceProvider;
|
|||
use Symfony\Component\Validator\Exception\MappingException;
|
||||
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
|
||||
{
|
||||
/**
|
||||
* @var Reader
|
||||
*/
|
||||
protected $reader;
|
||||
|
||||
public function __construct(Reader $reader)
|
||||
|
@ -35,7 +43,7 @@ class AnnotationLoader implements LoaderInterface
|
|||
{
|
||||
$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 @@ class AnnotationLoader implements LoaderInterface
|
|||
$metadata->addConstraint($constraint);
|
||||
}
|
||||
|
||||
$loaded = true;
|
||||
$success = true;
|
||||
}
|
||||
|
||||
foreach ($reflClass->getProperties() as $property) {
|
||||
|
@ -56,7 +64,7 @@ class AnnotationLoader implements LoaderInterface
|
|||
$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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,26 +13,42 @@ namespace Symfony\Component\Validator\Mapping\Loader;
|
|||
|
||||
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
|
||||
{
|
||||
/**
|
||||
* 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;
|
||||
|
|
|
@ -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 <mallluhuct@gmail.com>
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @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 @@ 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)
|
||||
{
|
||||
$loaders = array();
|
||||
|
||||
foreach ($paths as $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);
|
||||
}
|
||||
|
|
|
@ -15,25 +15,25 @@ use Symfony\Component\Validator\Exception\MappingException;
|
|||
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 <em>all</em> 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 <bschussek@gmail.com>
|
||||
*/
|
||||
class LoaderChain implements LoaderInterface
|
||||
{
|
||||
/**
|
||||
* @var LoaderInterface[]
|
||||
*/
|
||||
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 does not implement LoaderInterface
|
||||
* @throws MappingException If any of the loaders has an invalid type
|
||||
*/
|
||||
public function __construct(array $loaders)
|
||||
{
|
||||
|
|
|
@ -13,14 +13,19 @@ namespace Symfony\Component\Validator\Mapping\Loader;
|
|||
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* Loads validation metadata into {@link ClassMetadata} instances.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -14,10 +14,25 @@ namespace Symfony\Component\Validator\Mapping\Loader;
|
|||
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 <bschussek@gmail.com>
|
||||
*/
|
||||
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;
|
||||
|
|
|
@ -15,10 +15,15 @@ use Symfony\Component\Config\Util\XmlUtils;
|
|||
use Symfony\Component\Validator\Exception\MappingException;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* Loads validation metadata from an XML file.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
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 @@ class XmlFileLoader extends FileLoader
|
|||
}
|
||||
|
||||
if (isset($this->classes[$metadata->getClassName()])) {
|
||||
$xml = $this->classes[$metadata->getClassName()];
|
||||
$classDescription = $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);
|
||||
}
|
||||
|
||||
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 @@ 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 {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <mallluhuct@gmail.com>
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @see FilesLoader
|
||||
*/
|
||||
|
|
|
@ -14,10 +14,13 @@ namespace Symfony\Component\Validator\Mapping\Loader;
|
|||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Yaml\Parser as YamlParser;
|
||||
|
||||
/**
|
||||
* Loads validation metadata from a YAML file.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
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 @@ class YamlFileLoader extends FileLoader
|
|||
}
|
||||
}
|
||||
|
||||
// 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 @@ class YamlFileLoader extends FileLoader
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <mallluhuct@gmail.com>
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @see FilesLoader
|
||||
*/
|
||||
|
|
|
@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraints\NotNull;
|
|||
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 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
|||
$this->assertEquals($expected, $metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\MappingException
|
||||
* @expectedExceptionMessage Document types are not allowed.
|
||||
*/
|
||||
public function testDocTypeIsNotAllowed()
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,16 +33,31 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
|||
$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');
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
composer.lock
|
||||
phpunit.xml
|
||||
vendor/
|
Reference in New Issue