-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Validator] Add constraint on unique elements collection(Assert\Unique) #26555
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
Conversation
zzenmate
commented
Mar 15, 2018
Q | A |
---|---|
Branch? | master |
Bug fix? | no |
New feature? | yes |
BC breaks? | no |
Deprecations? | no |
Tests pass? | no |
Fixed tickets | #26535 |
License | MIT |
Doc PR | symfony/symfony-docs#... |
Please, review feature. |
return; | ||
} | ||
|
||
if (!is_array($value) && !($value instanceof \Traversable && $value instanceof \ArrayAccess)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scrap ($value instanceof \Traversable && $value instanceof \ArrayAccess)
and replace with !$value instanceof \IteratorAggregate
. It's apparently interface of Traversable highest up in a chain which guarantees iterator is rewindable
|
||
$collectionElements = []; | ||
foreach ($value as $element) { | ||
if (in_array($element, $collectionElements)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
third argument to true is important here to avoid obscure bugs https://3v4l.org/9QGNo
public function validate($value, Constraint $constraint) | ||
{ | ||
if (!$constraint instanceof UniqueCollection) { | ||
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UniqueCollection'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use ::class now
* | ||
* @author Yevgeniy Zholkevskiy <[email protected]> | ||
*/ | ||
class UniqueCollection extends Constraint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Symfony constraints don't tend to add such suffix. See e.g. constraint "All". Should be just Unique
. Would be in line with #9888 as well.
Would wait for a symfony team member to confirm, but I think this is pretty straight forward and feature should be accepted. status: needs review |
return; | ||
} | ||
|
||
if (!$value instanceof \IteratorAggregate) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_array part must stay, I wanted to change second part of condition only. This won't work with arrays now.
@zzenmate please check fabbot's failure |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your contribution!
foreach ($value as $element) { | ||
if (in_array($element, $collectionElements, true)) { | ||
$this->context->buildViolation($constraint->message) | ||
->setParameter('{{ value }}', $this->formatValue($value)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionnally to fabbot reported issues, there is too much indentation here:
$this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Unique::IS_NOT_UNIQUE)
- ->addViolation();
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Unique::IS_NOT_UNIQUE)
+ ->addViolation();
return; | ||
} | ||
|
||
if (!is_array($value) && !$value instanceof \IteratorAggregate) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen @ostrolucky 's #26555 (comment), but actually, we don't need ArrayAccess
here, right?
So !is_array($value) && !$value instanceof \Traversable
would be enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought so too until recently. Apparently this is considered too unsafe, see #24577 (comment) and #25506
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, IIUC that's an issue we already have with AllValidator
then?
Was it already raised somewhere and do we need to add a deprecation to warn about this issue if $value
isn't implementing IteratorAggregate
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dunno, what do you say @nicolas-grekas? I guess there should be an RFC to decide how to move forward in consistent way and let others to speak their mind about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd keep it as it is.
$collectionElements[] = $element; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing newline at end of file
status: needs review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test suite is IMO too noisy. Check e.g. RegexValidatorTest to learn how to utilize dataProviders to shrink this.
@ostrolucky Thank you for your advice! I have done it. |
} | ||
|
||
if (!is_array($value) && !$value instanceof \IteratorAggregate) { | ||
throw new UnexpectedTypeException($value, 'IteratorAggregate'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry but I think we should not throw here, to avoid issues like this one #26463
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with a minor comment
*/ | ||
class Unique extends Constraint | ||
{ | ||
const IS_NOT_UNIQUE = '7911c98d-b845-4da0-94b7-a8dac36bc55a'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public const
return; | ||
} | ||
|
||
if (!is_array($value) && !$value instanceof \IteratorAggregate) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd keep it as it is.
oh, please also mention the new constraint in the changelog of the component |
Friendly ping @zzenmate, could you please add an entry in the changelog file of the component? |
And create a doc issue/PR also please? |
ad8e7d4
to
bfa3278
Compare
bfa3278
to
d0eb13e
Compare
Rebased, ready. |
Thank you @zzenmate. |
…on(Assert\Unique) (zenmate, nicolas-grekas) This PR was merged into the 4.3-dev branch. Discussion ---------- [Validator] Add constraint on unique elements collection(Assert\Unique) | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | no <!-- please add some, will be required by reviewers --> | Fixed tickets | #26535 | License | MIT | Doc PR | symfony/symfony-docs#... <!-- required for new features --> <!-- Write a short README entry for your feature/bugfix here (replace this comment block.) This will help people understand your PR and can be used as a start of the Doc PR. Additionally: - Bug fixes must be submitted against the lowest branch where they apply (lowest branches are regularly merged to upper ones so they get the fixes too). - Features and deprecations must be submitted against the master branch. --> Commits ------- d0eb13e Rebase and update to latest CS fc66683 Add UniqueCollection constraint and validator
} | ||
|
||
if (!\is_array($value) && !$value instanceof \IteratorAggregate) { | ||
throw new UnexpectedValueException($value, 'array|IteratorAggregate'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not iterable
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see review chain
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought so too until recently. Apparently this is considered too unsafe, see #24577 (comment) and #25506
?
was it a blocker for this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i can pass a broken/unrewindbable iterator as aggregate just as easy. But i cant pass a working iterator/iterable directly. That's poor DX IMHO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consequencies of this validator being applied on non-rewindable iterators are much bigger than in #25506 so yes (generally, after validator validates the data, data is processed)
* | ||
* @author Yevgeniy Zholkevskiy <[email protected]> | ||
*/ | ||
class Unique extends Constraint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UniqueCollection
?