Buy
Buy

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Once you have your authentication system step, pff, life is easy! On a day-to-day basis, you'll spend most of your time in a controller where... well, there's really only two things you can do related to security. One, deny access, like, based on a role:

... lines 1 - 8
/**
* @IsGranted("ROLE_USER")
*/
class AccountController extends AbstractController
{
... lines 14 - 22
}

Or two, figure out who is logged in.

That's exactly what we need to do in AccountController so that we can start printing out details about the user's account. So... how can we find out who is logged in? With $this->getUser():

... lines 1 - 11
class AccountController extends AbstractController
{
... lines 14 - 16
public function index()
{
dd($this->getUser()->getFirstName());
... lines 20 - 22
}
}

Using the User Object

Go back to your browser and head to /account. Nice! This gives us the User entity object! That's awesome because we can do all kinds of cool stuff with it. For example, let's see if we can log the email address of who is logged in.

Add a LoggerInterface $logger argument:

... lines 1 - 4
use Psr\Log\LoggerInterface;
... lines 6 - 12
class AccountController extends AbstractController
{
... lines 15 - 17
public function index(LoggerInterface $logger)
{
... lines 20 - 23
}
}

Then say $logger->debug():

Checking account page for

And then $this->getUser(). Because we know this is our User entity, we know that we can call, getEmail() on it. Do that: ->getEmail():

... lines 1 - 12
class AccountController extends AbstractController
{
... lines 15 - 17
public function index(LoggerInterface $logger)
{
$logger->debug('Checking account page for '.$this->getUser()->getEmail());
... lines 21 - 23
}
}

Cool! Move over and refresh. No errors. Click anywhere down on the web debug toolbar to get into the profiler. Go to the logs tab, click "Debug" and... down a bit, there it is!

Checking account page for spacebar5@example.com.

Base Controller: Auto-complete $this->getUser()

But, hmm, something is bothering me: I do not get any auto-complete on this getEmail() method. Why not? Hold Command or Control and click the getUser() method. Ah: it's simple: Symfony doesn't know what our User class is. So, its PhpDoc can't really tell PhpStorm what this method will return.

To get around this, I like to create my own BaseController class. In the Controller/ directory, create a new PHP class called BaseController. I'll make it abstract because this is not going to be a real controller - just a helpful base class. Make it extend the normal AbstractController that we've been using in our existing controllers:

... lines 1 - 2
namespace App\Controller;
... lines 4 - 5
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
abstract class BaseController extends AbstractController
{
... lines 10 - 13
}

Tip

A simpler solution (and one that avoids a deprecation warning) is to advertise to your IDE that getUser() returns a User (or null) with some PHPDoc:

/**
 * @method User|null getUser()
 */
class BaseController extends AbstractController
{
}

Then, I'll go to the "Code"->"Generate" menu - or Command+N on a Mac, click "Override Methods" and override getUser(). We're not actually going to override how this method works. Just return parent::getUser(). But, add a return type User - our User class:

... lines 1 - 4
use App\Entity\User;
... lines 6 - 7
abstract class BaseController extends AbstractController
{
protected function getUser(): User
{
return parent::getUser();
}
}

From now on, instead of extending AbstractController, we should extend BaseController:

... lines 1 - 11
class AccountController extends BaseController
{
... lines 14 - 23
}

And this will give us the proper auto-completion on getUser():

... lines 1 - 11
class AccountController extends BaseController
{
... lines 14 - 16
public function index(LoggerInterface $logger)
{
$logger->debug('Checking account page for '.$this->getUser()->getEmail());
... lines 20 - 22
}
}

I also like to use my BaseController to add other shortcut methods specific to my app. If there's something that you do frequently, but it doesn't make sense to move that logic into a service, just add a new protected function.

I won't go and update my other controllers to extend BaseController right this second - I'll do that little-by-little when I need to.

Fetching the User in Twig

Ok: we now know how to fetch the User object in a controller. So, how can we fetch it inside a template? Find the templates/ directory and open our account/index.html.twig. The answer is... app.user. That's it! We can call app.user.firstName:

{% extends 'base.html.twig' %}
{% block title %}Manage Account!{% endblock %}
{% block body %}
<h1>Manage Your Account {{ app.user.firstName }}</h1>
{% endblock %}

Try that out. Go back to /account and... perfect!

Symfony gives you exactly one global variable in Twig: app. And it just has a few helpful things on it, like app.user and app.session. And because app.user returns our User object, we can call firstName on it. Twig will call getFirstName() on User.

Making the Account Page Pretty

Oh, and, oof. This page is super ugly. Clear out the h1. I'm going to paste in some HTML markup I prepared: you can copy this markup from the code block on this page:

{% extends 'base.html.twig' %}
{% block title %}Manage Account!{% endblock %}
... lines 4 - 10
{% block body %}
<div class="container">
<div class="row user-menu-container square">
<div class="col-md-12 user-details">
<div class="row spacepurplebg white">
<div class="col-md-2 no-pad">
<div class="user-image">
<img src="https://robohash.org/hello@symfonycasts.com" class="img-responsive thumbnail">
</div>
</div>
<div class="col-md-10 no-pad">
<div class="user-pad">
<h3>Welcome back, ?????</h3>
<h4 class="white"><i class="fa fa-twitter"></i> ?????</h4>
<a class="btn btn-labeled btn-info" href="#">
<span class="btn-label"><i class="fa fa-pencil"></i></span>Update
</a>
</div>
</div>
</div>
<div class="row overview">
<div class="col-md-4 user-pad text-center">
<h3>COMMENTS</h3>
<h4>184</h4>
</div>
<div class="col-md-4 user-pad text-center">
<h3>ARTICLES READ</h3>
<h4>1,910</h4>
</div>
<div class="col-md-4 user-pad text-center">
<h3>LIKES</h3>
<h4>3,892</h4>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

If you refresh right now... oof. It still looks pretty terrible. Oh, hello robot! Anyways, the page looks awful because this markup requires another CSS file. If you downloaded the course code, you should have a tutorial/ directory. We already copied this login.css file earlier. Now, copy account.css, find your public/ directory, open css/ and... paste! To include this stylesheet on this page, add block stylesheets and endblock:

... lines 1 - 4
{% block stylesheets %}
... lines 6 - 8
{% endblock %}
... lines 10 - 49

Inside, call parent() so that we add to the existing stylesheets, instead of replacing them. Add link and point to css/account.css:

... lines 1 - 4
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('css/account.css') }}">
{% endblock %}
... lines 10 - 49

PhpStorm auto-completes the asset() function for me.

Now refresh again. So much better! All of this markup is 100% hardcoded. But I added friendly ? marks where we need to print some dynamic stuff. Let's do it! For the Avatar, we're using this cool RoboHash site where you give it an email, and it gives you a robot avatar. I love the Internet!

Replace this with app.user.email:

... lines 1 - 10
{% block body %}
<div class="container">
<div class="row user-menu-container square">
<div class="col-md-12 user-details">
<div class="row spacepurplebg white">
<div class="col-md-2 no-pad">
<div class="user-image">
<img src="https://robohash.org/{{ app.user.email }}" class="img-responsive thumbnail">
</div>
</div>
... lines 21 - 29
</div>
... lines 31 - 44
</div>
</div>
</div>
{% endblock %}

Then, down by "Welcome back", replace that with app.user.firstName:

... lines 1 - 10
{% block body %}
<div class="container">
<div class="row user-menu-container square">
<div class="col-md-12 user-details">
<div class="row spacepurplebg white">
<div class="col-md-2 no-pad">
<div class="user-image">
<img src="https://robohash.org/{{ app.user.email }}" class="img-responsive thumbnail">
</div>
</div>
<div class="col-md-10 no-pad">
<div class="user-pad">
<h3>Welcome back, {{ app.user.firstName }}</h3>
... lines 24 - 27
</div>
</div>
</div>
... lines 31 - 44
</div>
</div>
</div>
{% endblock %}

Cool! Let's see how it looks like now.

Hey! A brand new robot avatar and we see the first name of the dummy user. We are still missing this twitter handle... because... our User class doesn't have that property yet:

... lines 1 - 10
{% block body %}
<div class="container">
<div class="row user-menu-container square">
<div class="col-md-12 user-details">
<div class="row spacepurplebg white">
... lines 16 - 20
<div class="col-md-10 no-pad">
<div class="user-pad">
... line 23
<h4 class="white"><i class="fa fa-twitter"></i> ?????</h4>
... lines 25 - 27
</div>
</div>
</div>
... lines 31 - 44
</div>
</div>
</div>
{% endblock %}

Let's add that next. Add a cool shortcut method to our User class and talk about how we can fetch the User object from the one place we haven't talked about yet - services.

Leave a comment!

  • 2019-01-22 weaverryan

    Hey Tomasz Gąsior!

    Yea, one of the biggest reason that it's not documented (or at least, poorly documented) is that I don't really like this feature (though other people definitely disagree with me!). We already have a lot of magic in the controller, and I didn't see the benefit of adding yet-another "magic" resolution of controller arguments. Also, because you have to type-hint UserInterface, your editor doesn't will only auto-complete methods on that interface - not any custom methods you have. That's also true with the getUser() shortcut, but you can work around that by adding documentation to a base controller (like we do in this tutorial). This is probably also why that bug exists - not a lot of people use this way of getting the user.

    So, I hope that at least explains a bit about the missing documentation and the bug. But, let me know!

    Cheers!

  • 2019-01-22 Tomasz Gąsior

    It seems to me there is no information about this feature in the documentation. There is blog post about it: https://symfony.com/blog/ne...

    There is a bug in combination of @IsGranted (specifically annotation, no AbstractController shortcut) and UserInterface type hint without nullable. It makes sense to use UserInterface type hint without nullable and without default "null" value if @IsGranted enforces user session. Technical details about bug are described here: https://github.com/sensiola... Basically @IsGranted is interpreted AFTER preparation of controller arguments, this causes problem. My pull request tries to fix it: https://github.com/sensiola...

  • 2019-01-22 Victor Bocharsky

    Hey Tomasz,

    Thank for this tip! Btw, if you can share a link to the docs where you found this feature - would be awesome! Yeah, it makes sense to use "?UserInterface" type-hint on those pages where user might be anonymous, good point! Otherwise, you will have problems. Or, probably "create(UserInterface $user = null)" might work too, but I have not tried it yet. But anyway, "?UserInterface" makes more sense here.

    But this is interesting that it's buggy in combination with isGranted(). Are you sure it's related to injecting the current user? If you fo not inject the current user - isGranted() starts working well again? Because, at the first sight, injecting the current user and isGranted() annotation has nothing in common except using current logged in user from the internals. Though I'm do not know the internal code that's responsible for annotations.

    Btw, what about using "$this->isGranted('IS_AUTHENTICATED_REMEMBERED')" in the beginning of your create() method instead of @isGranded annotation? Does it works well or also have problems?

    Cheers!

  • 2019-01-17 Tomasz Gąsior

    Instead of using `getUser()` shortcut method, we can use dependency injection: `UserInterface` type-hint in arguments of controller action.

    ```
    /**
    * @Route("/something")
    * @IsGranted("IS_AUTHENTICATED_REMEMBERED")
    */
    public function create(UserInterface $user): Response
    {
    ```

    Unfortunately, this is currently buggy in combination with @IsGranted when user is logged out. Now, to fix that it's needed to use nullable type-hint `?UserInterface`. I created pull request to fix it inside annotations bundle: https://github.com/sensiola...

  • 2018-10-19 Diego Aguiar

    Hey Serge Boyko

    Aha! Nice catch man. I already fixed the note

    Cheers!

  • 2018-10-19 Serge Boyko

    I think "getUser()" in BaseController should return "?User", because if nobody is logged in, the method will return null. And thanks for showing "https://robohash.org/" - it's hilarious! 🤖

  • 2018-10-05 weaverryan

    Hey Stéphane!

    Great catch! I didn't realize that these methods had been marked as final! I talked internally about it, and here is a better solution:


    // ....

    use App\Entity\User;

    // ...

    /**
    * @method User getUser()
    */
    class BaseController extends AbstractController
    {
    }

    Basically, you don't need to override getUser() in order to "advertise" that it returns some other object. You can instead use this nice @method trick. You get the same result, but no deprecation.

    Thanks again for the report!

  • 2018-10-03 Diego Aguiar

    Oh yes, you can extend from the "AbstractController" class, it's totally ok to do that (and probably it's better). About the deprecation message, I just tried it and yes, I do see that message too but I'm not totally sure if we can just ignore it. I'll ask Ryan about it but it may take some time to get an answer back.

    Cheers!

  • 2018-10-03 Stéphane

    Hey Diego Aguiar,
    Thank for your reply.

    I use Symfony 4.1.5. I have only follow the tip of Ryan about BaseController.

  • 2018-10-02 Diego Aguiar

    Hey Stéphane

    Which Symfony version are you using?
    In Symfony4 `AbstractController` is not final, but we don't extend from it anyways, we extend from `Controller.php`. If you keep seeing that message is because you are overriding the getUser() method when you should not.

    Cheers!

  • 2018-10-02 Stéphane

    Hello,
    I notice that symfony profiler generate a log message about deprecation :

    The "Symfony\Bundle\FrameworkBundle\Controller\AbstractController::getUser()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "App\Controller\BaseController".

    It is not a problem ?