How to use partial validation for a multi-step form

(Sebastian Kurfürst)

This is a pragmatic approach to implementing partial validation in Extbase. It is NOT the final solution to this problem, that's why this will not become part of Extbase at the moment. Instead, the FLOW3 team is working on a more complete and consistent solution.

The Problem

Extbase validates all action arguments completely before calling an action. However, for a multi-step form, it makes sense to only validate those model properties which are changed in the current request.

The pragmatic solution

  • Use the attached ExtendedValidatorResolver.php and place it somewhere in your extension. Make sure to adjust the class name inside the class accordingly.
  • Then, create a subclass of the ActionController from which all the controllers in your extension which need this functionality extend:
Tx_Extbase_MVC_Controller_ActionController
           /\
           ||
Tx_MyExtension_MyBaseController
           /\
           ||
Tx_MyExtension_Controller_Controller*
  • Inside this subclass, create a method registerPartialValidatorForArgument(), as follows:
/**
     * 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_BlogExample_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);
        }
    }

Usage

  • Now, suppose you have a form which consists of two steps:
step 1 --> step 2
  • For each step, create one action for showing the form, and one action for saving the form:
     step 1          step2

showstep1Action  
savestep1Action
      redirect \-- showstep2action
                   savestep2action
  • All show* actions need to get a @dontvalidate annotation, as usual.
  • At the end of a save*action, place a redirect() to the next showAction.
  • Now, for each save*action, create an appropriate initialize*Action. Example: for savestep1Action, create initializeSavestep1Action, and so on.
  • Inside this initialize*Action, call the method registerPartialValidatorForArgument from above, with the name of the argument for which partial validation should occur.

Sample

While reconstructing/research this technique I noticed, that there are somen pending requests in the web, so i decide to write a small example plugin.
You can find the plugin at http://typo3.org/extensions/repository/view/hh_multipageform_example/current/
I hope it helps (Heiko Hardt)

ExtendedValidatorResolver.php (2.5 kB) Sebastian Kurfuerst, 2010-03-16 15:18