[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