FOSUserBundle with Bootstrap 3 Sign In Form


In the previous video we looked at how we could log in with either our Username or our Email Address as our unique identifier. In that video we switched over to using a Bootstrap 3 Sign In template. In this video we will cover a little more of customising your Sign In template.

I pasted the full code to the template in the previous video write up, so be sure to look there if you want to copy / paste.

Back when we started customising our FOSUserBundle template in the third video, we came across the concept of template inheritence.

This isn't something unique to FOSUserBundle. If you use Twig at all, even outside of Symfony, you can make use of template inheritence.

The idea is simple. We want a base template which contains all the common HTML 'stuff' we find in any web page - head, title, body tags, and then our more specific templates update only a specific section of the base template.

A quick example may make this easier to understand:

<!-- app/Resources/views/base.html.twig -->
<!DOCTYPE html>
<html>
    <head>
        <title>{% block title %}A default title here{% endblock %}</title>
    </head>
    <body>
        {% block body %}{%endblock%}
    </body>
</html>
<!-- app/Resources/views/OurDir/our-1st-template.html.twig -->
{% extends '::base.html.twig' %}

{% block body %}
  <p>This page would get / inherit all the HTML 'stuff' from the base.html.twig file</p>
{% endblock %}
<!-- app/Resources/views/AnotherDir/our-2nd-template.html.twig -->
{% extends '::base.html.twig' %}

{% block title%}Fancy custom title{% endblock %}

{% block body %}
  <p>This page would also inherit all the HTML from the base.html.twig file</p>
  <p>And this time we provided a more specific / overridden title also</p>
{%endblock%}

Perhaps the most confusing of all is the use of {% extends '::base.html.twig' %}.

In this instance extends is telling Twig to use the given template name - '::base.html.twig' - as the 'wrapper' around this template. As long as our block names match up, we can overwrite the less specific / base template content without affecting all the HTML tags etc.

But what about '::base.html.twig'?

Well, what this is saying is look inside the default directory (app/Resources/views), and we should find our template file in the root of that folder.

That is what the two colons represent.

Prior to the release of the Best Practices document, you would be much more likely to see template names like this:

MyBundle:SomeDir:my-template.html.twig - meaning that our template lived in src/MyBundle/Resources/views/SomeDir/my-template.html.twig.

Take a look at the best practices doc for more examples if this is unclear. The two colons confused me no end when I first started using Symfony.

But back to the video content...

As mentioned, in video three we set up our own FOSUserBundle base layout file. As such, our login / sign in template inheritence looks like this:

app/Resources/FOSUserBundle/views/Security/login.html.twig

which extends:

app/Resources/FOSUserBundle/views/layout.html.twig

which extends:

app/Resources/views/base.html.twig

This sort of inheritence is common, though the fewer the number of layers, the easier things are to understand. My advice - keep as few layers as possible. If you go over five layers, try to refactor.

If you've followed along, you may be wondering why we have the 'Login' link on our Sign In template, whereas that doesn't exist on the Bootstrap example.

That link is coming from our app/Resources/FOSUserBundle/views/layout.html.twig template:

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

{% block content %}

    <div>
        {% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}
            {{ 'layout.logged_in_as'|trans({'%username%': app.user.username}, 'FOSUserBundle') }} |
            <a href="{{ path('fos_user_security_logout') }}">
                {{ 'layout.logout'|trans({}, 'FOSUserBundle') }}
            </a>
        {% else %}
            <a href="{{ path('fos_user_security_login') }}">{{ 'layout.login'|trans({}, 'FOSUserBundle') }}</a>
        {% endif %}
    </div>

    {% if app.request.hasPreviousSession %}
        {% for type, messages in app.session.flashbag.all() %}
            {% for message in messages %}
                <div class="flash-{{ type }}">
                    {{ message }}
                </div>
            {% endfor %}
        {% endfor %}
    {% endif %}

    <div>
        {% block fos_user_content %}
        {% endblock fos_user_content %}
    </div>

{% endblock %}%

Specifically, the lines:

    {% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}
        * snip *
    {% else %}
        <a href="{{ path('fos_user_security_login') }}">{{ 'layout.login'|trans({}, 'FOSUserBundle') }}</a>
    {% endif %}

It's worth pointing out here that this may not behave entirely as you might expect.

Assuming we have never logged in, we would hit the else block, and see the Login link.

So far, so good.

But if you log in, you won't see the Logout link from the if is_granted("IS_AUTHENTICATED_REMEMBERED") block. At least, not by default.

Why not?

Well, this is a confusing, but it's to do with the location of that snippet.

Once we have logged in, we are likely to be redirected to a page not related to any of our FOSUserBundle templates. So we won't inherit that snippet as part of our template structure. This means we won't see it. We must move it.

This doesn't have anything to do with the Remember Me functionality, which you might have guessed at if you were reading the code.

Symfony has a load of cool security features, but many of them are not enabled by default. FOSUserBundle won't enable the remember_me functionality if you simply follow the installation instructions - as we have done so far.

To get access to the remember_me functionality, you have to manually add it in yourself. Now, this isn't difficult... and good news! Remember me will be the next video in this series.

Can't wait till then? Check out the official docs for the needed implementation details.

But what does remember_me have to do with our login?

We will cover this more thoroughly in the next video, but for now, just be aware that IS_AUTHENTICATED_REMEMBERED means that the logged in user has only been authenticated using their 'remember me' cookie, which is not the same as when a user provides their credentials during the current session.

Flash Messages

The error message you see if you fail login for any reason is not a flash message. It is a form error message.

Inside our app/Resources/FOSUserBundle/views/layout.html.twig template we have a section for flash messages:

    {% if app.request.hasPreviousSession %}
        {% for type, messages in app.session.flashbag.all() %}
            {% for message in messages %}
                <div class="flash-{{ type }}">
                    {{ message }}
                </div>
            {% endfor %}
        {% endfor %}
    {% endif %}

You may wish to style these according to Bootstrap's Alerts styling:

            {% for message in messages %}
                <div class="alert alert-{{ type }}">
                    {{ message }}
                </div>
            {% endfor %}

However, you may have tried this already and wondered why your Login Error messages do not get styled in this section.

Again, slightly confusing.

To style the FOSUserBundle error messages for login, we need to update the app/Resources/FOSUserBundle/views/Security/login.html.twig template. Specifically:

{% if error %}
    <div class="alert alert-danger" role="alert">
        {{ error.messageKey|trans(error.messageData, 'security') }}
    </div>
{% endif %}

I have added in the Bootstrap 3 'danger' alert styles to the above.

You can look inside the SecurityController.php file from FOSUserBundle and see that the $error information is passed directly to our template, rather than added to the Flashbag. The Flashbag is a Symfony object / concept that acts as a container for all our messages that we want to flash / show only once to our user.

Code For This Course

Get the code for this course.

Episodes