-
Notifications
You must be signed in to change notification settings - Fork 822
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
NEW Validate DBFields #11408
base: 6
Are you sure you want to change the base?
NEW Validate DBFields #11408
Conversation
00a13da
to
1f4a38a
Compare
926371b
to
9c169bd
Compare
9614506
to
2b9af83
Compare
src/Core/Validation/FieldValidation/CompositeFieldValidator.php
Outdated
Show resolved
Hide resolved
5242fd9
to
158a9d4
Compare
There's a bunch of stuff still not addressed. |
2b7c95c
to
913734a
Compare
913734a
to
c4d4886
Compare
@GuySartorelli after rebasing in the template work I needed to make a small change to a test in CastingServiceTest.php as DBCurrency extends DBDecimal and getValue() now returns a float, previously the test was expecting a 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.
Looks mostly good, but found a few anomolies while testing
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 testing I'm able to enter values like "this is not a boolean" and instead of getting a validation error, the value just gets turned into 1
.
This seems to be related to setting the value from forms specifically - setting the value programatically results in the appropriate ValidationException
.
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.
That's behaving as expected due to prepValueForDB() being called before DBField->validate()
public function prepValueForDB(mixed $value): array|int|null
{
$bool = $this->convertBooleanLikeValue($value);
// Ensure a tiny int is returned no matter what e.g. value is an
return $bool ? 1 : 0;
}
Once we put on FormField validation, these validation message will be surfaced in the CMS
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.
That's behaving as expected due to
prepValueForDB()
being called beforeDBField->validate()
Seems to me like those are being called the wrong way around. Validation should be performed on the value that is currently stored. prepValueForDB()
should then operate on values which have been validated and are known to not be garbage and is only used to (as the method name says) prepare the valid value for storage in the database. It should not be used to transform invalid values into valid ones because that goes against the whole purpose of the validation.
tl;dr "this is not a boolean" is not a boolean value, so setting that value in a DBBoolean
field in a DataObject
record and then calling write()
on that record should result in a validation exception being thrown.
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.
Apologies, confused myself, DBField->validate() is called BEFORE DataObject::prepValueForDB()
They're both called from DataObject::write() - order below with the call at the top being first
DataObject::write()
DataObject::preWrite()
- DataObject::validateWrite()
- DataObject::validate()
- DBField::validate()
DataObject::writeBaseRecord() / DataObject::writeManipulation()
- DataObject::prepareManipulationTable()
- DBField::writeToManipulation()
- DBField::prepValueForDB()
values such as 't' are converted to the appropriate bool in DBBoolean::setValue() BEFORE validation which retains backwards compatibility
In testing I'm able to enter values like "this is not a boolean" and instead of getting a validation error, the value just gets turned into 1. .. This seems to be related to setting the value from forms specifically
Just ignore any 'setting via forms' for now as that's out of scope for this card, and is being handled in a follow up card. We only need to be concerned with setting programmatically for now
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 only need to be concerned with setting programmatically for now
In that case I'm going to need to add some ACs to the formfield card (which we didn't refine or else we would have probably caught this at the time)
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.
As discussed in person, the concern here is when someone uses a form field other than the one that's scaffolded. e.g. in the case above I was using a TextField
. The value from that text field was definitely 100% not a valid boolean, and the form field validation wouldn't catch that.
We need to find out why the value was being transformed, and make sure it instead gets correctly validated by the DBField
validation.
You mentioned in our discussion you probably want to do it separately to this PR - in that case please open a new card 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.
That particular instance is is happening in DBBoolean::saveInto()
, which only happens as part of saving a form in the CMS - $dataObject->__set($fieldName, (bool) $this->value);
I'll open a third card for one as I think it doesn't quite fit on the FormField one, as this is a DBField interacting Forms issue which is a little different
d18eeeb
to
1a6c9ce
Compare
36989d6
to
873e01d
Compare
873e01d
to
a5133fd
Compare
Issue #11403
Adds a $value param to ValidationResult. This is mainly to make debugging easier.Note that while this PR adds support to FormField for FieldValidators, I haven't actually added the FieldValidators to the FormField's in this PR, as too much would be changing at once and many things would break in CI. Instead I've split off a separate issue for that to be done after this has been mergedHave removed FormField changesThere is some type conversion going on in
DBField::setValue()
, which happens before validation, though I'm trying to not be too permissive as this PR is essentially adding strict typing to DBFields. Some examples:'t'
will get converted totrue
A few things that we might want to consider changing (easiest done as follow up cards):
DBBoolean should possibly stop accepting values such as 't'will retain backward compatibilityDBDate currently accepts a range of dates and attempts to fix things. Changes were made in this PR, though one not unexpected and not great side effect is that a value that previously threw an error duringHave reverted changes, now behaves like CMS 5DBDate::setValue()
"03-03-04" now gets turned into "2003-03-04". The existing behaviour of turning "0" into "1970-01-01" remain. Perhaps we should be stricter and only allow 'Y-m-d' style inputs.Also to answer the question "why are we even doing this when you can just get the database to do this?". It's because we cannot give any guarantees is MySQL is set in strict mode (will complain and not update value when invalid) vs not in strict mode (will truncate and insert). Also, FieldValidator is an abstract class that will be shared with FormField validation, which doesn't even touch the database. Also, things like EmailFieldValidator can't be validated by MySQL.