Buy
This tutorial has a new version, check it out!

Error Formatting for Twitter Bootstrap

Error Formatting for Twitter Bootstrap

Submit the form with some bad data. Oh, it’s terrible. The errors, they’re so ugly. We must fix this.

Go back to form_div_layout.html.twig. We don’t know which block renders errors, but if you search for the word “errors”, you’ll find it: form_errors.

Copy it into our template:

{# app/Resources/views/form_theme.html.twig #}
{# ... #}

{% block form_errors %}
    {% if errors|length > 0 %}
    <ul>
        {% for error in errors %}
            <li>{{ error.message }}</li>
        {% endfor %}
    </ul>
    {% endif %}
{% endblock form_errors %}

Here’s the plan. Give the ul a help-block class. This class is from Twitter Bootstrap:

{# app/Resources/views/form_theme.html.twig #}
{# ... #}

{% block form_errors %}
    {% if errors|length > 0 %}
    <ul class="help-block">
        {% for error in errors %}
            <li>{{ error.message }}</li>
        {% endfor %}
    </ul>
    {% endif %}
{% endblock form_errors %}

Refresh. It’s a very minor improvement, but we’ve at least modified our second form block. I’ll leave the bullet point, but if you want to add some CSS to get rid of it, be my guest. It is ugly.

Next, let’s see if we can highlight the error message in red. Hardcode a has-error field to the div in form_row:

{# app/Resources/views/form_theme.html.twig #}

{% block form_row %}
    <div class="form-group has-error">
        {{ form_label(form) }}
        {{ form_errors(form) }}
        {{ form_widget(form) }}
    </div>
{% endblock form_row %}

Refresh. This worked, we have red error text but in a second this class is also going to turn the fields red. But we don’t want every field to always look like an emergency, so what can we do?

Form Variables: The Holy Grail of Form Rendering Control

Inside the form_errors block, we have access to some errors variable. In fact, in each block we have access to a bunch of variables, like label, value, name, full_name and required.

Let’s use a trick to see all of the variables we have access to in form_errors:

{# app/Resources/views/form_theme.html.twig #}
{# ... #}

{% block form_errors %}
    {{ dump(_context|keys) }}

    {% if errors|length > 0 %}
    <ul class="help-block">
        {% for error in errors %}
            <li>{{ error.message }}</li>
        {% endfor %}
    </ul>
    {% endif %}
{% endblock form_errors %}

Tip

dump is a Twig debugging function, like var_dump. You can pass it any variable to print it out.

Refresh! For each field, you now see a giant list - for me, 27 things. All of these are variables that you magically have access to inside a form theme block. And the variables are the same no matter what block you’re in.

Remove the dump call. So we can finally use the errors variable in form_row to only print the class if the field has errors:

{# app/Resources/views/form_theme.html.twig #}
{# ... #}

{% block form_row %}
    <div class="form-group {{ errors|length > 0 ? 'has-error' : '' }}">
        {{ form_label(form) }}
        {{ form_errors(form) }}
        {{ form_widget(form) }}
    </div>
{% endblock form_row %}
{# ... #}

Re-submit, fill in some fields correctly. Cool, we still see the red errors, but the other fields are missing this class. That’s awesome.

Leave a comment!

  • 2018-02-23 Victor Bocharsky

    Hey Junaid,

    Hm, I see a few problems in your code which could cause this validation problems, so let me tweak your code to the best practice and if you're wondering about any changes I made - just ask another question ;) For simplicity, if I only change things of add new ones, so if I'm not sowing something in my code - this means it's the same as in yours.

    So, first of all your need to bind RegisterFormType to the entity:


    class RegisterFormType extends AbstractType
    {
    // ...
    public function configureOptions(OptionsResolver $resolver)
    {
    $resolver->setDefaults([
    'data_class' => User::class,
    ]);
    }
    }


    public function registerAction(Request $request)
    {
    $user = new User();
    $form = $this->createForm(RegisterFormType::class, $user);

    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
    $em = $this->getDoctrine()->getManager();
    $em->persist($user);
    $em->flush();

    $request->getSession()
    ->getFlashBag()
    ->add('success', 'User Registered Successfully!');

    $this->authenticateUser($user);

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

    return array('form' => $form->createView());
    }

    So, it should work now, let me know if it does not help. I suppose the problem is that your don't have this configureOptions() method, so validator does not see your validation constrains ;) Also, don't forget to clear the cache after those changes.

    Cheers!

  • 2018-02-22 Junaid Farooq

    Hey Victor Bocharsky

    Again Thank you for your prompt response. Back to your response. yes I am using the namespaces required in the User Entity. here are all of them:


    use Doctrine\ORM\Mapping as ORM;
    use Gedmo\Mapping\Annotation as Gedmo;
    use Symfony\Component\Security\Core\User\AdvancedUserInterface;
    use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
    use Symfony\Component\Validator\Constraints as Assert;
    use Doctrine\Common\Collections\ArrayCollection;

    //...........
    class User implements AdvancedUserInterface, \Serializable
    //...........

    To your question: Yes i am using "$form->isSubmitted().....".Here is the whole registerAction():


    // ...........
    class RegisterController extends Controller
    // .........................
    public function registerAction(Request $request)
    {
    $form = $this->createForm(new RegisterFormType());

    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
    $data = $form->getData();

    $user = new User();
    $user->setUsername($data['username']);
    $user->setEmail($data['email']);
    $user->setPlainPassword($data['plainPassword']);

    $em = $this->getDoctrine()->getManager();
    $em->persist($user);
    $em->flush();

    $request->getSession()
    ->getFlashBag()
    ->add('success', 'User Registered Successfully!');

    $this->authenticateUser($user);

    $url = $this->generateUrl('event');
    return $this->redirect($url);

    }

    return array('form' => $form->createView());
    }
    //.....................

    Here is my RegisterFormType code:


    namespace Yoda\UserBundle\Form;

    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;

    class RegisterFormType extends AbstractType
    {
    /**
    * @param FormBuilderInterface $builder
    * @param array $options
    */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    $builder->add('username', 'text', array(
    'required' => true
    ))
    ->add('email', 'email', array(
    'required' => true
    ))
    ->add('plainPassword', 'repeated', array(
    'type' => 'password',
    'required' => true,
    'first_options' => array('label' => 'Password'),
    'second_options' => array('label' => 'Repeat Password'),
    ));
    }

    public function getName()
    {
    return 'user_register';
    }

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

    Please have a look and thank you once again for reaching out. Cheers!

  • 2018-02-22 Victor Bocharsky

    Hey Junaid,

    Hm, looks legitimate for me if you use the next namespace in User entity:
    use Symfony\Component\Validator\Constraints as Assert;

    So it really should work. Do you use "$form->isSubmitted() && $form->isValid()" in your controller? Well, try to clear the cache first and if you still have this problem, can you show your form type you use and a part of your controller which relates to this form?

    Cheers!

  • 2018-02-22 Junaid Farooq

    Hey Victor Bocharsky

    Thanks for replying. I am using the constraints in the User entity. Following is an example of sych for Username and email:

    class User implements AdvancedUserInterface, \Serializable
    ..................
    /**
    * @var string
    *
    * @ORM\Column(name="username", type="string", length=255, unique=true)
    * @Assert\NotBlank(message="Username cannot be empty")
    * @Assert\Length(min=3, minMessage="Username should be at least 3 characters!")
    */
    private $username;

    /**
    * @var string
    *
    * @ORM\Column(name="email", type="string", length=255, unique=true)
    * @Assert\NotBlank(message="Email cannot be empty")
    * @Assert\Email
    *
    */
    private $email;
    ...................

    Cheers!

  • 2018-02-21 Victor Bocharsky

    Hey Junaid,

    What validation constraints do you use? And where do you declare it? Because, by default, Symfony does not add some validation on the server side for empty fields, you need to add it by your own.

    Cheers!

  • 2018-02-21 Junaid Farooq

    Hey weaverryan

    Thanks for replying. I meant my server side form validation is not working. No errors are shown. For example if i disable the front end validation and then try to submit an empty form, it doesn't validate the form from the server side instead it tries to insert the data into the database which then throws a db exception. I hope you get what i mean. Cheers!

  • 2018-02-20 weaverryan

    Hey Junaid Farooq!

    So... do you mean that form validation IS working, but you don't see the bootstrap styles? Or something different? If so, if you view the HTML source, do you see classes like form-group and form-control in your HTML, or not?

    Cheers!

  • 2018-02-20 Junaid Farooq

    Hi there,

    My form validation is working partially let alone bootstrap validation. Thanks in advance.