Dynamische Validierung mit Extbase

In einem unserer Projekte haben wir 2 Anwendungsfälle, die man bis auf eine Kleinigkeit mit einer Extension abbilden könnte. Bei dieser Kleinigkeit handelt es sich um eine E-Mail-Adresse, die bei dem einen Anwendungsfall ein Pflichtfeld ist und in dem anderen Fall nicht zwingend angegeben werden muss.

Extbase ist Dank dem MVC-Prinzip sehr konsistent. Wenn also in einem Model bestimmt wird, dass die E-Mail-Adresse kein Pflichtfeld ist, dann kann man zur Laufzeit nicht einfach sagen, dass die E-Mail-Adresse auf einmal ein Pflichtfeld wird.

Warum das nicht geht schauen wir uns hier im Quellcode an:

public function createAction(Tx_ExtName_Domain_Model_Topic $newTopic)

Es scheitert an der createAction(). Denn hier wird bestimmt, dass alle Formulareingaben in das Model Tx_ExtName_Domain_Model_Topic zu übertragen sind. Wir befinden uns hier aber innerhalb der Parameterliste eines Methodenaufrufs und haben somit überhaupt keine Möglichkeit z.B. ein anderes Model mit einer anderen Validierung auszuwählen, da PHP-Methoden wie if und switch hier nicht erlaubt sind.

Auch die Möglichkeit die E-Mail-Adresse innerhalb der createAction nachträglich zu überprüfen scheitert, da Fluid nun nicht mehr herausfinden kann, ob diese Eigenschaft einen Fehler verursacht hat oder nicht. Das äußert sich dahingehend, dass dem E-Mail-Feld im Fehlerfalle die CSS-Fehlerklasse "f3-form-error" nicht mehr hinzugefügt werden kann. Der Webseitenbesucher erhält also überhaupt kein visuelles Feedback mehr.

Mit Hilfe einer zusätzlichen initializeCreateAction-Methode ist es jedoch möglich, noch VOR dem eigentlichen Aufruf der createAction-Methode einzugreifen. Aber auch hier gibt es Probleme. Zwar ist der ConjunctionValidator grundsätzlich dafür gedacht auch mehrere Validatoren aufzunehmen, aber selbst nach mehreren Stunden ist es nicht gelungen mit addValidator einen eigenen Validator hinzuzufügen, der auch funktioniert. Extbase und Fluid erwarten hier anscheinend eine fest vorgegebene Struktur/Verschachtelung der Validatoren.

Die Lösung

Es müssen 2 Modelle erstellt werden. Ein Model mit Pflichtfeld und ein Model ohne Pflichtfeld. In die createAction kommt, wie im Codeschnipsel oben, das Model ohne Pflichtfeld rein. Das Geheimnis liegt in der initializeCreateAction:

/**
 * add new validator to topic
 *
 * @return void
 */
public function initializeCreateAction() {
  if ($this->arguments->hasArgument('newTopic')) {
    if ($this->settings['emailIsMandatory']) {
      /** @var Tx_Extbase_Validation_ValidatorResolver $validatorResolver */
      $validatorResolver = $this->objectManager->get('Tx_Extbase_Validation_ValidatorResolver');
      $topicValidator = $validatorResolver->getBaseValidatorConjunction('Tx_ExtName_Domain_Model_Wme_Topic');

      /** @var Tx_Extbase_Validation_Validator_ConjunctionValidator $conjunctionValidator */
      $conjunctionValidator = $this->arguments->getArgument('newTopic')->getValidator();

      /* remove default validator */
      foreach ($conjunctionValidator->getValidators() as $validator) {
        $conjunctionValidator->removeValidator($validator);
      }

      /* add our own validator with mandatory email to topic */
      $conjunctionValidator->addValidator($topicValidator);
    }
  }
}

In der initializeCreateAction-Methode wird nun überprüft, ob die nachfolgende Methode (createAction) das Argument $newTopic beinhaltet und ob über TypoScript die E-Mail-Adresse als Pflichtfeld gesetzt wurde.

Wenn dem so ist, holen wir uns das ValidatorResolver-Objekt. Mit Hilfe der enthaltenen getBaseValidatorConjunction-Methode, können wir nun einen neuen Validator für das Domainmodel, bei dem nun die E-Mail-Adresse ein Pflichtfeld ist, erstellen lassen (WME = With Mandatory Email).

Wir holen uns nun die von Extbase erstellten Validatoren, löschen alle und fügen unseren selbst erstellten Validator dem Argument für die createAction wieder hinzu.

Autor: Stefan Frömken

Aktualisiert: 29.06.2016