[TYPO3-mvc] Validating nested objects on create
Franz Koch
typo3.RemoveForMessage at elements-net.de
Wed Dec 8 13:59:47 CET 2010
Hey Claus,
> Am 13.11.2010 10:28, schrieb Franz Koch:
>> I'd say custom validators combined with custom viewHelpers to display
>> the nested errors (see my comments on the ticket). With those I managed
>> to validate 1:1 relations for now, but 1:n or n:m relations
>> (objectStorages) should also work, although they are more tricky to
>> display correctly (bound to the related object while it doesn't have a
>> identifier yet).
>
> I run into the same problem and the part with the custom validator is
> absolute clear, but how do you realized the custom viewHelper to display
> the error message ?
hehe, yes, that was a bit tricky. I mainly use two viewHelpers, one for
resolving validation related information (like errors for a certain
property and mapping the error-code to related css-classes I need for my
inline validation) and one to translate the error message, checking
various translation combinations (first if there's a specific error code
translate for a given property, if that's missing it's using a default
message for that code etc).
In my validationResolver viewHelper I manly fetch the errors for the
form itself (like it's done in the default error viewHelper) and then
iterate over them, trying to find the one matching the given
propertyPath. To be able to deal with objectStorages, I had to group all
errors related to one childObject in it's own array and assigned it
using a special key (in my case 'obj:'.$index as identifier) to the
errors of the parent property.
I think I'll better show you some code then trying to explain everything :)
---- part of my generic object validator ----------
public function isValid($value) {
$dataType = $this->options['dataType'];
// no dependencyInjection? Create the object by hand
if (!is_object($this->validatorResolver)) {
$this->validatorResolver =
t3lib_div::makeInstance('Tx_Extbase_Validation_ValidatorResolver');
$this->validatorResolver->injectObjectManager(t3lib_div::makeInstance('Tx_Extbase_Object_Manager'));
$this->validatorResolver->injectReflectionService(t3lib_div::makeInstance('Tx_Extbase_Reflection_Service'));
}
$conjunctions =
$this->validatorResolver->getBaseValidatorConjunction($dataType);
if ($value instanceof Tx_Extbase_Persistence_ObjectStorage || $value
instanceof SplObjectStorage || $value instanceof Traversable) {
$storageErrors = array();
$isValid = TRUE;
$i = 0;
foreach($value as $childObject) {
$isChildValid = $conjunctions->isValid($childObject);
if (!$isChildValid) {
$isValid = FALSE;
$storageErrors['storage']['obj:'.$i] = $conjunctions->getErrors();
}
$i++;
}
if (count($storageErrors)) {
$this->errors = array_merge_recursive($this->errors, $storageErrors);
}
} else {
$isValid = $conjunctions->isValid($value);
$this->errors = array_merge($this->errors, $conjunctions->getErrors());
}
return $isValid;
}
---------------------------------------------------
----- stuff of my validationResolver vH -----------
public function render($propertyPath) {
if
($this->viewHelperVariableContainer->exists('Tx_Fluid_ViewHelpers_FormViewHelper',
'formObjectName')) {
$formName =
$this->viewHelperVariableContainer->get('Tx_Fluid_ViewHelpers_FormViewHelper',
'formObjectName');
} else {
throw new Exception('The validationHandlerViewHelper can only be used
inside a formViewHelper with proper \'name\' attribute', 1279380442);
}
# resolve the form errors
$allErrors = $this->controllerContext->getRequest()->getErrors();
$formErrors = $allErrors[$formName];
if (!is_object($formErrors)) {
$formErrors = array();
} else {
$formErrors = $formErrors->getErrors();
}
$this->view->assign('errors',
$this->getErrorsForProperty($propertyPath, $formErrors));
}
private function getErrorsForProperty($propertyPath, $errors) {
$pathSegments = explode('.', $propertyPath);
foreach($pathSegments as $property) {
foreach ($errors as $error) {
if ($error instanceof Tx_Extbase_Validation_PropertyError) {
if ($error->getPropertyName() === $property) {
$errors = $error->getErrors();
if ($property == end($propertyPath)) {
return $errors;
}
break;
}
} else if (is_array($error)) {
if (isset($error['obj:'.$property])) {
$errors = $error['obj:'.$property];
} else if(isset($error[$property])) {
$errors = $error[$property];
}
}
}
}
return array();
}
---------------------------------------------------
The only important thing you have to take care of in order to have this
work is, that in your templates you assign nummeric keys for
objectStorages, starting with 0. So something like this:
<f:form.textbox property="yourObjectStorageProperty.0.someProperty"
value="..." />
You could of course also use the "name" attribute and use
name="nameOfForm[yourObjectStorageProperty][0][someProperty]"
Hope that helps, don't know if it's the best solution, but it's working
for me.
--
kind regards,
Franz Koch
More information about the TYPO3-project-typo3v4mvc
mailing list