Home > Blockchain >  How to display form errors in Symfony?
How to display form errors in Symfony?

Time:10-14

I wish to understand how to display errors that come from forms. For now, when I validate any form on my project, I don't have anything appearing.

How to display form errors in Symfony ?

Thank you for your help.

You can see my code from :

  • register.html.twig file where my userform helping users to register is shown.
  • UserController.php file where you can see what happens when userform is validated.
  • Also User.php and UserType.php.

register.html.twig

{% extends 'base.html.twig' %}

{% block title %}Incris-toi !{% endblock %}

{% block main %}

{{ form_start(userform) }}

    <div  role="alert">

        {{ form_errors(userform.email) }}
        {{ form_errors(userform.password) }}
        {{ form_errors(userform.gender) }}
        {{ form_errors(userform.firstname) }}
        {{ form_errors(userform.lastname) }}
        {{ form_errors(userform.birthdate) }}
        {{ form_errors(userform.occupation) }}
        {{ form_errors(userform.nationality) }}
        {{ form_errors(userform.nativelanguage) }}

    </div>
    
    {{ form_widget(userform.email) }}
    {{ form_widget(userform.password) }}
    {{ form_widget(userform.gender) }}
    {{ form_widget(userform.firstname) }}
    {{ form_widget(userform.lastname) }}
    {{ form_widget(userform.birthdate) }}
    {{ form_widget(userform.occupation) }}
    {{ form_widget(userform.nationality) }}
    {{ form_widget(userform.nativelanguage) }}
    {{ form_widget(userform.save) }}

{{ form_end(userform) }}

UserController.php

<?php

namespace App\Controller\Front;

use App\Entity\User;
use App\Form\UserType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

class UserController extends AbstractController
{
    #[Route('/register', name: 'register', methods: ['GET', 'POST'])]
    public function createUser(
        Request $request,
        EntityManagerInterface $entityManagerInterface,
        UserPasswordHasherInterface $userPasswordHasherInterface
    ){
        $user = new User();
        $userform = $this->createForm(UserType::class, $user);
        $userform->handleRequest($request);

        if ($userform->isSubmitted() && $userform->isValid()) {

            $user->setRoles(["ROLE_USER"]);

            $plainPassword = $userform->get('password')->getData();
            $hashedPassword = $userPasswordHasherInterface->hashPassword($user, $plainPassword);
            $user->setPassword($hashedPassword);

            $entityManagerInterface->persist($user);
            $entityManagerInterface->flush();

            return $this->redirectToRoute('home');
        }

        return $this->renderForm('front/register.html.twig', [
            'userform' => $userform,
        ]);
    }

User.php

<?php

namespace App\Entity;

use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;

#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 180, unique: true)]
    private ?string $email = null;

    #[ORM\Column]
    private array $roles = [];

    /**
     * @var string The hashed password
     */
    #[ORM\Column]
    private ?string $password = null;

    #[ORM\Column(length: 255)]
    private ?string $gender = null;

    #[ORM\Column(length: 255)]
    private ?string $firstname = null;

    #[ORM\Column(length: 255)]
    private ?string $lastname = null;

    #[ORM\Column(type: Types::DATE_MUTABLE)]
    private ?\DateTimeInterface $birthdate = null;

    #[ORM\Column(length: 255)]
    private ?string $occupation = null;

    #[ORM\ManyToOne(inversedBy: 'users')]
    private ?Country $nationality = null;

    #[ORM\ManyToOne(inversedBy: 'users')]
    private ?Language $nativelanguage = null;

    public function __construct()
    {
        $this->events = new ArrayCollection();
        $this->participations = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }

    /**
     * A visual identifier that represents this user.
     *
     * @see UserInterface
     */
    public function getUserIdentifier(): string
    {
        return (string) $this->email;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(array $roles): self
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * @see PasswordAuthenticatedUserInterface
     */
    public function getPassword(): string
    {
        return $this->password;
    }

    public function setPassword(string $password): self
    {
        $this->password = $password;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials()
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }

    public function getGender(): ?string
    {
        return $this->gender;
    }

    public function setGender(string $gender): self
    {
        $this->gender = $gender;

        return $this;
    }

    public function getFirstname(): ?string
    {
        return $this->firstname;
    }

    public function setFirstname(string $firstname): self
    {
        $this->firstname = $firstname;

        return $this;
    }

    public function getLastname(): ?string
    {
        return $this->lastname;
    }

    public function setLastname(string $lastname): self
    {
        $this->lastname = $lastname;

        return $this;
    }

    public function getBirthdate(): ?\DateTimeInterface
    {
        return $this->birthdate;
    }

    public function setBirthdate(\DateTimeInterface $birthdate): self
    {
        $this->birthdate = $birthdate;

        return $this;
    }

    public function getOccupation(): ?string
    {
        return $this->occupation;
    }

    public function setOccupation(string $occupation): self
    {
        $this->occupation = $occupation;

        return $this;
    }

    public function getNationality(): ?Country
    {
        return $this->nationality;
    }

    public function setNationality(?Country $nationality): self
    {
        $this->nationality = $nationality;

        return $this;
    }

    public function getNativelanguage(): ?Language
    {
        return $this->nativelanguage;
    }

    public function setNativelanguage(?Language $nativelanguage): self
    {
        $this->nativelanguage = $nativelanguage;

        return $this;
    }
}

UserType.php

<?php

namespace App\Form;

use App\Entity\User;
use App\Entity\Country;
use App\Entity\Language;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('gender', ChoiceType::class, [
                'choices' => [
                    'Je suis ...' => '',
                    'un homme' => 'male',
                    'une femme' =>'female',
                    'non-binaire' => 'non-binary',
                    'error_bubbling' => true,
                ]
            ])
            ->add('lastname', TextType::class, [
                'error_bubbling' => true
            ])
            ->add('firstname', TextType::class, [
                'error_bubbling' => true
            ])
            ->add('birthdate', BirthdayType::class, [
                'placeholder' => [
                    'year' => 'Année', 'month' => 'Mois', 'day' => 'Jour',
                ],
                'choice_translation_domain' => true,
                'error_bubbling' => true,
            ])
            ->add('occupation', TextType::class, [
                'error_bubbling' => true
            ])
            ->add('nationality', EntityType::class, [
                'class' => Country::class,
                'choice_label' => 'name',
                'placeholder' => 'Je choisis un pays',
                'error_bubbling' => true
            ])
            ->add('nativelanguage', EntityType::class, [
                'class' => Language::class,
                'choice_label' => 'name',
                'placeholder' => 'Je sélectionne ma langue maternelle',
                'error_bubbling' => true
            ])
            ->add('email', TextType::class, [
                'error_bubbling' => true
            ])
            ->add('password', PasswordType::class, [
                'mapped' => false,
                'error_bubbling' => true
            ])
            ->add('password', RepeatedType::class, [
                'type' => PasswordType::class,
                'invalid_message' => 'Les deux mots de passe doivent être identiques.',
                'options' => ['attr' => ['class' => 'password-field']],
                'required' => true,
                'first_options'  => ['label' => 'Password'],
                'second_options' => ['label' => 'Repeat Password'],
                'error_bubbling' => true
            ])
            ->add('save', SubmitType::class, [
                'attr' => ['class' => 'save'],
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => User::class,
            'translation_domain' => 'forms'
        ]);
    }
}

CodePudding user response:

Try to test "firstname" for example with min length 3 by typing only 2 chars in the form, the form error appears or no ?

use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Column(length: 255)]
#[Assert\NotBlank]
#[Assert\Length(min: 3)]
private ?string $firstname = null;

CodePudding user response:

_ I think there is a problem with your User class, you didn't set any validations for your form : https://symfony.com/doc/current/forms.html#validating-forms

Validation is done by adding a set of rules, called (validation) constraints, to a class. You can add them either to the entity class or to the form class.

Try to add some rules (#[Assert\NotBlank]) to your class and your form will throw errors if it's not fit.

_ Also, you need te remove your default value on your fields in your User class ( = null) to the variable you need to be filled (at least you need to remove the ID one, who can't be null).

_ In your form on each fields, you set 'error_bubbling' to true : https://symfony.com/doc/current/reference/forms/types/text.html#error-bubbling

If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

If you want to have the control on every field, i think you need to remove this option, then symfony will attach errors on each fields. Or you can let this option but then you need to update your code in the Alert div, to not render each fields, but the parent field or form.

  • Related