Skip to content
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

[Validator] Advanced Validation Group Provider #18744

Merged
merged 1 commit into from
Nov 21, 2023

Conversation

yceruto
Copy link
Member

@yceruto yceruto commented Aug 11, 2023

@yceruto yceruto requested a review from xabbuh as a code owner August 11, 2023 23:22
@carsonbot carsonbot added this to the 6.4 milestone Aug 11, 2023
@yceruto yceruto force-pushed the feature/group_sequence_provider branch 3 times, most recently from 5d88270 to bcad0c2 Compare August 11, 2023 23:27
@OskarStark OskarStark added the Waiting Code Merge Docs for features pending to be merged label Aug 12, 2023
@carsonbot carsonbot modified the milestones: 6.4, next Aug 12, 2023
@carsonbot carsonbot changed the title Advanced Group Sequence Provider [Validator] Advanced Group Sequence Provider Aug 14, 2023
@yceruto yceruto changed the title [Validator] Advanced Group Sequence Provider [Validator] Advanced Validation Group Provider Aug 17, 2023
@yceruto yceruto force-pushed the feature/group_sequence_provider branch from 7d76721 to f7adf11 Compare October 18, 2023 22:50
fabpot added a commit to symfony/symfony that referenced this pull request Oct 20, 2023
…tion groups provider outside DTOs (Yonel Ceruto)

This PR was squashed before being merged into the 6.4 branch.

Discussion
----------

[FrameworkBundle][Validator] Allow implementing validation groups provider outside DTOs

| Q             | A
| ------------- | ---
| Branch?       | 6.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | -
| License       | MIT
| Doc PR        | symfony/symfony-docs#18744

Alternative to #51233
Inspiration: #51012

Currently, you can determine the sequence of groups to apply dynamically based on the state of your DTO by implementing the `GroupSequenceProviderInterface` in your DTO class. https://symfony.com/doc/current/validation/sequence_provider.html#group-sequence-providers
```php
use Symfony\Component\Validator\GroupSequenceProviderInterface;

#[Assert\GroupSequenceProvider]
class UserDto implements GroupSequenceProviderInterface
{
    // ...

    public function getGroupSequence(): array|GroupSequence
    {
        if ($this->isCompanyType()) {
            return ['User', 'Company'];
        }

        return ['User'];
    }
}
```
It covers most of the common scenarios, but for more advanced ones, it may not be sufficient. Suppose now you need to provide the sequence of groups from an external configuration (or service) which can change its value dynamically:
```php
#[Assert\GroupSequenceProvider]
class UserDto implements GroupSequenceProviderInterface
{
    // ...

    public __constructor(private readonly ConfigService $config)
    {
    }

    public function getGroupSequence(): array|GroupSequence
    {
        if ($this->config->isEnabled()) {
            return ['User', $this->config->getGroup()];
        }

        return ['User'];
    }
}
```
This issue cannot be resolved at present without managing the DTO initialization and manually setting its dependencies. On the other hand, since the state of the DTO is not used at all, the implementation of the `GroupSequenceProviderInterface` becomes less fitting to the DTO responsibility. Further, stricter programming may raise a complaint about a violation of SOLID principles here.

So, the proposal of this PR is to allow configuring the validation groups provider outside of the DTO, while simultaneously enabling the registration of this provider as a service if necessary.

To achieve this, you'll need to implement a new `GroupProviderInterface` in a separate class, and configure it using the new `provider` option within the `GroupSequenceProvider` attribute:
```php
#[Assert\GroupSequenceProvider(provider: UserGroupProvider::class)]
class UserDto
{
    // ...
}

class UserGroupProvider implements GroupProviderInterface
{
    public __constructor(private readonly ConfigService $config)
    {
    }

    public function getGroups(object $object): array|GroupSequence
    {
        if ($this->config->isEnabled()) {
            return ['User', $this->config->getGroup()];
        }

        return ['User'];
    }
}
```
That's all you'll need to do if autowiring is enabled under your custom provider. Otherwise, you can manually tag your service with `validator.group_provider` to collect it and utilize it as a provider service during the validation process.

In conclusion, no more messing with the DTO structure, just use the new `class` option for more advanced use cases.

---

TODO:

- [x] Add tests
- [x] Create doc PR

Commits
-------

a3a089a [FrameworkBundle][Validator] Allow implementing validation groups provider outside DTOs
symfony-splitter pushed a commit to symfony/framework-bundle that referenced this pull request Oct 20, 2023
…tion groups provider outside DTOs (Yonel Ceruto)

This PR was squashed before being merged into the 6.4 branch.

Discussion
----------

[FrameworkBundle][Validator] Allow implementing validation groups provider outside DTOs

| Q             | A
| ------------- | ---
| Branch?       | 6.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | -
| License       | MIT
| Doc PR        | symfony/symfony-docs#18744

Alternative to symfony/symfony#51233
Inspiration: symfony/symfony#51012

Currently, you can determine the sequence of groups to apply dynamically based on the state of your DTO by implementing the `GroupSequenceProviderInterface` in your DTO class. https://symfony.com/doc/current/validation/sequence_provider.html#group-sequence-providers
```php
use Symfony\Component\Validator\GroupSequenceProviderInterface;

#[Assert\GroupSequenceProvider]
class UserDto implements GroupSequenceProviderInterface
{
    // ...

    public function getGroupSequence(): array|GroupSequence
    {
        if ($this->isCompanyType()) {
            return ['User', 'Company'];
        }

        return ['User'];
    }
}
```
It covers most of the common scenarios, but for more advanced ones, it may not be sufficient. Suppose now you need to provide the sequence of groups from an external configuration (or service) which can change its value dynamically:
```php
#[Assert\GroupSequenceProvider]
class UserDto implements GroupSequenceProviderInterface
{
    // ...

    public __constructor(private readonly ConfigService $config)
    {
    }

    public function getGroupSequence(): array|GroupSequence
    {
        if ($this->config->isEnabled()) {
            return ['User', $this->config->getGroup()];
        }

        return ['User'];
    }
}
```
This issue cannot be resolved at present without managing the DTO initialization and manually setting its dependencies. On the other hand, since the state of the DTO is not used at all, the implementation of the `GroupSequenceProviderInterface` becomes less fitting to the DTO responsibility. Further, stricter programming may raise a complaint about a violation of SOLID principles here.

So, the proposal of this PR is to allow configuring the validation groups provider outside of the DTO, while simultaneously enabling the registration of this provider as a service if necessary.

To achieve this, you'll need to implement a new `GroupProviderInterface` in a separate class, and configure it using the new `provider` option within the `GroupSequenceProvider` attribute:
```php
#[Assert\GroupSequenceProvider(provider: UserGroupProvider::class)]
class UserDto
{
    // ...
}

class UserGroupProvider implements GroupProviderInterface
{
    public __constructor(private readonly ConfigService $config)
    {
    }

    public function getGroups(object $object): array|GroupSequence
    {
        if ($this->config->isEnabled()) {
            return ['User', $this->config->getGroup()];
        }

        return ['User'];
    }
}
```
That's all you'll need to do if autowiring is enabled under your custom provider. Otherwise, you can manually tag your service with `validator.group_provider` to collect it and utilize it as a provider service during the validation process.

In conclusion, no more messing with the DTO structure, just use the new `class` option for more advanced use cases.

---

TODO:

- [x] Add tests
- [x] Create doc PR

Commits
-------

a3a089a15b8 [FrameworkBundle][Validator] Allow implementing validation groups provider outside DTOs
symfony-splitter pushed a commit to symfony/validator that referenced this pull request Oct 20, 2023
…tion groups provider outside DTOs (Yonel Ceruto)

This PR was squashed before being merged into the 6.4 branch.

Discussion
----------

[FrameworkBundle][Validator] Allow implementing validation groups provider outside DTOs

| Q             | A
| ------------- | ---
| Branch?       | 6.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | -
| License       | MIT
| Doc PR        | symfony/symfony-docs#18744

Alternative to symfony/symfony#51233
Inspiration: symfony/symfony#51012

Currently, you can determine the sequence of groups to apply dynamically based on the state of your DTO by implementing the `GroupSequenceProviderInterface` in your DTO class. https://symfony.com/doc/current/validation/sequence_provider.html#group-sequence-providers
```php
use Symfony\Component\Validator\GroupSequenceProviderInterface;

#[Assert\GroupSequenceProvider]
class UserDto implements GroupSequenceProviderInterface
{
    // ...

    public function getGroupSequence(): array|GroupSequence
    {
        if ($this->isCompanyType()) {
            return ['User', 'Company'];
        }

        return ['User'];
    }
}
```
It covers most of the common scenarios, but for more advanced ones, it may not be sufficient. Suppose now you need to provide the sequence of groups from an external configuration (or service) which can change its value dynamically:
```php
#[Assert\GroupSequenceProvider]
class UserDto implements GroupSequenceProviderInterface
{
    // ...

    public __constructor(private readonly ConfigService $config)
    {
    }

    public function getGroupSequence(): array|GroupSequence
    {
        if ($this->config->isEnabled()) {
            return ['User', $this->config->getGroup()];
        }

        return ['User'];
    }
}
```
This issue cannot be resolved at present without managing the DTO initialization and manually setting its dependencies. On the other hand, since the state of the DTO is not used at all, the implementation of the `GroupSequenceProviderInterface` becomes less fitting to the DTO responsibility. Further, stricter programming may raise a complaint about a violation of SOLID principles here.

So, the proposal of this PR is to allow configuring the validation groups provider outside of the DTO, while simultaneously enabling the registration of this provider as a service if necessary.

To achieve this, you'll need to implement a new `GroupProviderInterface` in a separate class, and configure it using the new `provider` option within the `GroupSequenceProvider` attribute:
```php
#[Assert\GroupSequenceProvider(provider: UserGroupProvider::class)]
class UserDto
{
    // ...
}

class UserGroupProvider implements GroupProviderInterface
{
    public __constructor(private readonly ConfigService $config)
    {
    }

    public function getGroups(object $object): array|GroupSequence
    {
        if ($this->config->isEnabled()) {
            return ['User', $this->config->getGroup()];
        }

        return ['User'];
    }
}
```
That's all you'll need to do if autowiring is enabled under your custom provider. Otherwise, you can manually tag your service with `validator.group_provider` to collect it and utilize it as a provider service during the validation process.

In conclusion, no more messing with the DTO structure, just use the new `class` option for more advanced use cases.

---

TODO:

- [x] Add tests
- [x] Create doc PR

Commits
-------

a3a089a15b8 [FrameworkBundle][Validator] Allow implementing validation groups provider outside DTOs
@yceruto
Copy link
Member Author

yceruto commented Oct 20, 2023

Linked PR was merged and I updated this PR according latest changed, so ready for review ;)

@OskarStark OskarStark removed the Waiting Code Merge Docs for features pending to be merged label Oct 20, 2023
@OskarStark OskarStark modified the milestones: next, 6.4 Oct 20, 2023
@javiereguiluz javiereguiluz merged commit c8348c7 into symfony:6.4 Nov 21, 2023
3 checks passed
@javiereguiluz
Copy link
Member

Yonel, thanks a lot for contributing this feature and the docs for it.

It's a really advanced feature, but it's explained nicely, so anybody can use it. Thanks!

@yceruto yceruto deleted the feature/group_sequence_provider branch November 21, 2023 16:18
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.

None yet

4 participants