Skip to content

[Validator] Fix override of protected property Assertions on Child Class from Parent Class property #51025

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 2 commits into from

Conversation

wopoczynski
Copy link

@wopoczynski wopoczynski commented Jul 19, 2023

Symfony version(s) affected

6.3 and above

Description

During validation if we are using abstract base class with any Assert on property in it, and on child class we are overriding the property and Asserts current behaviour is enforcing the parent protected property Asserts.

I.e.:


abstract class ParentClass
{
    public function __construct(
        #[Assert\NotNull]
        protected ?string $value    
    ) {
    }
}
class Child extends ParentClass
{
    public function __construct(
        #[Assert\Expression(expression: 'this.isValid() === true', message: 'value is invalid. ')]
        #[Assert\Length(min: 11, max: 11, exactMessage: 'must be 11 digits long')]
        #[Assert\NotBlank(message: 'value is required')]
        #[Assert\Regex(pattern: '/^[0-9]+$/', message: 'Must contain digits only')]
        protected ?string $value = null
    ) {}
}

How to reproduce

To reproduce you will need validator, annotations and property access

composer require symfony/validator

example.php:

<?php
// composer require symfony/validator
use Doctrine\Common\Annotations\AnnotationReader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\ValidatorBuilder;

// parent abstract class
abstract class ParentClass
{
    public function __construct(
        #[Assert\NotNull]
        protected ?string $value    
    ) {
    }
}

// example child class
class Child extends ParentClass
{
    public function __construct(
        #[Assert\Expression(expression: 'this.isValid() === true', message: 'value is invalid. ')]
        #[Assert\Length(min: 11, max: 11, exactMessage: 'must be 11 digits long')]
        #[Assert\NotBlank(message: 'value is required')]
        #[Assert\Regex(pattern: '/^[0-9]+$/', message: 'Must contain digits only')]
        protected ?string $value = null
    ) {}
}

// example test case 
class ExampleTest extends TestCase
{
    /**
     * @dataProvider provideValues
     */
    public function testValidation(string $value, int $violationCount): void
    {
        $validatorBuilder = new ValidatorBuilder();
        $validatorBuilder->enableAnnotationMapping()
            ->setDoctrineAnnotationReader(new AnnotationReader());
        $validator = $validatorBuilder->getValidator();

        $child = new Child($value);

        $this->assertEquals($violationCount, $validator->validate($child)->count());
    }

    /**
     * @return array<int, string|int>[]
     */
    public function provideValues(): array
    {
        return [
            ['07127669600', 0],
            ['35424643507', 0],
            ['11144477735', 0],
            ['11144477734', 1],
            ['11144477725', 1],
            ['11111111111', 1],
            ['55555555555', 1],
            ['aaaaaaaaaaa', 1],
            ['           ', 1],
            ['071276696', 2],
        ];
    }
}

Possible Solution

For the path in \Symfony\Component\Validator\Mapping\ClassMetadata::mergeConstraints:349 handle not only private properties but also protected in example:

                if ($member instanceof MemberMetadata && (!$member->isProtected($this->name) && !$member->isPrivate($this->name))) {
Q A
Branch? 6.3 for bug fixes
Bug fix? yes
New feature? no
Deprecations? no

@carsonbot
Copy link

Hey!

I see that this is your first PR. That is great! Welcome!

Symfony has a contribution guide which I suggest you to read.

In short:

  • Always add tests
  • Keep backward compatibility (see https://symfony.com/bc).
  • Bug fixes must be submitted against the lowest maintained branch where they apply (see https://symfony.com/releases)
  • Features and deprecations must be submitted against the 6.4 branch.

Review the GitHub status checks of your pull request and try to solve the reported issues. If some tests are failing, try to see if they are failing because of this change.

When two Symfony core team members approve this change, it will be merged and you will become an official Symfony contributor!
If this PR is merged in a lower version branch, it will be merged up to all maintained branches within a few days.

I am going to sit back now and wait for the reviews.

Cheers!

Carsonbot

@carsonbot
Copy link

Hey!

Thanks for your PR. You are targeting branch "6.3" but it seems your PR description refers to branch "6.4 for features / 5.4, 6.2, or 6.3 for bug fixes".
Could you update the PR description or change target branch? This helps core maintainers a lot.

Cheers!

Carsonbot

### Symfony version(s) affected

6.3 and above

### Description

During validation if we are using abstract base class with any Assert on property in it, and on child class we are overriding the property and Asserts current behaviour is enforcing the parent protected property Asserts. 

I.e.:

```

abstract class ParentClass
{
    public function __construct(
        #[Assert\NotNull]
        protected ?string $value    
    ) {
    }
}
class Child extends ParentClass
{
    public function __construct(
        #[Assert\Expression(expression: 'this.isValid() === true', message: 'value is invalid. ')]
        #[Assert\Length(min: 11, max: 11, exactMessage: 'must be 11 digits long')]
        #[Assert\NotBlank(message: 'value is required')]
        #[Assert\Regex(pattern: '/^[0-9]+$/', message: 'Must contain digits only')]
        protected ?string $value = null
    ) {}
}
```


### How to reproduce

To reproduce you will need validator, annotations and property access
```
composer require symfony/validator
```


example.php:
```php
<?php
// composer require symfony/validator
use Doctrine\Common\Annotations\AnnotationReader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\ValidatorBuilder;

// parent abstract class
abstract class ParentClass
{
    public function __construct(
        #[Assert\NotNull]
        protected ?string $value    
    ) {
    }
}

// example child class
class Child extends ParentClass
{
    public function __construct(
        #[Assert\Expression(expression: 'this.isValid() === true', message: 'value is invalid. ')]
        #[Assert\Length(min: 11, max: 11, exactMessage: 'must be 11 digits long')]
        #[Assert\NotBlank(message: 'value is required')]
        #[Assert\Regex(pattern: '/^[0-9]+$/', message: 'Must contain digits only')]
        protected ?string $value = null
    ) {}
}

// example test case 
class ExampleTest extends TestCase
{
    /**
     * @dataProvider provideValues
     */
    public function testValidation(string $value, int $violationCount): void
    {
        $validatorBuilder = new ValidatorBuilder();
        $validatorBuilder->enableAnnotationMapping()
            ->setDoctrineAnnotationReader(new AnnotationReader());
        $validator = $validatorBuilder->getValidator();

        $child = new Child($value);

        $this->assertEquals($violationCount, $validator->validate($child)->count());
    }

    /**
     * @return array<int, string|int>[]
     */
    public function provideValues(): array
    {
        return [
            ['07127669600', 0],
            ['35424643507', 0],
            ['11144477735', 0],
            ['11144477734', 1],
            ['11144477725', 1],
            ['11111111111', 1],
            ['55555555555', 1],
            ['aaaaaaaaaaa', 1],
            ['           ', 1],
            ['071276696', 2],
        ];
    }
}

```

### Possible Solution

For the path in `\Symfony\Component\Validator\Mapping\ClassMetadata::mergeConstraints:349` handle not only private properties but also protected
in example:
```php
                if ($member instanceof MemberMetadata && (!$member->isProtected($this->name) && !$member->isPrivate($this->name))) {

```
@lyrixx lyrixx removed their request for review July 19, 2023 13:48
@lyrixx
Copy link
Member

lyrixx commented Jul 19, 2023

Can you add some tests? Thanks

@szymat
Copy link

szymat commented Jul 19, 2023

I am having same issue after upgrading to 6.3, this needs to be fixed 🙏

@AwesomeGogo
Copy link

AwesomeGogo commented Jul 19, 2023

@szymat definitely!! +1 I am also facing that issue

@dawidf
Copy link

dawidf commented Jul 19, 2023

+1

@wopoczynski wopoczynski changed the title fix: override of protected property from parent [validator] fix: override of protected property asserts from parent Jul 19, 2023
@wopoczynski wopoczynski changed the title [validator] fix: override of protected property asserts from parent [validator] Fix override of protected property Assertions on Child Class from Parent Class property Jul 19, 2023
@wopoczynski wopoczynski changed the title [validator] Fix override of protected property Assertions on Child Class from Parent Class property [Validator] Fix override of protected property Assertions on Child Class from Parent Class property Jul 19, 2023
@kontsevoye
Copy link

I made almost the same changes in #51027 and I can say that they are relevant for v6.3.1+ v6.2.12+ v5.4.25+ as well, and not only for protected properties

@xabbuh
Copy link
Member

xabbuh commented Jul 20, 2023

This looks like a duplicate of #50788 to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants