Formular-Validierung in Symfony
Formular-Validierung in Symfony
~15 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
Aktuell akzeptiert unser Projekt-Formular JEDEN Wert – auch einen leeren Namen. Symfonys Validator-Komponente löst das über deklarative Constraints, die wir GENAU dort platzieren, wo sie logisch hingehören: an der Datenklasse selbst.
Eine dedizierte Datenklasse statt Array
Bisher gab $form->getData() ein Array zurück – für Validierung brauchen wir eine ECHTE PHP-Klasse mit typisierten Eigenschaften, an die sich Constraints als Attribute anheften lassen:
<?php
declare(strict_types=1);
namespace App\Form\Data;
use Symfony\Component\Validator\Constraints as Assert;
class ProjectData
{
#[Assert\NotBlank(message: 'Bitte geben Sie einen Projektnamen ein.')]
#[Assert\Length(
min: 3,
max: 100,
minMessage: 'Der Projektname muss mindestens {{ limit }} Zeichen lang sein.',
maxMessage: 'Der Projektname darf höchstens {{ limit }} Zeichen lang sein.',
)]
public string $name = '';
#[Assert\Length(max: 1000, maxMessage: 'Die Beschreibung darf höchstens {{ limit }} Zeichen lang sein.')]
public ?string $beschreibung = null;
}Wir platzieren diese Klasse bewusst unter src/Form/Data/ statt src/Entity/ – in Block 4 wird ProjectData durch die echte Project-Entity ersetzt, aber das Validierungsprinzip bleibt IDENTISCH.
Den Form Type mit data_class verbinden
use App\Form\Data\ProjectData;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProjectType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, ['label' => 'Projektname'])
->add('beschreibung', TextareaType::class, ['label' => 'Beschreibung', 'required' => false])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(['data_class' => ProjectData::class]);
}
}data_class teilt dem Formular mit, WELCHE Klasse es befüllen soll – $form->getData() gibt jetzt ein ProjectData-OBJEKT zurück statt eines Arrays, UND isValid() prüft automatisch ALLE Constraints, die an dieser Klasse definiert sind.
Häufige Constraints im Überblick
| Constraint | Prüft |
|---|---|
| #[Assert\NotBlank] | Darf nicht leer sein. |
| #[Assert\Length(min: ..., max: ...)] | Mindest-/Maximallänge für Strings. |
| #[Assert\Email] | Muss eine gültige E-Mail-Adresse sein (Kapitel 28 nutzt das für die Registrierung). |
| #[Assert\Range(min: ..., max: ...)] | Numerischer Wert innerhalb eines Bereichs. |
| #[Assert\Choice(choices: [...])] | Wert muss aus einer festen Liste stammen (Kapitel 17 nutzt das für den Task-Status). |
| #[Assert\GreaterThan('today')] | Für Datumswerte – nützlich für das Fälligkeitsdatum unserer Task-Entity. |
Validierungsfehler werden automatisch angezeigt
Da {{ form(form) }} (Kapitel 15) Fehler bereits automatisch mit rendert, brauchen wir am Template NICHTS zu ändern – schlägt die Validierung fehl, erscheint die konfigurierte message AUTOMATISCH unter dem betroffenen Feld.
Eigene Validierungslogik: Callback-Constraint
Für Regeln, die sich nicht mit einem eingebauten Constraint abdecken lassen (z. B. "Fälligkeitsdatum darf nicht auf ein Wochenende fallen"), gibt es #[Assert\Callback]:
use Symfony\Component\Validator\Context\ExecutionContextInterface;
#[Assert\Callback]
public function validiere(ExecutionContextInterface $context): void
{
if (str_contains($this->name, 'TODO')) {
$context->buildViolation('Der Projektname darf nicht "TODO" enthalten.')
->atPath('name')
->addViolation()
;
}
}Tipp: Faustregel: eingebaute Constraints (NotBlank, Length, ...) IMMER bevorzugen, wenn sie die Regel abdecken – nur für WIRKLICH projektspezifische Logik, die sich nicht als einzelnes Feld isolieren lässt, zu #[Assert\Callback] greifen.