Fetch the User Object
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOnce 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.
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 ?