Blog

Erstellung eines eigenen ConstraintValidatior mit Doctrine Anbindung

Derzeitig nutze ich Symfony 2.1 zur Realisierung meiner Projekte und nun bin ich bei der Realisierung eines Formulars auf ein Problem gestoßen.

Ich erstelle in der Regel ein AbstractType mit den Feldern des Formulars und ergänze die einzelnen Felder mit Constraints um die Eingaben serverseitig zu prüfen. Nun möchte ich jedoch bei einem Formular auch überprüfen, ob z.B. ein Benutzer in der Datenbank existiert. Man könnte die Prüfung im Controller wie folgt durchführen:

if ($form->isValid() === true) {
    $user = $this->getDoctrine()
        ->getRepository('TestSomeBundle:User')
        ->findByUsername($user->getUsername());

    if ($user === null)
        //Raise error in form
    else
        //Login user or reset password
}

Eine wirklich saubere Lösung ist dies jedoch nicht. Eigentlich möchte ich nur $form->isValid() aufrufen und somit die Überprüfung der Eingabe und die logische Prüfung durchführen. Meiner Meinung nach wäre die sauberste Lösung die Überprüfung auf die Existenz des Benutzer innerhalb eines selbst-definierten Constraints.

use Symfony\Component\Validator\Constraint;

/**
* @Annotation
*/
class UserExist extends Constraint {

    public $message = 'This user doesn\'t exist';

}

Nachdem ich mir nun nach der Anleitung aus der Symfony Dokumentation meinen eigenen Constraint “UserExist” erstellt habe, komme ich nun auf mein zu Beginn erwähntes Problem. Leider habe ich innerhalb des Constraints keine Möglichkeit auf den Entity-Manager von Doctrine zuzugreifen. Folglich ist es mir auch nicht möglich die Existenz des Benutzers zu prüfen.

Nach ein bisschen recherche im Internet, hab ich nun eine Möglichkeit gefunden. Jedoch benötigt man ein bisschen Wissen über den Aufbau eines Constraints. Er besteht in der Regel aus zwei Klassen. Die erste Klasse hat Symfony\Component\Validator\Constraint als Oberklasse und ist für die Einstellungen, wie z.B. die Fehlermeldung ($message) zuständig. Die zweite Klasse hat wiederum Symfony\Component\Validator\ConstraintValidator als Oberklasse und führt die eigentlich Prüfung durch.

Die Magie entsteht nun mit der Definition des ConstraintValidator als Service in der services.yml und der Übergabe des Doctrine Entity-Managers. Außerdem wird dieser Service automatisch beim Ausführen des ConstraintValidator initialisiert (Zeile 5).

    validator_userExist:
        class: Test\SomeBundle\Form\Constraint\UserExistValidator
        arguments: [@doctrine.orm.entity_manager]
        tags:
            - { name: validator.constraint_validator, alias: Test\SomeBundle\Form\Constraint\UserExistValidator }

Nachfolgend muss noch der ConstraintValidator erstellt werden. Der Konstruktor bekommt einen Übergabeparameter für den Enity-Manger und innerhalb der Methode validate wird die eigentliche Überprüfung mit der Abfrage des Benutzer in der Datenbank ausgeführt.

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Doctrine\ORM\EntityManager;

class UserExistValidator extends ConstraintValidator {

    private $doctrine;

    public function __construct(EntityManager $entityManager){

        $this->doctrine = $entityManager;
    }

    public function validate($value, Constraint $constraint) {

        $user = $this->doctrine
            ->getRepository('TestSomeBundle:User')
            ->findOneByEmail($value);

        if ($user === null) {
            $this->context->addViolation($constraint->message);
        }
    }
}

Nun ist der selbst erstellte Constraint, bei dem wir DependencyInjection nutzen können, um auf alle Services von Symfony zuzugreifen, fertig. Falls jemand eine besseren Best-Practice kennt, um auf den Entity-Manager im ConstraintValidator zuzugreifen, lasst es mich bitte in den Kommentaren wissen.

No Comments

Leave a Reply

Your email address will not be published. Required fields are marked *