-
Notifications
You must be signed in to change notification settings - Fork 820
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
v4 improvement: Standardise getCMSValidator for DataObjects/Forms #9406
v4 improvement: Standardise getCMSValidator for DataObjects/Forms #9406
Conversation
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.
Nice work! A few tweaks but I really like the approach here, it’s always bugged me that there’s no base getCMSValidator
implementation and we rely on method_exists()
...
src/Forms/ValidatorList.php
Outdated
/** | ||
* @var ArrayList|Validator[] | ||
*/ | ||
private $validators; |
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.
Any particular reason why this is an ArrayList
instead of just an array?
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 don't have any strong feelings about ArrayList
vs array
. I think I just wondered if maybe(?) an ArrayList
could be more useful to someone somewhere.
I would be happy to change it.
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.
My preference is an array as ArrayList
is intended for models and model-like data structures, which this isn’t. I’m struggling to think of a use case that would benefit from it being an ArrayList
😅 you can always manually wrap it in an ArrayList
anyway
Thank you for the feedback, @kinglozzer ! I really appreciate it. I'll be working on implements all of your comments today :) |
4c2db7d
to
15cebff
Compare
src/Forms/DefaultFormFactory.php
Outdated
} | ||
|
||
$validator = $context['Record']->getValidatorList(); | ||
|
||
// Extend validator | ||
$this->invokeWithExtensions('updateFormValidator', $validator, $controller, $name, $context); |
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 think this will need to be amended slightly - currently $validator
is a single validator, not a list. I think the safest way is probably just to do:
foreach ($validator->getValidators() as $validatorListItem) {
$this->invokeWithExtensions('updateFormValidator', $validatorListItem, $controller, $name, $context);
}
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.
For BC concerns, you're right, we'll need to continue to send what was previously being sent.
I would be keen to add in a second extension point as well - one for updateValidatorList
. I'm not entirely sure if I can reasonably throw a deprecation notice on the old one, but at the very least, I can add some comments around it.
src/Forms/ValidatorList.php
Outdated
/** | ||
* @var ArrayList|Validator[] | ||
*/ | ||
private $validators; |
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.
My preference is an array as ArrayList
is intended for models and model-like data structures, which this isn’t. I’m struggling to think of a use case that would benefit from it being an ArrayList
😅 you can always manually wrap it in an ArrayList
anyway
Thanks again for the feedback, @kinglozzer ! I have implemented those change requests :) |
Is there any additional feedback for me on this one? |
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’m happy with this, I’ll give it a few days in case any other committers have any feedback/changes and then merge
cc @silverstripe/core-team |
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.
A few comments, I think the API docs could use a bit of work. Imagine that someone knows nothing about this class except what you write in the API docs.
src/Forms/ValidatorList.php
Outdated
use SilverStripe\ORM\ValidationResult; | ||
|
||
/** | ||
* Class ValidatorList |
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.
Docblock please – we're introducing a public API that will be supported for years to come.
src/Forms/ValidatorList.php
Outdated
* | ||
* @package SilverStripe\Forms | ||
*/ | ||
class ValidatorList extends Validator |
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.
Not sure about the name ValidatorList. Does the order of the validators impact behaviour? If not, it's a set rather than a list. Aside from that my instinct would be to call this a CompositeValidator, as the name ValidatorList doesn't fully communicate that it is itself a Validator.
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.
CompositeValidator
it is! Thanks.
@@ -0,0 +1,25 @@ | |||
<?php |
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.
Could you do this with reflection instead?
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.
Mostly I was just following the paradigm already set by the Validator
tests.
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.
OK fair enough 👍
src/Forms/ValidatorList.php
Outdated
* @param int $key | ||
* @return ValidatorList | ||
*/ | ||
public function removeValidatorByKey(int $key): ValidatorList |
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.
How confident are we that this is the right public API to add?
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.
Happy to make it protected
. If it's a feature that folks want in the future, they'll ask for it.
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 agree - it feels a bit odd, whereas removeValidatorsByType
makes a lot of sense, and matches the GridFieldConfig equivalent nicely
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.
Maybe protected and put @internal
in the docblock so people know we might change it in the future.
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.
Sounds good. Thanks.
src/ORM/DataObject.php
Outdated
/** | ||
* @return ValidatorList | ||
*/ | ||
public function getValidatorList(): ValidatorList |
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 needs a better Docblock too. Again, we're introducing an API that will be used by 1000s of different sites and we need to be able to make changes to how the core uses it without breaking those sites. No pressure! ;-)
Some things we need to clarify, some of which might prompt adjustments to our API...
- Is the validator applied here intended to be only used in the CMS/admin context, or is it safe to use for front-end forms as well?
- How do we define the difference between those two use-cases?
- If just for the admin context, should it be called
getCMSValidatorList()
? - If it's intended to be used multiple contexts, do we need to assume that the validator will be identical in all contexts? If not, should there be a string $context argument that could be e.g. 'cms', 'website', but app developers could also use and handle their own contexts? How should an unknown context be treated?
- How should people customise this in subclasses? Overriding the method will mean that people need to re-implement the extension code, or face having extensions applied before their subclass overrides – this mirrors getCMSFields / updateCMSFields, but has been a pain there. Perhaps there needs to be a pair of methods:
- one is
public final
and is the one you call. If contains the invokeWithExtensions() call. - one is
protected
and is the one you override. By default it contains the getCMSValidator polyfill.
- one is
Related to this although I think CompositeValidator a great class name, I've got more mixed feelings about calling this accessor getCompositeValidator()
. What do others think? cc @robbieaverill @unclecheese
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 the validator applied here intended to be only used in the CMS/admin context, or is it safe to use for front-end forms as well?
Are Validator
s in general intended for both? Because the CompositeValidator
can be used in the same way as a regular Validator
. It has the same public API, and returns the same (single) ValidationResult
.
I can definitely update the docblock to reflect whatever the answer to the above is.
How should people customise this in subclasses?
It is intended to be used in the same way as getCMSFields
. I feel like that's a paradigm that we're all familiar with. If we're wanting to change that paradigm, then I'm definitely happy to take that on as part of this change - so long as it's going to be mirrored in the future for other areas (like getCMSFields
, and getCMSActions
).
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.
Validator is available to all Forms. getCMSValidator()
was used as the companion of getCMSFields()
and was intended to be applicable to administrative fields only.
Potentially we should call this getCMSCompositeValidator()
if we want to maintain the same scope of applicability?
Regarding subclassing, retaining the same setup as getCMSFields()
in spite of the same limitations seems okay.
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.
Cool. getCMSCompositeValidator()
sounds good to me, and matches the current convention.
Thanks :)
97cb176
to
8ba6531
Compare
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.
LGTM now
Nice! Thank you for all of the feedback, @sminnee! |
Linting fixed. Should (hopefully) be good to go. Please let me know if there is any additional feedback. |
How's everyone feeling about this one? |
Loz and I have given it a 👍, @unclecheese or @robbieaverill do you want to merge? |
It looks cool. I've not been super involved with this PR, but LGTM. |
Nice work @chrispenny! |
Wooo! Thank you for all the help, team! |
Standardise getCMSValidator for DataObjects/Forms
In response to the issue raised here:
#9407
Dependent PRs (to be merged after this)
Changes
ValidatorList
The
ValidatorList
deals with an array of validators. You can add additional validators (or edit existing validators) through extension points/etc.This
ValidatorList
is itself a subclass ofValidator
, so all existing methods are available on it (generally this means that it collates info from its array of validators, and then returns a single expected value, EG: a singleValidationResult
), and they return values as expected. It should, therefor, be compatible with all of our current implementations/usages ofValidator
.New method
getValidatorList()
onDataObject
Wherever we previously called
getCMSValidator()
we should now instead callgetValidatorList()
.In order to provide BC for
4
, this new method will check for the existence ofgetCMSValidator
on theDataObject
, and if it exists, it will add that validator to theValidatorList
.Extra
Some related discussion here:
silverstripe/silverstripe-admin#1005