Merge branch '5.2' into 5.x

* 5.2:
  [Messenger] StopWorkersCommand improve doc helper
  Added compatibility with PHPunit 9.5
  do not apply the Valid constraint on scalar form data
  [Test] Reproduce issue with cascading validation
  [SecurityBundle] Don't use the container as resource type in fixtures.
  Fix bug with whitespace in Kernel::stripComments()
This commit is contained in:
Fabien Potencier 2020-12-05 17:05:36 +01:00
commit 7dcaf98373
7 changed files with 140 additions and 20 deletions

View File

@ -13,7 +13,7 @@ if (!getenv('SYMFONY_PHPUNIT_VERSION')) {
} elseif (\PHP_VERSION_ID < 70300) { } elseif (\PHP_VERSION_ID < 70300) {
putenv('SYMFONY_PHPUNIT_VERSION=8.5'); putenv('SYMFONY_PHPUNIT_VERSION=8.5');
} else { } else {
putenv('SYMFONY_PHPUNIT_VERSION=9.4'); putenv('SYMFONY_PHPUNIT_VERSION=9.5');
} }
} }
if (!getenv('SYMFONY_PATCH_TYPE_DECLARATIONS')) { if (!getenv('SYMFONY_PATCH_TYPE_DECLARATIONS')) {

View File

@ -42,7 +42,7 @@ class CoverageListenerTrait
return; return;
} }
$annotations = $test->getAnnotations(); $annotations = Test::parseTestMethodAnnotations(\get_class($test), $test->getName(false));
$ignoredAnnotations = ['covers', 'coversDefaultClass', 'coversNothing']; $ignoredAnnotations = ['covers', 'coversDefaultClass', 'coversNothing'];

View File

@ -110,7 +110,9 @@ class FormValidator extends ConstraintValidator
foreach ($constraints as $constraint) { foreach ($constraints as $constraint) {
// For the "Valid" constraint, validate the data in all groups // For the "Valid" constraint, validate the data in all groups
if ($constraint instanceof Valid) { if ($constraint instanceof Valid) {
if (\is_object($data)) {
$validator->atPath('data')->validate($data, $constraint, $groups); $validator->atPath('data')->validate($data, $constraint, $groups);
}
continue; continue;
} }

View File

@ -17,6 +17,7 @@ use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension; use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@ -27,6 +28,7 @@ use Symfony\Component\Validator\Constraints\Expression;
use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader;
@ -286,6 +288,39 @@ class FormValidatorFunctionalTest extends TestCase
$this->assertSame('children[field2].data', $violations[1]->getPropertyPath()); $this->assertSame('children[field2].data', $violations[1]->getPropertyPath());
} }
public function testCascadeValidationToChildFormsWithTwoValidConstraints()
{
$form = $this->formFactory->create(ReviewType::class);
$form->submit([
'rating' => 1,
'title' => 'Sample Title',
]);
$violations = $this->validator->validate($form);
$this->assertCount(1, $violations);
$this->assertSame('This value should not be blank.', $violations[0]->getMessage());
$this->assertSame('children[author].data.email', $violations[0]->getPropertyPath());
}
public function testCascadeValidationToChildFormsWithTwoValidConstraints2()
{
$form = $this->formFactory->create(ReviewType::class);
$form->submit([
'title' => 'Sample Title',
]);
$violations = $this->validator->validate($form);
$this->assertCount(2, $violations);
$this->assertSame('This value should not be blank.', $violations[0]->getMessage());
$this->assertSame('data.rating', $violations[0]->getPropertyPath());
$this->assertSame('This value should not be blank.', $violations[1]->getMessage());
$this->assertSame('children[author].data.email', $violations[1]->getPropertyPath());
}
public function testCascadeValidationToChildFormsUsingPropertyPathsValidatedInSequence() public function testCascadeValidationToChildFormsUsingPropertyPathsValidatedInSequence()
{ {
$form = $this->formFactory->create(FormType::class, null, [ $form = $this->formFactory->create(FormType::class, null, [
@ -444,3 +479,62 @@ class FooType extends AbstractType
$resolver->setDefault('data_class', Foo::class); $resolver->setDefault('data_class', Foo::class);
} }
} }
class Review
{
public $rating;
public $title;
public $author;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('title', new NotBlank());
$metadata->addPropertyConstraint('rating', new NotBlank());
}
}
class ReviewType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('rating', IntegerType::class, [
'constraints' => [new Valid()],
])
->add('title')
->add('author', CustomerType::class, [
'constraints' => [new Valid()],
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('data_class', Review::class);
}
}
class Customer
{
public $email;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('email', new NotBlank());
}
}
class CustomerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('data_class', Customer::class);
}
}

View File

@ -831,6 +831,9 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
// replace multiple new lines with a single newline // replace multiple new lines with a single newline
$rawChunk .= preg_replace(['/\n{2,}/S'], "\n", $token[1]); $rawChunk .= preg_replace(['/\n{2,}/S'], "\n", $token[1]);
} elseif (\in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT])) { } elseif (\in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT])) {
if (!\in_array($rawChunk[\strlen($rawChunk) - 1], [' ', "\n", "\r", "\t"], true)) {
$rawChunk .= ' ';
}
$ignoreSpace = true; $ignoreSpace = true;
} else { } else {
$rawChunk .= $token[1]; $rawChunk .= $token[1];
@ -838,6 +841,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
// The PHP-open tag already has a new-line // The PHP-open tag already has a new-line
if (\T_OPEN_TAG === $token[0]) { if (\T_OPEN_TAG === $token[0]) {
$ignoreSpace = true; $ignoreSpace = true;
} else {
$ignoreSpace = false;
} }
} }
} }

View File

@ -226,10 +226,37 @@ class KernelTest extends TestCase
$kernel->handle($request, $type, $catch); $kernel->handle($request, $type, $catch);
} }
public function testStripComments() /**
* @dataProvider getStripCommentsCodes
*/
public function testStripComments(string $source, string $expected)
{ {
$source = <<<'EOF' $output = Kernel::stripComments($source);
// Heredocs are preserved, making the output mixing Unix and Windows line
// endings, switching to "\n" everywhere on Windows to avoid failure.
if ('\\' === \DIRECTORY_SEPARATOR) {
$expected = str_replace("\r\n", "\n", $expected);
$output = str_replace("\r\n", "\n", $output);
}
$this->assertEquals($expected, $output);
}
public function getStripCommentsCodes(): array
{
return [
['<?php echo foo();', '<?php echo foo();'],
['<?php echo/**/foo();', '<?php echo foo();'],
['<?php echo/** bar */foo();', '<?php echo foo();'],
['<?php /**/echo foo();', '<?php echo foo();'],
['<?php echo \foo();', '<?php echo \foo();'],
['<?php echo/**/\foo();', '<?php echo \foo();'],
['<?php echo/** bar */\foo();', '<?php echo \foo();'],
['<?php /**/echo \foo();', '<?php echo \foo();'],
[<<<'EOF'
<?php <?php
include_once \dirname(__DIR__).'/foo.php';
$string = 'string should not be modified'; $string = 'string should not be modified';
@ -267,9 +294,10 @@ class TestClass
// inline comment // inline comment
} }
} }
EOF; EOF
$expected = <<<'EOF' , <<<'EOF'
<?php <?php
include_once \dirname(__DIR__).'/foo.php';
$string = 'string should not be modified'; $string = 'string should not be modified';
$string = 'string should not be $string = 'string should not be
@ -294,18 +322,9 @@ class TestClass
{ {
} }
} }
EOF; EOF
],
$output = Kernel::stripComments($source); ];
// Heredocs are preserved, making the output mixing Unix and Windows line
// endings, switching to "\n" everywhere on Windows to avoid failure.
if ('\\' === \DIRECTORY_SEPARATOR) {
$expected = str_replace("\r\n", "\n", $expected);
$output = str_replace("\r\n", "\n", $output);
}
$this->assertEquals($expected, $output);
} }
public function testSerialize() public function testSerialize()

View File

@ -50,7 +50,7 @@ The <info>%command.name%</info> command sends a signal to stop any <info>messeng
Each worker command will finish the message they are currently processing Each worker command will finish the message they are currently processing
and then exit. Worker commands are *not* automatically restarted: that and then exit. Worker commands are *not* automatically restarted: that
should be handled by something like supervisord. should be handled by a process control system.
EOF EOF
) )
; ;