Problem/Motivation
After #2403817: Feed entity validation misses form validation logic, a unique field value validator was created to simplify and unify definition of base fields. However, to define a unique constraint one still needs to create a new Constraint class and use the same boilerplate code viz:
class UserMailUnique extends Constraint {
public $message = 'The email address %value is already taken.';
/**
* {@inheritdoc}
*/
public function validatedBy() {
return '\Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator';
}
}
class FeedUrlConstraint extends Constraint {
public $message = 'A feed with this URL %value already exists. Enter a unique URL.';
/**
* {@inheritdoc}
*/
public function validatedBy() {
return '\Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator';
}
}
The unique constraint will be used often in contrib for defining entities (I already have 3 use-cases) and it doesn't make sense to create a little new class each time.
Proposed resolution
Make a new generic UniqueField constraint that can be reused in all field definitions. This replaces the existing FeedTitleConstraint
, FeedUrlConstraint
, UserMailUnique
and UserNameUnique
constraints which are virtually clones. We use this new constraint for block content as well, which used to have odd, form-only validation.
We can now define constraints in addConstraint() as opposed to in separate classes. The unique field constraint is used to fix a bug in BlockContentForm where entity validation is still done in the form.
Remaining tasks
Review
Commit
User interface changes
None
API changes
A new UniqueField constraint.
Beta phase evaluation
Issue category | Task (although the form-only validation for block content is arguably a bug) |
---|---|
Issue priority | Major because fixes custom form-only validation for block content |
Prioritized changes | This issue reduces fragility and improves usability of the unique field constraint. |
Disruption | Not disruptive for core. Would be disruptive for contributed and custom modules/themes if they used any of UserMailUnique , UserNameUnique , FeedTitleConstraint or FeedUrlConstraint in their field definitions - this is highly unlikely though. |
Comment | File | Size | Author |
---|---|---|---|
#59 | interdiff.txt | 3.18 KB | stefan.r |
#58 | 2483869-59.patch | 13.13 KB | swentel |
#55 | 2483869-55.patch | 11.25 KB | stefan.r |
#52 | make_unique_field-2483869-52.patch | 12.93 KB | almaudoh |
#52 | interdiff.txt | 623 bytes | almaudoh |
Comments
Comment #1
almaudoh CreditAttribution: almaudoh commentedHere goes.
Comment #2
BerdirHaving a generic implementation for this would be great.
Core has one more use case that is currently not implemented as a constraint at all.
See BlockContentForm::validateForm(). That's weird, custom, form-only validation code.
Comment #3
stefan.r CreditAttribution: stefan.r commentedI'll go ahead and add a
BlockContentDescriptionConstraint
, seems like a good way to test if the patch works while the testbot isn't testing :)Comment #4
stefan.r CreditAttribution: stefan.r commentedWell adding a constraint on the block description worked quite nicely, so your patch makes sense :)
The generic test message doesn't have test coverage and the placeholders aren't easily override-able from the unique field constraints themselves but maybe that's for the better, as people only really need the field value when validating uniqueness.
Comment #5
stefan.r CreditAttribution: stefan.r commentedAnd a test
Comment #6
almaudoh CreditAttribution: almaudoh commentedI'm thinking we can improve this further by removing all the clone classes and figure out a way to override the message in the
BaseFieldDefinition::addConstraint()
where necessary.That way we could get rid of
FeedTitleConstraint
,FeedUrlConstraint
,UserMailUnique
,UserNameUnique
and evenBlockContentDescriptionConstraint
and use the singleUniqueFieldConstraint
in the base field definitions.So something like
Comment #7
stefan.r CreditAttribution: stefan.r commentedSounds like a good idea! I asked about this on IRC and apparently @Berdir had already tried to push for that in other issues but it didn't end up happening because people just wanted to get the issue done.
Comment #8
almaudoh CreditAttribution: almaudoh commentedLike so. Removed all the clones and used a single
UniqueField
constraint for all of them. Also added a test to verify that the user email validator works.Comment #9
stefan.r CreditAttribution: stefan.r commentedNice work! Had a look at the interdiff and don't see any problem with this at first sight. If this passes tests, +1 :)
Comment #10
BerdirShouldn't we use the entity type label here, and not the id? There is a method to get the lowercase label I think. It's actually the same string for user in english, but not other languages, and for example it's block_content vs "custom block".
Ah, you use custom messages then to replace the message completely. sneaky ;) Still, the default should be better I'd say :)
We already have tests for this in UserValidationTest I think.
Comment #11
almaudoh CreditAttribution: almaudoh commented#10:
1. Was thinking about that too. Done.
2. :) Didn't know which tests were based on exact validation messages. I prefer the default message also.
3. Didn't see that. Removed the new test.
Comment #12
almaudoh CreditAttribution: almaudoh commented#10.2: We can remove some (or all) of the custom messages in a later patch after testbot comes back.
Comment #16
stefan.r CreditAttribution: stefan.r commentedTests are green, I think you can remove the custom messages now wherever it makes sense.
IMO we shouldn't remove all of them, but most can be removed.
Comment #17
almaudoh CreditAttribution: almaudoh commentedOk. Testbot is back. Removes custom messages from the username and user email constraints to see what breaks. I still like to retain some of the custom messages as examples for developers of how easily the messages can be overridden.
Comment #18
almaudoh CreditAttribution: almaudoh commentedComment #19
stefan.r CreditAttribution: stefan.r commentedI think we can lose the empty array in the second argument of addConstraint(). We also shouldn't be deleting that newline before the end of the closing brace of the class definition.
As to what will break, you could just do a grep for the error message and replace the verbiage in the relevant tests.
This will also need a beta evaluation.
Comment #21
almaudoh CreditAttribution: almaudoh commentedFixed verbiage in the failing tests and implemented lower case fieldname in the violation error message as well.
This newline was inadvertently introduced by me when I added tests to
UserCreateTest
and is out of scope.Comment #22
almaudoh CreditAttribution: almaudoh commentedForgot that. Fixed now and some more validation error verbiage in the tests. Sorry for the noise.
Comment #24
stefan.r CreditAttribution: stefan.r commentedThis doesn't need a second argument either, or maybe use a custom message in the example?
I wonder if the custom verbiage wasn't clearer than the automatic one. But maybe that just means we need to change the labels on the entity? (ie. s/custom block/block/)
In the examples I've quoted so far "A %entity_type" would've been more correct than "The %entity type". Maybe that would be better for a default message? Also does anyone know, have we standardized on "email" or "email address"? We shouldn't be mixing those up...
Comment #25
almaudoh CreditAttribution: almaudoh commentedWas trying to avoid cases where an entity name like "apple" would result in "A apple with the name...." which sounds weird. Guess that's where the custom message becomes useful.
Comment #26
stefan.r CreditAttribution: stefan.r commentedHmm good point. "A/An" would look as odd as "The", and coding the grammar rules would be a bit of a convoluted way of avoiding custom messages.
Maybe we should stick with the custom messages then as they're more descriptive/end user-friendly, maybe @Berdir can chime in as he had suggested using the defaults.
Comment #27
BerdirI'm OK with keeping the custom messages. It's still a huge simplification compared to what we have now. Generic messages like that are always hard and they are very hard to translate as well, usually.
Comment #28
almaudoh CreditAttribution: almaudoh commentedI don't really know how strings that are not in
t()
or$this->t()
are translated, so another question is: would the custom strings be translatable?Comment #29
BerdirThere is explicit support to translate the default string, but the custom strings need to be passed through t() explicitly, like the labels/descriptions on the field definitions.
Comment #30
stefan.r CreditAttribution: stefan.r commentedWell @almaudoh if you have that doubt I'm sure others would have it as well, so wrapping custom messages in
t()
would remove that doubt :)Also not sure it's worth making custom messages translated by default given unique constraints are a rarity and how little effort it is to just wrap a message in
t()
, which people are used to anyway.May also be worth checking if the labels are already translated (I guess so)?
Comment #31
almaudoh CreditAttribution: almaudoh commentedSo reverted all the original custom messages. Changed default message to "A %entity_type...", entities where that doesn't work can always use custom messages. Also added custom message to the
FieldConfigInterface
comment example.Comment #32
stefan.r CreditAttribution: stefan.r commentedJust looking at the interdiff and this looks great to me now.
Technically maybe the tests should say t() instead of format_string() but those tests are run in English anyway so I don't think that's worth changing.
Comment #33
almaudoh CreditAttribution: almaudoh commentedForgot to make Feed custom message translatable also.
Comment #34
almaudoh CreditAttribution: almaudoh commentedt()
is not encouraged in tests due to the dependencies.Comment #37
stefan.r CreditAttribution: stefan.r commentedUpdating issue summary
Comment #38
stefan.r CreditAttribution: stefan.r commented@Berdir do you think any of this is Major? Or that "reduces fragility" might apply here?
Comment #39
almaudoh CreditAttribution: almaudoh commentedI think it should be major on account of the BlockContent entity validation fix. And "reduces fragility" also applies.
Comment #40
BerdirHm, actually, this doesn't work.
This will immediately translate without replacing the placeholder. So you will have a german string that still has the placeholder. Then we translate it again, using the german string as translation source.
This is one of the cases where we, instead of actually translating, just need to mark it as a translatable string, which is unfortunately something that we still don't have.
Can we switch this to SafeMarkup::format() when we touch it anyway? format_string() never got a @deprecated, but it really is..
Comment #41
stefan.r CreditAttribution: stefan.r commented@almaudoh yes that makes sense, updated issue summary.
As to the translation problem, indeed the translated strings would be retranslated in
ExecutionContext::addViolation()
so we could remove the t()'s but as @Berdir mentions then the potx extractor wouldn't find them, so probabably something like #1542144: Mark strings as localizable/translatable (new t()-alike string function that isn't t(), only for potx) / #2363099: Using translated field definition descriptions in entity schema results in different schema definitions, resulting in update.php changes would be the cleanest way to fix?Comment #42
almaudoh CreditAttribution: almaudoh commentedComment #43
almaudoh CreditAttribution: almaudoh commentedLooks like the translation issue won't be fixed soonest.
We can revert the "clones" and then remove them in a follow up after translation issue is fixed. Thoughts?
Comment #44
larowlanThe BlockContent part of this is a critical bug - opened #2529892: BlockContentForm uses form validation instead of a validation Constraint after confirming with @catch
Comment #45
almaudoh CreditAttribution: almaudoh commentedAnother way we could fix this without the dependency on the translation issue is to just accept the generic violation messages, which are okay IMHO, adjust the strings in the tests and be done with it.
Comment #46
almaudoh CreditAttribution: almaudoh commentedTrying to move this issue forward:
1. Re-instated the sub-classes of
UniqueFieldConstraint
(FeedUrl
,FeedName
,UserNameUnique
andUserMailUnique
) to cater for existing specific violation messages. Note that these sub-classes exist only because we've not yet resolved the translation issue for custom dynamic messages. That sucks, IMHO.2. I didn't create a new subclass for
BlockContent
, which is why the violation message looks weird.We can fix 1) and 2) in follow-ups.
Comment #47
almaudoh CreditAttribution: almaudoh commentedMissed one test assert...
Comment #52
almaudoh CreditAttribution: almaudoh commentedOk. The patch still applies. This fixes the test fail.
Comment #54
almaudoh CreditAttribution: almaudoh as a volunteer commentedNeeds reroll after #2529892: BlockContentForm uses form validation instead of a validation Constraint went in.
Comment #55
stefan.r CreditAttribution: stefan.r commentedAttempt at a reroll
Comment #57
almaudoh CreditAttribution: almaudoh as a volunteer commentedKeeping an eye on #2557113: Make t() return a TranslationWrapper object to remove reliance on a static, unpredictable safe list, as it may help resolve the custom translated messages.
Comment #58
swentel CreditAttribution: swentel commentedComment #59
stefan.r CreditAttribution: stefan.r commentedlooks great
Comment #60
stefan.r CreditAttribution: stefan.r commentedComment #61
jibranLGTM
Comment #62
alexpottLess boilerplate will help people make less mistakes. Committing under the committer discretion provision in the beta policy. Committed c73fdfc and pushed to 8.0.x. Thanks!