-
Notifications
You must be signed in to change notification settings - Fork 825
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 Show more information in ValidationException message #11562
base: 6.0
Are you sure you want to change the base?
NEW Show more information in ValidationException message #11562
Conversation
e148ac2
to
2a63e66
Compare
2a63e66
to
89727e1
Compare
6b90450
to
53569d9
Compare
53569d9
to
dd52956
Compare
263d324
to
adf3ca1
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.
Haven't tested locally yet - but the below stick out to me
if ($this->doShowAdditionalInfo()) { | ||
if ($message['fieldName']) { | ||
$exceptionMessage .= ' - fieldName: ' . $message['fieldName']; | ||
} | ||
$dataClass = $result->getDataClass(); | ||
if (is_subclass_of($dataClass, DataObject::class, true)) { | ||
$exceptionMessage .= ', recordID: ' . $result->getRecordID(); | ||
$exceptionMessage .= ', dataClass: ' . $dataClass; | ||
} | ||
} |
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 there ever a reason not to include this additional info in the exception message? e.g. where ValidationException
gets thrown for a CMS form submission and $result
is a ValidationResult
, the actual message isn't parsed by the UI - instead the validation result's individual messages are included in JSON in the response and are used appropriately.
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 quite understand what you mean by this
There's a call to $this->doShowAdditionalInfo()
above which checks that we're either in a CLI context or the current controller is Development Admin
The rest of the time, for instance when doing XHR form submissions, the fieldname, recordID and dataClass aren't useful to return because they are already known by the form/field that's being submitted
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.
What I mean is, why do we need doShowAdditionalInfo()
at all?
This discussion can ultimately be handled in #11562 (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.
As mentioned in #11562 (comment) I think we need to keep doShowAdditionalInfo()
// e.g. dev/build | ||
if (is_a(Controller::curr(), DevelopmentAdmin::class)) { | ||
return 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.
Why specifically that controller? I feel like it makes more sense to have a configurable deny list, and say "If the controller is in the deny list, return false" (e.g. LeftAndMain
and maybe even up to FormSchemaController
should be in the deny list, but if the current controller is front-end or custom, we should assume the extra context is needed unless developers say otherwise)
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.
The point of this PR is to add additional info to the error message string, e.g. fieldName
. fieldName
is already part of ValidationResult. However in a CLI or dev/build context, you simply get the error message outputted to the terminal/screen - you don't see the rest of the metadata in the ValidationResult object.
We don't want the fieldname being suffixed to the error message string in a UX context because then you'd end up with a validation message like "Invalid email address - fieldname: Email" showing up underneath the Email field which already has a label "Email"
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 a UX context (at least in the CMS) the exception message isn't used, the validation result messages are accessed directly, so the additional context does not display in the edit form.
I think this condition of "where are we? Should we add the info?" isn't needed - it should just be always added to the exception message.
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.
Looking at LeftAndMain::handleRequest() - it's using $e->getMessage(), which a native PHP method for Exception. In ValidationException, whose parent class is Exception, we're calling parent::__construct($exceptionMessage, $code);
LeftAndMain::handleRequest is obviously a pretty central place, so it seems pretty likely this additional info will popup on the frontend where we don't intend it to.
I've upgraded the the allow list to make it configurable
src/ORM/DataObject.php
Outdated
@@ -1257,7 +1257,7 @@ public function forceChange() | |||
*/ | |||
public function validate(): ValidationResult | |||
{ | |||
$result = ValidationResult::create(); | |||
$result = ValidationResult::create(get_class($this), $this->ID); |
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 this the only place this gets set in core right now?
What about if I call validate()
on a form, and the form belongs to a specific model? E.g. I could conceivably set up a PHP Form
for some record that can be submitted from CLI using the fancy symfony console IO functionality.
Personally I think that scenario is likely to contain all the relevant context already, but want to get your thoughts and make sure that sort of thing has been considered.
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 Form has no awareness of a DataObject, it simply keeps track of key=value style data. The only time it interacts with DataObject is when calling Form::saveInto(DataObjectInterface $dataObject)
It's the same story with most the other things that call ValidationResult::create() and could be related to DataObjects, e.g. FormField, ConstraintValidator, FieldValidator
MemberAuthenticator and PasswordValidator do deal with Member DataObjects, though I don't we shouldn't be displaying any values for those in a CLI 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've updated the PR to remove the constructor arguments as that functionality is basically only for DataObjects and not for other thing contexts ValidationResult is used in such as Forms. I've updated DataObject::validate() to call $result->setModelClass(static::class);
and $result->setRecordID($this->ID);
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.
I've updated the PR to remove the constructor arguments as that functionality is basically only for DataObjects
I think the additional context should still be settable in the constructor. It's useful for any scenario in a project where you're setting validation results on any model and you don't expect the result to be displayed in a normal CMS edit context. i.e. projects can use this the same way we're using it in DataObject
.
That said, the setters are public so we're not locking people out of that, just making the functionality a little harder to discover - so I won't block merging if that isn't done.
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 Form has no awareness of a DataObject
Form has a $record
property which gets populated when $form->loadDataFrom()
is called.
MemberAuthenticator and PasswordValidator do deal with Member DataObjects, though I don't we shouldn't be displaying any values for those in a CLI context
I assume you meant "I think we shouldn't" - in which case I agree, but none of this functionality deals with values. It deals with model classes and IDs.
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.
Oh yeah you're right of course Form is sometimes aware of DataObject via loadDataFrom().
Still that's not the bit of Form that actually triggers a ValidationException, that happens in FormRequestHandler::httpSubmission()
In that context we could possibly (haven't tested) access the DataObject via $this->form->getRecord(), however however I don't think it makes sense to here as the relevant code will extract the ValidationResult out of the ValidationException, and use that to make a response
Re constructor without arguments, I'd like to keep that as is in this PR.
I don't think there's any further changes required here
b6220e8
to
6123954
Compare
src/Forms/Form.php
Outdated
* @param $message the text of the message | ||
* @param $type Should be set to good, bad, or warning. | ||
* @param $cast Cast type; One of the CAST_ constant definitions. |
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.
Need the types as per #11562 (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.
Updated
src/Forms/Form.php
Outdated
* @param $message the text of the message | ||
* @param $type Should be set to good, bad, or warning. | ||
* @param $cast Cast type; One of the CAST_ constant definitions. |
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.
Need the types as per #11562 (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.
Updated
if ($this->doShowAdditionalInfo()) { | ||
if ($message['fieldName']) { | ||
$exceptionMessage .= ' - fieldName: ' . $message['fieldName']; | ||
} | ||
$dataClass = $result->getDataClass(); | ||
if (is_subclass_of($dataClass, DataObject::class, true)) { | ||
$exceptionMessage .= ', recordID: ' . $result->getRecordID(); | ||
$exceptionMessage .= ', dataClass: ' . $dataClass; | ||
} | ||
} |
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.
What I mean is, why do we need doShowAdditionalInfo()
at all?
This discussion can ultimately be handled in #11562 (comment)
// e.g. dev/build | ||
if (is_a(Controller::curr(), DevelopmentAdmin::class)) { | ||
return 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.
In a UX context (at least in the CMS) the exception message isn't used, the validation result messages are accessed directly, so the additional context does not display in the edit form.
I think this condition of "where are we? Should we add the info?" isn't needed - it should just be always added to the exception message.
src/ORM/DataObject.php
Outdated
@@ -1257,7 +1257,7 @@ public function forceChange() | |||
*/ | |||
public function validate(): ValidationResult | |||
{ | |||
$result = ValidationResult::create(); | |||
$result = ValidationResult::create(get_class($this), $this->ID); |
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 updated the PR to remove the constructor arguments as that functionality is basically only for DataObjects
I think the additional context should still be settable in the constructor. It's useful for any scenario in a project where you're setting validation results on any model and you don't expect the result to be displayed in a normal CMS edit context. i.e. projects can use this the same way we're using it in DataObject
.
That said, the setters are public so we're not locking people out of that, just making the functionality a little harder to discover - so I won't block merging if that isn't done.
src/ORM/DataObject.php
Outdated
@@ -1257,7 +1257,7 @@ public function forceChange() | |||
*/ | |||
public function validate(): ValidationResult | |||
{ | |||
$result = ValidationResult::create(); | |||
$result = ValidationResult::create(get_class($this), $this->ID); |
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 Form has no awareness of a DataObject
Form has a $record
property which gets populated when $form->loadDataFrom()
is called.
MemberAuthenticator and PasswordValidator do deal with Member DataObjects, though I don't we shouldn't be displaying any values for those in a CLI context
I assume you meant "I think we shouldn't" - in which case I agree, but none of this functionality deals with values. It deals with model classes and IDs.
$result = ValidationResult::create(); | ||
$result->setModelClass(static::class); | ||
$result->setRecordID($this->ID); |
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.
Since these aren't available through the constructor anymore, calling two methods to set a single model feels a bit off. Maybe there should be a $result->setModel($this);
?
Don't have to implement this, just a thought.
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 it makes sense to keep separate as you may want to set a non-numeric recordID that doesn't relate to a DataObject/model. I don't want to add what is essentially a duplicate method
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 understand how having an ID without knowing what class that ID belongs to would ever be helpful, but probably not worth discussing since the methods are there to set the stuff.
6123954
to
a0bbb38
Compare
Issue #11423
Also added in a bunch of strong typing while was doing it, removed nullable params and instead use blank string to denote "nothing", and changing a string|bool param param to just string