Emilie Tossan
Emilie Tossan

Reputation: 97

How to display form errors in Symfony?

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

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

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

{% block main %}

{{ form_start(userform) }}

    <div class="alert alert-danger text-center" role="alert">

        {% set formErrors = userform.vars.errors.form.getErrors(true) %}

    {% if formErrors|length %}
        <div class="alert alert-danger text-center" role="alert">
            {% if userform.vars.value.email == null or userform.vars.value.email != 'email' or userform.vars.value.email != 'unique' %}
                {{ form_errors(userform.email) }}
            {% elseif userform.vars.value.password|length < 6 %}
                {{ form_errors(userform.password) }}
            {% elseif userform.vars.value.gender == null or (userform.vars.value.gender != 'male' and userform.vars.value.gender != 'female' and userform.vars.value.gender != 'non-binary') %}
                {{ form_errors(userform.gender) }}
            {% elseif userform.vars.value.firstname|length < 2 %}
                {{ form_errors(userform.firstname) }}
            {% elseif userform.vars.value.lastname|length < 2 %}
                {{ form_errors(userform.lastname) }}
            {% elseif userform.vars.value.birthdate == null %}
                {{ form_errors(userform.birthdate) }}
            {% elseif userform.vars.value.occupation|length < 2 %}
                {{ form_errors(userform.occupation) }}
            {% elseif userform.vars.value.nationality == null %}
                {{ form_errors(userform.nationality) }}
            {% elseif userform.vars.value.nativelanguage == null %}
                {{ form_errors(userform.nativelanguage) }}
            {% endif %}
        </div>
    {% endif %}

    </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;

    #[ORM\Column(length: 180, unique: true)]
    #[Assert\NotBlank(message:'Tu as oublié d\'entrer ton adresse e-mail.')]
    #[Assert\Email(message: 'Entre une adresse e-mail valide.')]
    private ?string $email = null;

    /**
     * @var string The hashed password
     */
    #[ORM\Column]
    #[Assert\NotBlank(message:'Tu as oublié de créer un mot de passe.')]
    #[Assert\Length(min: 6, minMessage: 'Crée un mot de passe de 6 caractères minimum.')]
    private ?string $password = null;

    #[ORM\Column(length: 255)]
    #[Assert\NotBlank(message:'Tu as oublié de sélectionner ton genre.')]
    private ?string $gender = null;

    #[ORM\Column(length: 255)]
    #[Assert\NotBlank(message:'Tu as oublié d\'entrer ton prénom.')]
    #[Assert\Length(min: 2, minMessage: 'Écris un prénom valide.')]
    private ?string $firstname = null;

    #[ORM\Column(length: 255)]
    #[Assert\NotBlank(message:'Tu as oublié d\'entrer ton nom de famille.')]
    #[Assert\Length(min: 2, minMessage: 'Écris un nom de famille valide.')]
    private ?string $lastname = null;

    #[ORM\Column(type: Types::DATE_MUTABLE)]
    #[Assert\NotBlank(message:'Tu as oublié de sélectionner ta date de naissance.')]
    private ?\DateTimeInterface $birthdate = null;

    #[ORM\Column(length: 255)]
    #[Assert\NotBlank(message:'Tu as oublié de nous dire ce que tu fais.')]
    #[Assert\Length(min: 2, minMessage: 'Écris une occupation valide.')]
    private ?string $occupation = null;

    #[ORM\ManyToOne(inversedBy: 'users')]
    #[Assert\NotBlank(message:'Tu as oublié de nous sélectionner le pays d\'où tu viens.')]
    private ?Country $nationality = null;

    #[ORM\ManyToOne(inversedBy: 'users')]
    #[Assert\NotBlank(message:'Tu as oublié de nous sélectionner ta langue maternelle.')]
    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 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'
                ]
            ])
            ->add('lastname')
            ->add('firstname')
            ->add('birthdate', BirthdayType::class, [
                'placeholder' => [
                    'year' => 'Année', 'month' => 'Mois', 'day' => 'Jour',
                ],
                'choice_translation_domain' => true
            ])
            ->add('occupation')
            ->add('nationality', EntityType::class, [
                'class' => Country::class,
                'choice_label' => 'name',
                'placeholder' => 'Je choisis un pays'
            ])
            ->add('nativelanguage', EntityType::class, [
                'class' => Language::class,
                'choice_label' => 'name',
                'placeholder' => 'Je sélectionne ma langue maternelle'
            ])
            ->add('email')
            ->add('password', PasswordType::class, [
                'mapped' => false
            ])
            ->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']
            ])
            ->add('save', SubmitType::class, [
                'attr' => ['class' => 'save'],
            ])
        ;
    }

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

-- This question is still open. --

EDIT : I have found the way to display error messages. But this problem persists : password doesn't show its error message. I don't understand why.

Upvotes: 1

Views: 6129

Answers (2)

hous
hous

Reputation: 2679

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;

/**
 * @Assert\NotBlank(
 *     message="Mot de passe ne doit pas être vide."
 * )
 * @Assert\Length(
 *     min="6",
 *     max="32",
 *     minMessage="Mot de passe doit avoir au minimum ({{ limit }}) caractères.",
 *     maxMessage="Mot de passe doit avoir au maximum ({{ limit }}) caractères."
 * )
 * @Assert\Length(max=4096)
 */
private $plainPassword;

public function getPlainPassword(): ?string
{
    return $this->plainPassword;
}

public function setPlainPassword(?string $password): self
{
    $this->plainPassword = $password;

    return $this;
}

FormType:

        use Symfony\Component\Validator\Constraints\Length;
        use Symfony\Component\Validator\Constraints\NotBlank;

        ->add('plainPassword', RepeatedType::class, array(
            'type' => PasswordType::class,
            'first_options'  => array(
                'label' => 'Mot de passe (6 caractères au minimum)',
                'constraints' => [
                    new NotBlank([
                        'message' => 'Mot de passe ne doit pas être vide',
                    ]),
                    new Length([
                        'min' => 6,
                        'minMessage' => 'Mot de passe doit avoir au minimum {{ limit }} caractères',
                        'max' => 4096,
                    ]),
                ],

            ),
            'second_options' => array('label' => 'Confirmation'),
            'invalid_message' => 'Les deux mots de passe ne sont pas identiques'
        ))

The controller:

    use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

    private UserPasswordHasherInterface $passwordHasher;

    public function __construct(UserPasswordHasherInterface $passwordHasher)
{
    $this->passwordHasher = $passwordHasher;
}

    if ($form->isSubmitted() && $form->isValid()) {
        $user->addRole('ROLE_AAAAAA');
        $user->setPassword(
            $this->passwordHasher->hashPassword($user, $form->get('plainPassword')->getData()));

     // ...........
     }

Twig form:

  {% if not userform.vars.valid %}
    <div class="alert alert-danger">
    {{ form_errors(userform) }}
    {% for children in userform.children %}
        {% if not children.vars.valid %}
            {{ form_errors(children) }}
        {% endif %}
        {% for child in children %}
            {% if not child.vars.valid %}
                {{ form_errors(child) }}
            {% endif %}
        {% endfor %}
    {% endfor %}
    </div>
  {% endif %}


        <div class="col-md-6">
            {{ form_row(form.plainPassword.first) }}
        </div>
        <div class="col-md-6">
            {{ form_row(form.plainPassword.second) }}
        </div>

Upvotes: 5

Appheros
Appheros

Reputation: 29

_ 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.

Upvotes: 0

Related Questions