-
Notifications
You must be signed in to change notification settings - Fork 821
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
ENH Use FieldValidators for FormField validation #11449
base: 6
Are you sure you want to change the base?
ENH Use FieldValidators for FormField validation #11449
Conversation
cb45d46
to
e733757
Compare
e733757
to
aaba479
Compare
/** | ||
* Converter to convert date strings to another format for display in error messages | ||
* | ||
* @var callable |
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 using strong typing because you cannot strongly type a callable property in PHP, even though you can assign it to an untyped property
fc9d6db
to
bd8e82d
Compare
'Field validates values in source map' | ||
); | ||
|
||
$field->setValue( |
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 setValue() / assertion did nothing, the ArrayData is ignored in setValue(), so all it's testing is that setting a value of false
is valid
@@ -256,7 +268,7 @@ public function getValueArray() | |||
$replaced = []; | |||
foreach ($value as $item) { | |||
if (!is_array($item)) { | |||
$item = json_decode($item, true); | |||
$item = json_decode($item ?? '', true); |
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.
?? '' to prevent deprecation warning if setting a value of null
bd8e82d
to
cd48fd0
Compare
*/ | ||
protected function internalToFrontend($time) | ||
public function internalToFrontend(mixed $value): ?string |
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.
Changed to public as required by FieldValidatorConverterInterface
@@ -102,8 +102,6 @@ abstract class DBField extends ModelData implements DBIndexable, FieldValidation | |||
'ProcessedRAW' => 'HTMLFragment', | |||
]; | |||
|
|||
private static array $field_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.
Duplicate as is already defined on FieldValidationTrait
@@ -72,11 +78,28 @@ public function setValue($value, $obj = null) | |||
if ($obj instanceof DataObject) { | |||
$this->loadFrom($obj); | |||
} else { | |||
parent::setValue($value); | |||
parent::setValue($value, $obj); |
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 argument should have been passed through to parent before this PR
2060ed8
to
6e6a280
Compare
* @see Formfield::setValue() | ||
* @return mixed | ||
*/ | ||
public function dataValue() |
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.
Moved location in the file to be closer to the other *Value() methods
d7b8aac
to
ae7dcb8
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.
Didn't do a full review, just looked at things that are relevant or adjacent to relevancy for the CMS 5 PRs.
*/ | ||
protected function extendValidationResult(bool $result, Validator $validator): bool |
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 are we getting rid of this? Did you look at the PR that added it and the reasoning for it? #10569 (comment)
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.
Yes I did look at it. That's no longer compatible with the new way of doing this
The old FormField::validate()
method had a signature of validate(Validator $validator): bool
, whereas this has now been updated to match DataObject and DBField with validate(): ValidationResult
The replicate the extension functionality, there's a new extension hook updateValidate
on FormField::validate() and a ValidationResult
param to use. I've used this in combination with $this->beforeExtending('updateValidate')
and calling parent::validate() on any overridden validate method so that the extension hook is always called. I've added a unit test on FormFieldTest::testUpdateValidateHookCalled()
to ensure this is happening
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.
Sweet. I think so long as that test is still there that's the main thing.
I do like that this now means the updateValidate
extension hook is the same method signature for both DataObject
and FormField
- we should call that out in the changelog probably.
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.
Have mentioned in 6.0.0 changelog - https://github.com/silverstripe/developer-docs/pull/621/files#diff-027bda823f29903fb90f1ca3e86dc918c3b6a814c50096c66a3074b1da24eb62R799
* Returns the field value. | ||
* Returns the internal value of the field | ||
*/ | ||
public function getValue(): mixed |
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 now have four different variations on getValue()
.... that's confusing as hell. Why do we need 4 different ways to get the 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.
We need them all?
getValue()
= Get the interval value of the field (this->value), which should be as "unmodified" as possible. Required to meet FieldValidationInterface
getValueForValidation()
= Used for FieldValidators. Defaults to $this->getValue(). Something need modify this for some FieldValidators, such as CompositeFieldValidator() which validates child fields rather than the composite field itself. Required to the meet FieldValidationInterface.
dataValue()
= Get a value the for insertion in the database. Will do some casting. If were were to get rid of one of the methods, I'd say it should be this one. But definitely not in this PR.
Value()
= Get a value for use in templates. May modify the value quite a bit
Looking at this again, getValueForValidation()
is possibly badly named as it's only intended to be used by FieldValidators, and not custom validate() methods which should use getValue() instead. There's a few options
a) Leave as is
b) Rename the method to getValueForFieldValidation()
c) Better doc block description
d) Remove from FormField + DBField and put it on FieldValidationTrait since they're identical
Perhaps b), c) and d)?
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.
Yeah, c/d sounds good.
In my mind the name you've suggested there is functionally identical so I don't care if it gets renamed to that or not. This is a field, so all of its validation is "field validation" even if it's not all using a FieldValidator.
That still doesn't solve the problem that we're going from two methods to four in this PR. Most developers will be able to rule out getValueForValidation()
for their use case, but this PR still makes the decision go from "do I use value()
or dataValue()
to now do I use
value()or
getValue()or
dataValue()`?
getValue()
and Value()
are so identically named that I would expect them to always return the same value, with one being a legacy alias for the other.
Value()
= Get a value for use in templates. May modify the value quite a bit
I assume this is used via getDefaultAttributes()
and getSchemaStateDefaults()
, not directly via $Value
in templates? If that's the case, it would be sensible to either rename it to something like getValueForHtmlAttributes()
or else transformValueForDataAttribute($value)
or something like that.
That makes it immediately clear that if I'm trying to just get the value from the field in my PHP code, I don't want to call that one.
That still leaves us with getValue()
vs dataValue()
, but at least that confusion already existing before this PR so I'd be okay with leaving it at that point.
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 assume this is used via getDefaultAttributes() and getSchemaStateDefaults(), not directly via $Value in templates?
I'm not sure where or how it's exactly used, my best understanding is that it prepares the value for display to a human. Here's a few examples of how it's implemented in subclasses
class DateField extends TextField
// ..
public function Value()
{
return $this->internalToFrontend($this->value);
}
class SiteTreeURLSegmentField extends TextField
// ...
public function Value()
{
return rawurldecode($this->value ?? '');
}
class ReadonlyField
// ...
public function Value()
{
// Get raw value
$value = $this->dataValue();
if ($value) {
return $value;
}
// "none" text
$label = _t('SilverStripe\\Forms\\FormField.NONE', 'none');
return "<i>('{$label}')</i>";
}
We can't use getValue() as an alias for Value(), as Value() isn't simply returning $this->value
Feels like the best we can do here is to have better PHP doc for both Value() and getValue()
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't use getValue() as an alias for Value()
I'm not suggesting we do that at all - I'm saying that developers will expect that because they're basically the same named method. The name of the methods is what's problematic here.
I'm suggesting we rename Value()
to something more specific so that developers don't have any confusion about what the difference between getValue()
and Value()
is. PHPDoc will help. Well named methods is better.
I'm not sure where or how it's exactly used
Please find out where and how it's used, and then give it a name that is appropriate for that usage.
That will make it easier both to know when to call it, and to know what to implement inside that method when implementing new form fields.
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.
In PHP It's primarily called within FormFields as $this->Value()
There's a handful of other places, seems to be mainly GridField related such as GridFieldNestedForm, GridFieldAddNewInlineButton that will call something like $formField->Value(). You could make an argument that alot of these should be switched to use getValue() instead, but that's outside the scope of this PR
It's used a lot in FormField templates as $Value
I think we should simply keep things as they are
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.
Keeping things as they are is not an option IMO. We can't add a getValue()
and not rename Value()
because the two methods are named too-similarly and it will cause unnecessary confusion. I'm already confused by them and this hasn't even been merged.
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 split off as new card, the scope of work is too large (~50 files to change) and will just muddy up this PR. Also a high risk of breaking stuff
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 PR adds the confusion, so this PR should clear it up, unless you can promise me the split off card is the very next thing you work on.
* | ||
* @var mixed | ||
*/ | ||
protected $rawValue = null; |
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.
While I am glad to see we're not storing two sets of the value now... please explain what this was used for and why we don't need it anymore?
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.
On DateField/DatetimeField/TimeField it was was an entirely unmodified value that came in from setValue() / setSubmittedValue(). Then the internal value $this->value was changed due to all the localisation fun that happens. rawValue was then checked to see if the field was empty all along and to not validate it if was.
tl;dr it was pointless
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.
Sweet, sounds like it was a non-standard workaround for the way the value property was being (ab)used. I'm okay with it being axed.
f740332
to
3e87f73
Compare
3e87f73
to
51a2dbf
Compare
Issue #11422