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

v4 improvement: Standardise getCMSValidator for DataObjects/Forms #9406

Merged

Conversation

chrispenny
Copy link
Contributor

@chrispenny chrispenny commented Feb 13, 2020

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 of Validator, 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 single ValidationResult), and they return values as expected. It should, therefor, be compatible with all of our current implementations/usages of Validator.

New method getValidatorList() on DataObject

Wherever we previously called getCMSValidator() we should now instead call getValidatorList().

In order to provide BC for 4, this new method will check for the existence of getCMSValidator on the DataObject, and if it exists, it will add that validator to the ValidatorList.

Extra

Some related discussion here:

silverstripe/silverstripe-admin#1005

Copy link
Member

@kinglozzer kinglozzer left a 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/Form.php Outdated Show resolved Hide resolved
src/Forms/GridField/GridFieldDetailForm.php Outdated Show resolved Hide resolved
src/Forms/Validator.php Outdated Show resolved Hide resolved
/**
* @var ArrayList|Validator[]
*/
private $validators;
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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

src/Forms/ValidatorList.php Outdated Show resolved Hide resolved
src/Forms/ValidatorList.php Outdated Show resolved Hide resolved
src/ORM/DataObject.php Outdated Show resolved Hide resolved
src/Forms/DefaultFormFactory.php Show resolved Hide resolved
@chrispenny
Copy link
Contributor Author

Thank you for the feedback, @kinglozzer ! I really appreciate it. I'll be working on implements all of your comments today :)

@chrispenny chrispenny force-pushed the feature/standardise-get-cms-validator branch from 4c2db7d to 15cebff Compare February 13, 2020 20:23
@chrispenny chrispenny changed the base branch from 4.5 to 4 February 13, 2020 20:23
@chrispenny chrispenny marked this pull request as ready for review February 18, 2020 22:38
@chrispenny chrispenny changed the title WIP/POC: Standardise getCMSValidator for DataObjects/Forms v4 improvement: Standardise getCMSValidator for DataObjects/Forms Feb 18, 2020
}

$validator = $context['Record']->getValidatorList();

// Extend validator
$this->invokeWithExtensions('updateFormValidator', $validator, $controller, $name, $context);
Copy link
Member

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);
}

Copy link
Contributor Author

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.

/**
* @var ArrayList|Validator[]
*/
private $validators;
Copy link
Member

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

@chrispenny
Copy link
Contributor Author

Thanks again for the feedback, @kinglozzer !

I have implemented those change requests :)

@chrispenny
Copy link
Contributor Author

Is there any additional feedback for me on this one?

Copy link
Member

@kinglozzer kinglozzer left a 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

@robbieaverill
Copy link
Contributor

cc @silverstripe/core-team

Copy link
Member

@sminnee sminnee left a 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.

use SilverStripe\ORM\ValidationResult;

/**
* Class ValidatorList
Copy link
Member

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.

*
* @package SilverStripe\Forms
*/
class ValidatorList extends Validator
Copy link
Member

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.

Copy link
Contributor Author

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
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK fair enough 👍

* @param int $key
* @return ValidatorList
*/
public function removeValidatorByKey(int $key): ValidatorList
Copy link
Member

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?

Copy link
Contributor Author

@chrispenny chrispenny May 27, 2020

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.

Copy link
Contributor

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

Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Thanks.

/**
* @return ValidatorList
*/
public function getValidatorList(): ValidatorList
Copy link
Member

@sminnee sminnee May 27, 2020

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.

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

Copy link
Contributor Author

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 Validators 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).

Copy link
Member

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.

Copy link
Contributor Author

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 :)

@chrispenny chrispenny force-pushed the feature/standardise-get-cms-validator branch from 97cb176 to 8ba6531 Compare May 27, 2020 23:18
Copy link
Member

@sminnee sminnee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now

@chrispenny
Copy link
Contributor Author

Nice! Thank you for all of the feedback, @sminnee!

@chrispenny
Copy link
Contributor Author

Linting fixed. Should (hopefully) be good to go. Please let me know if there is any additional feedback.

@chrispenny
Copy link
Contributor Author

How's everyone feeling about this one?

@sminnee
Copy link
Member

sminnee commented Jul 16, 2020

Loz and I have given it a 👍, @unclecheese or @robbieaverill do you want to merge?

@robbieaverill
Copy link
Contributor

It looks cool. I've not been super involved with this PR, but LGTM.

@robbieaverill robbieaverill merged commit 84b4057 into silverstripe:4 Jul 16, 2020
@robbieaverill
Copy link
Contributor

Nice work @chrispenny!

@chrispenny chrispenny deleted the feature/standardise-get-cms-validator branch July 16, 2020 23:26
@chrispenny
Copy link
Contributor Author

Wooo! Thank you for all the help, team!

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

5 participants