1

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

16
  • I added UserController.php so that you can see what happens with validation. Also, I wish to display only one error by the time, this is why I wanted to separate each error. Commented Oct 14, 2022 at 12:19
  • Thank you Marleen. I added User and UserType files. Commented Oct 14, 2022 at 12:49
  • "How to display form errors in Symfony ?" - what exactly is not working? What have you tried to resolve the problem? Commented Oct 17, 2022 at 14:09
  • Thanks for asking Nico Haase. Yes, I moved forward... I finally succeeded to have proper errors messages displayed. However, there is still something that doesn't work properly. I don't get an error message when the password is not filled in. I think it comes from twig file. I don't understand why. I am going to update my description with it. Commented Oct 18, 2022 at 15:06
  • "I don't get an error message when the password is not filled in", Don't use the attribute password in the formType but create another attribute plainPassword , the example is here symfony.com/doc/4.0/doctrine/… Commented Oct 18, 2022 at 15:32

2 Answers 2

5

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>
Sign up to request clarification or add additional context in comments.

30 Comments

Yes hous, the form error appears.
good, try also to not using "error_bubbling => true', let it false by default because symfony.com/doc/current/reference/forms/types/…
Hello hous. Could you give me a hand ? I am trying to find the way to set errors messages when fields from form are not filled.
Hello, just add assert\notBlank and remove "error_bubbling => true', , it should work
Yes, I removed error_bubbling => true. The problem remains the same.
|
0

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

1 Comment

Symfony will guess some validation constraints directly from the doctrine metadata. Although is not strictly neccessary to add the constraints manually it's more explicit and it'll give you finer control, so it's not a bad practice, if you ask me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.