Skip to content

[2.2] [Validator] "AnyOf" validation constraint #4861

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
4 changes: 2 additions & 2 deletions src/Symfony/Component/Console/Helper/DialogHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,9 @@ private function hasSttyAvailable()
return self::$stty;
}

exec('/usr/bin/env stty', $output, $exicode);
exec('/usr/bin/env stty', $output, $exitcode);

return self::$stty = $exicode === 0;
return self::$stty = $exitcode === 0;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/Console/Tests/ApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ public function testOverwritingDefaultInputDefinitionOverwritesDefaultValues()

$inputDefinition = $application->getDefinition();

// check wether the default arguments and options are not returned any more
// check whether the default arguments and options are not returned any more
$this->assertFalse($inputDefinition->hasArgument('command'));

$this->assertFalse($inputDefinition->hasOption('help'));
Expand All @@ -622,7 +622,7 @@ public function testSettingCustomInputDefinitionOverwritesDefaultValues()

$inputDefinition = $application->getDefinition();

// check wether the default arguments and options are not returned any more
// check whether the default arguments and options are not returned any more
$this->assertFalse($inputDefinition->hasArgument('command'));

$this->assertFalse($inputDefinition->hasOption('help'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ public function testUntick()
$node = $this->createNode('input', '', array('type' => 'checkbox', 'name' => 'name', 'checked' => 'checked'));
$field = new ChoiceFormField($node);
$field->untick();
$this->assertNull($field->getValue(), '->untick() unticks checkoxes');
$this->assertNull($field->getValue(), '->untick() unticks checkboxes');
}

public function testSelect()
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/DomCrawler/Tests/FormTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ public function testMultiselectSetValues()
{
$form = $this->createForm('<form><select multiple="multiple" name="multi"><option value="foo">foo</option><option value="bar">bar</option></select><input type="submit" /></form>');
$form->setValues(array('multi' => array("foo", "bar")));
$this->assertEquals(array('multi' => array('foo', 'bar')), $form->getValues(), '->setValue() sets tehe values of select');
$this->assertEquals(array('multi' => array('foo', 'bar')), $form->getValues(), '->setValue() sets the values of select');
}

public function testGetPhpValues()
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ private function buildDatesFiltering(Command $command, array $dates)
if (0 > $mins) {
// mtime is in the future
$command->add(' -mmin -0');
// we will have no result so we dont need to continue
// we will have no result so we don't need to continue
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Finder/Tests/FinderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ public function testNotContainsOnDirectory(Adapter\AdapterInterface $adapter)

/**
* Searching in multiple locations involves AppendIterator which does an unnecessary rewind which leaves FilterIterator
* with inner FilesystemIterator in an ivalid state.
* with inner FilesystemIterator in an invalid state.
*
* @see https://bugs.php.net/bug.php?id=49104
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public function testPrototypeMultipartPropagation()
$this->assertTrue($form->createView()->vars['multipart']);
}

public function testGetDataDoesNotContainsProtypeNameBeforeDataAreSet()
public function testGetDataDoesNotContainsPrototypeNameBeforeDataAreSet()
{
$form = $this->factory->create('collection', array(), array(
'type' => 'file',
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/HttpFoundation/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -1097,7 +1097,7 @@ public function isOk()
}

/**
* Is the reponse forbidden?
* Is the response forbidden?
*
* @return Boolean
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ public function testAdjacentVariables()
$matcher = new UrlMatcher($coll, new RequestContext());
// 'w' eagerly matches as much as possible and the other variables match the remaining chars.
// This also shows that the variables w-z must all exclude the separating char (the dot '.' in this case) by default requirement.
// Otherwise they would also comsume '.xml' and _format would never match as it's an optional variable.
// Otherwise they would also consume '.xml' and _format would never match as it's an optional variable.
$this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'Y', 'z' => 'Z','_format' => 'xml', '_route' => 'test'), $matcher->match('/wwwwwxYZ.xml'));
// As 'y' has custom requirement and can only be of value 'y|Y', it will leave 'ZZZ' to variable z.
// So with carefully chosen requirements adjacent variables, can be useful.
Expand Down
26 changes: 26 additions & 0 deletions src/Symfony/Component/Validator/Constraints/AnyOf.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

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

namespace Symfony\Component\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;

/**
* @Annotation
*
* @api
*
* @author Oleg Stepura <github@oleg.stepura.com>
*/
class AnyOf extends All
{
}
61 changes: 61 additions & 0 deletions src/Symfony/Component/Validator/Constraints/AnyOfValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

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

namespace Symfony\Component\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
* AnyOfValidator class.
*
* Validates against all constraints and leaves all violations only if not
* validated against any of given constraint. If successfully validated against
* at least one constraint value is considered to be valid and violations
* are rolled back.
*
* @author Oleg Stepura <github@oleg.stepura.com>
*/
class AnyOfValidator extends ConstraintValidator
{
/**
* {@inheritDoc}
*/
public function validate($value, Constraint $constraint)
{
$walker = $this->context->getGraphWalker();

$violationList = $this->context->getViolations();
$violationListBefore = iterator_to_array($violationList);
$violationCountPrevious = $violationList->count();
$validationFailed = true;

foreach ($constraint->constraints as $constr) {
$walker->walkConstraint($constr, $value, '', '');
$violationCount = $violationList->count();

if ($violationCount === $violationCountPrevious) {
// At least one constraint did not fail
$validationFailed = false;
}

$violationCountPrevious = $violationCount;
}

if (!$validationFailed) {
foreach ($violationList as $id => $violation) {
if (!isset($violationListBefore[$id])) {
$violationList->remove($id);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

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

namespace Symfony\Component\Validator\Tests\Constraints;

use Symfony\Component\Validator\Constraints\AnyOf;
use Symfony\Component\Validator\Constraints\MinLength;
use Symfony\Component\Validator\Constraints\Null;
use Symfony\Component\Validator\Constraints\AnyOfValidator;

class AnyOfValidatorTest extends \PHPUnit_Framework_TestCase
{
protected $violations;
protected $validator;
protected $constraint;

protected function setUp()
{
$this->violations = $this->getMock('Symfony\Component\Validator\ConstraintViolationList', array(), array(), '', false);
$context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false);
$walker = $this->getMock('Symfony\Component\Validator\GraphWalker', array(), array(), '', false);

$this->validator = new AnyOfValidator();
$this->validator->initialize($context);
$this->constraint = new AnyOf(
array('constraints' => array(
new MinLength(5),
new Null
))
);

$context->expects($this->any())
->method('getGraphWalker')
->will($this->returnValue($walker));
$context->expects($this->any())
->method('getViolations')
->will($this->returnValue($this->violations));

for ($i = 0; $i < count($this->constraint->constraints); $i ++) {
$walker->expects($this->at($i))->method('walkConstraint');
}
}

protected function tearDown()
{
$this->validator = null;
$this->violations = null;
$this->constraint = null;
}

public function testNoOneIsValid()
{
$value = '123';

$this->violations->expects($this->never())->method('remove');

$this->violations->expects($this->at(0))
->method('count')
->will($this->returnValue(0));

for ($i = 0; $i < 3; $i ++) {
$this->violations->expects($this->at($i + 1))
->method('count')
->will($this->returnValue($i));
}

$this->violations->expects($this->once())
->method('getIterator')
->will($this->returnValue(new \ArrayIterator(array())));

$this->validator->validate($value, $this->constraint);
}

public function testOneIsValid()
{
$this->expectRemovalOfViolations();

$this->violations->expects($this->at(2))
->method('count')
->will($this->returnValue(1));

$this->violations->expects($this->at(3))
->method('count')
->will($this->returnValue(1));

// actually value `null` here doesn't make sense since everything is mocked
$this->validator->validate(null, $this->constraint);
}

public function testSecondOneIsValid()
{
$this->expectRemovalOfViolations();

$this->violations->expects($this->at(2))
->method('count')
->will($this->returnValue(0));

$this->violations->expects($this->at(3))
->method('count')
->will($this->returnValue(1));

$this->validator->validate('123456', $this->constraint);
}

public function testWithExistingViolationsInAList()
{
$this->expectRemovalOfViolations(3, array_fill(0, 3, ''));

$this->violations->expects($this->at(2))
->method('count')
->will($this->returnValue(4));

$this->violations->expects($this->at(3))
->method('count')
->will($this->returnValue(4));

$this->validator->validate('123456', $this->constraint);
}

protected function expectRemovalOfViolations($startingAt = 0, $initialArray = array())
{
$this->violations->expects($this->at(0))
->method('getIterator')
->will($this->returnValue(new \ArrayIterator($initialArray)));

$this->violations->expects($this->at(1))
->method('count')
->will($this->returnValue($startingAt));

$this->violations->expects($this->at(4))
->method('getIterator')
->will($this->returnValue(new \ArrayIterator(array_fill(0, $startingAt + 2, ''))));

for ($i = 0; $i < 2; $i ++) {
$this->violations->expects($this->at(5 + $i))
->method('remove')
->with($startingAt + $i);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,13 @@ public function testHtmlPattern()
));
$this->assertNull($constraint->getHtmlPattern());

// Automaticaly converted
// Automatically converted
$constraint = new Regex(array(
'pattern' => '/^[a-z]+$/',
));
$this->assertEquals('[a-z]+', $constraint->getHtmlPattern());

// Automaticaly converted, adds .*
// Automatically converted, adds .*
$constraint = new Regex(array(
'pattern' => '/[a-z]+/',
));
Expand Down