Zum Inhalt springen

Partielle Formular Validierung (multistep)

Erstellt am 6. Oktober 2011 · 1 Antwort · letzte Antwort am 25. November 2011

Tags: Frage

Typo3Cuckoo ·

Hi,

ich bin gerade dabei ein mehrstufiges E-Mailformular zu erstellen, dessen Eingabefelder den Eigenschaften (Properties) eines Domain Models entsprechen.

Ich habe an folgende Anleitung gehalten:
http://forge.typo3.org/projects/typo3v4-mvc/wiki/How_to_use_partial_validation_for_a_multi-step_form
sowie
http://lists.typo3.org/pipermail/typo3-project-typo3v4mvc/2011-May/009643.html

Wenn ich das Formular in Schritt 2 mit leeren Eingabefeldern abschicke, dann erhalte ich FormErrors für alle Felder, also auch diejenigen, welche erst in Schritt 2 dran sind. Irgendwie greift diese partielle Valiedierung nicht 🙁

Hat das schon mal einer erfolgreich umgesetzt und könnte mir helfen?

Das Domain Model MyDomainModel sieht gekürzt so aus (hier stehen auch alle Validierungsregeln mittels Annotation @validate):

<?php
class Tx_MyExt_Domain_Model_MyDomainModel extends Tx_Extbase_DomainObject_AbstractEntity {

	/**
	 * firstName
	 *
	 * @var string
	 * @validate notEmpty
	 */
	protected $firstName;

	/**
	 * __construct
	 * 
	 * @param array $data
	 */
	public function __construct() {
	}
	
	/**
	 * Returns the first name
	 *
	 * @return string $firstName
	 */
	public function getFirstName() {
		return $this->firstName;
	}
	
	/**
	 * Sets the first name
	 *
	 * @param string $firstName
	 * @return void
	 */
	public function setFirstName($firstName) {
		$this->firstName = $firstName;
	}
	
}
?>

Die ExtendedValidatorResolver Klasse (siehe Download ersten Link) liegt im Verzeichnis ext_key/Classes/Valdation/ExtendedValidatorResolver.php und sieht so aus:

<?php
class Tx_MyExt_Validation_ExtendedValidatorResolver extends Tx_Extbase_Validation_ValidatorResolver {
	/**
	 * Builds a base validator conjunction for the given data type.
	 *
	 * The base validation rules are those which were declared directly in a class (typically
	 * a model) through some @validate annotations on properties.
	 * It will only validate the properties which are sent with the current request.
	 *
	 *
	 * @param string $dataType The data type to build the validation conjunction for. Needs to be the fully qualified object name.
	 * @return Tx_Extbase_Validation_Validator_ConjunctionValidator The validator conjunction or NULL
	 */
	public function buildBaseValidatorConjunctionWithRequestData($dataType, $rawRequest) {
		
		// Model based validator
		// This method looks almost exactly like "buildBaseValidatorConjunction" in ValidatorResolver.
		if (strstr($dataType, '_') !== FALSE && class_exists($dataType)) {
			$objectValidator = $this->createValidator('GenericObject');

			$validatorCount = 0;

			foreach ($this->reflectionService->getClassPropertyNames($dataType) as $classPropertyName) {
				$classPropertyTagsValues = $this->reflectionService->getPropertyTagsValues($dataType, $classPropertyName);
				if (!isset($classPropertyTagsValues['validate'])) continue;
				// This is the only real change to the ValidatorResolver: We only add the
				// validation for a property if this property has been changed in the current request.
				if (!is_array($rawRequest) || !isset($rawRequest[$classPropertyName])) continue;

				foreach ($classPropertyTagsValues['validate'] as $validateValue) {
					$parsedAnnotation = $this->parseValidatorAnnotation($validateValue);
					foreach ($parsedAnnotation['validators'] as $validatorConfiguration) {
						$newValidator = $this->createValidator($validatorConfiguration['validatorName'], $validatorConfiguration['validatorOptions']);
						if ($newValidator === NULL) {
							throw new Tx_Extbase_Validation_Exception_NoSuchValidator('Invalid validate annotation in ' . $dataType . '::' . $classPropertyName . ': Could not resolve class name for  validator "' . $validatorConfiguration['validatorName'] . '".', 1241098027);
						}
						$objectValidator->addPropertyValidator($classPropertyName, $newValidator);
						$validatorCount ++;
					}
				}
			}
			if ($validatorCount > 0) return $objectValidator;
		}
		return $this->createValidator('Conjunction'); // Just a validator returning TRUE; in case no validation needs to take place.
		
	}
}
?>

Davon abgeleitet die Subclass (siehe ersten Link), welche dann vom eigentlichen Controller geerbt wird:

<?php
class Tx_MyExt_Controller_MultiStepFormController extends Tx_Extbase_MVC_Controller_ActionController {

	/**
	 * If you need a multi-step form which updates a domain object, you only want properties validated which are
	 * sent with the current request. This method should be called in the corresponding initialize*Action,
	 * and it will rebuild the registered validators for this argument.
	 *
	 * It will also respect the @validate annotation on the action method name.
	 *
	 * THIS METHOD WILL NOT CHECK A @dontvalidate ANNOTATION. Thus, it should NOT be used
	 * for displaying a form, but instead be used for SAVING data.
	 *
	 * @param string $argumentName The name of the argument where the partial validator should be registered for.
	 */
	protected function registerPartialValidatorForArgument($argumentName) {
		
		if ($this->request->hasArgument($argumentName)) {
			
			// Initialize the extended validator resolver.
			$extendedValidatorResolver = t3lib_div::makeInstance('Tx_MyExt_Validation_ExtendedValidatorResolver');
			$extendedValidatorResolver->injectObjectManager(t3lib_div::makeInstance('Tx_Extbase_Object_Manager')); // Singleton
			$extendedValidatorResolver->injectReflectionService(t3lib_div::makeInstance('Tx_Extbase_Reflection_Service')); // Singleton
			
			// Load all parameter validators and pick the one for the current argument, as this is the base validator.
			$parameterValidators = $this->validatorResolver->buildMethodArgumentsValidatorConjunctions(get_class($this), $this->actionMethodName);
			$baseValidator = $parameterValidators[$argumentName];
			
			// Build up the validator for all submitted data.
			$rawRequestDataForArgument = $this->request->getArgument($argumentName);
			$argument = $this->arguments[$argumentName];
			$partialValidator = $extendedValidatorResolver->buildBaseValidatorConjunctionWithRequestData($argument->getDataType(), $rawRequestDataForArgument);
			
			// Add the partial validator to the base validator; and override the validations of the argument.
			$baseValidator->addValidator($partialValidator);
			$argument->setValidator($baseValidator);
		}
	}
	
}
?>

Und hier der Crontoller zum Domain Model MyDomainModel von oben:

<?php
class Tx_MyExt_Controller_MyDomainModelController extends Tx_MyExt_Controller_MultiStepFormController {

	/**
	 * Show Step 1
	 * 
	 * @param Tx_MyExt_Domain_Model_MyDomainModel $myDomainModel
	 * @dontvalidate $myDomainModel
	 * 
	 * @return string A HTML Form
	 */
	public function showStep1Action(Tx_MyExt_Domain_Model_MyDomainModel $myDomainModel=NULL) {
		$this->view->assign('myDomainModel', $myDomainModel);
	}
	
	/**
	 * Save Step 1
	 * 
	 * @param Tx_MyExt_Domain_Model_MyDomainModel $myDomainModel
	 * 
	 * @return string A HTML Form
	 */
	public function saveStep1Action(Tx_MyExt_Domain_Model_MyDomainModel $myDomainModel) {
		$this->redirect('showStep2');
	}
	
	/**
	 * Initialize Save Step 1
	 * 
	 * @return void
	 */
	public function initializeSaveStep1Action() {
		$this->registerPartialValidatorForArgument('firstName');
		$this->registerPartialValidatorForArgument('lastName');
		$this->registerPartialValidatorForArgument('birthdayDay');
		$this->registerPartialValidatorForArgument('birthdayMonth');
		$this->registerPartialValidatorForArgument('birthdayYear');
		$this->registerPartialValidatorForArgument('company');
		$this->registerPartialValidatorForArgument('street');
		$this->registerPartialValidatorForArgument('postalCode');
		$this->registerPartialValidatorForArgument('city');
		$this->registerPartialValidatorForArgument('email');
	}
	
	/**
	 * Show Step 2
	 * 
	 * @param Tx_MyExt_Domain_Model_MyDomainModel $myDomainModel
	 * @dontvalidate $myDomainModel
	 * 
	 * @return string A HTML Form
	 */
	public function showStep2Action(Tx_MyExt_Domain_Model_MyDomainModel $myDomainModel) {
		$this->view->assign('myDomainModel', $myDomainModel);
	}

	/**
	 * Save Step 2
	 * 
	 * @param Tx_MyExt_Domain_Model_MyDomainModel $myDomainModel
	 * 
	 * @return string A HTML Form
	 */
	public function saveStep2Action(Tx_MyExt_Domain_Model_MyDomainModel $myDomainModel) {
		$this->redirect('submit');
	}
	
	/**
	 * Initialize Save Step 2
	 * 
	 * @return void
	 */
	public function initializeSaveStep2Action() {
		$this->registerPartialValidatorForArgument('agb');
	}

	/**
	 * Show Submit View
	 * 
	 * @param Tx_MyExt_Domain_Model_MyDomainModel $myDomainModel
	 * @dontvalidate $myDomainModel
	 * 
	 * @return string A HTML Form
	 */
	public function submitAction(Tx_MyExt_Domain_Model_MyDomainModel $myDomainModel) {
		$this->view->assign('myDomainModel', $myDomainModel);
	}
}
?>

Schaut man sich die initializeSaveStep*Action Methoden an, dann sieht man, dass das Feld agb erst in Schritt 2 validiert werden soll, wie jedoch oben beschrieben erhalte ich schon in Schritt 1 die Validierungsfehlermeldungen für alle im Domain Model mit der Annotation @validate versehenen Eigenschaften:

firstName: The given subject was empty.
lastName: The given subject was empty.
birthdayDay: The given subject was empty.
birthdayMonth: The given subject was empty.
birthdayYear: The given subject was empty.
company: The given subject was empty.
street: The given subject was empty.
postalCode: The given subject was empty.
city: The given subject was empty.
email: The given subject was empty.
agb: The given subject was NULL.  <--- Das dürfte erst in Schritt 2 validiert werden!

Die Fluid Templates erspar ich euch jetzt mal (falls ihr sie braucht/wollt werd ich die nachreichen).

Ich hoffe ihr könnt mir weiterhelfen 🙂

vizArt ·

Hi Typo3Cuckoo,

da ich das gleiche Problem hatte, und irgendwann meinen "Denkfehler" gefunden hatte, hab ich ein kleines Beispiel-Plugin geschrieben. Dieses habe ich dem Artikel http://forge.typo3.org/projects/typo3v4-mvc/wiki/How_to_use_partial_validation_for_a_multi-step_form ergänzt.

Prinzipiell liegt aber unser Fehler darin, dass wir dachten es müsse die Property in die registerPartialValidatorForArgument() - Methode übergeben werden.
Dies ist jedoch falsch. Du übergibst sozusagen das "model-object".

In Deinem Besipiel wäre das:

<?php
/**
 * Initialize Save Step 1
 * 
 * @return void
 */
 public function initializeSaveStep1Action() {
    $this->registerPartialValidatorForArgument($myDomainModel);
  }
?>

Die Technik von Sebastian erkennt selbst welche Properties übergeben wurden, und wendet darauf dann die validierung an.

Ich hoffe ich konnte Dir helfen
LG viz