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 SubscribeOne of the amazing features of our site is that you can up vote and down vote each answer. Right now, you don't even need to be logged in to do this. Let's change that.
Find the controller that handles the Ajax call that's made when we vote: it's src/Controller/AnswerController.php
... the answerVote()
method. Ok: I want to require the user to be logged in to use this endpoint. Let's do that with an annotation... or attribute: @IsGranted
... then select that class and hit tab so that it adds the use
statement we need up on top. Inside, use IS_AUTHENTICATED_REMEMBERED
:
... lines 1 - 8 | |
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; | |
... lines 10 - 13 | |
class AnswerController extends AbstractController | |
{ | |
... lines 16 - 29 | |
/** | |
... line 31 | |
* @IsGranted("IS_AUTHENTICATED_REMEMBERED") | |
*/ | |
public function answerVote(Answer $answer, LoggerInterface $logger, Request $request, EntityManagerInterface $entityManager) | |
{ | |
... lines 36 - 51 | |
} | |
} |
Because we're using the remember me system, this is the correct way to check if the user is simply logged in.
If we stop now, because we're not logged in, we won't be able to vote. Yay! But it's going to look funny on the frontend because the vote links are still visible. So let's hide those.
The template for this section is templates/answer/_answer.html.twig
. Let's see... down... here are the vote arrows. So we basically want to hide this entire div
section if we are not logged in. If is_granted('IS_AUTHENTICATED_REMEMBERED')
, find the closing div
... here it is, and add endif
:
<li class="mb-4"> | |
... lines 2 - 12 | |
<div class="row"> | |
... lines 14 - 20 | |
<div class="col-2 text-end"> | |
... line 22 | |
{% if is_granted('IS_AUTHENTICATED_REMEMBERED') %} | |
<div | |
class="vote-arrows" | |
{{ stimulus_controller('answer-vote', { | |
url: path('answer_vote', { | |
id: answer.id | |
}) | |
}) }} | |
> | |
... lines 32 - 44 | |
</div> | |
{% endif %} | |
</div> | |
</div> | |
</li> |
When we refresh... yes! The vote links are gone.
In a real app, when we save the vote to the database, we will probably also store who voted so we can prevent a user from voting multiple times on the same answer. We're not going to do that right now... but let's try something simpler: let's log a message that includes the email address of who is voting.
But wait: how do we find out who is logged in? In a controller, it's easy peasy: use the $this->getUser()
shortcut. Check it out: on top, I'll say $logger->info('')
with the message:
{user} is voting on answer {answer}
... lines 1 - 13 | |
class AnswerController extends AbstractController | |
{ | |
... lines 16 - 33 | |
public function answerVote(Answer $answer, LoggerInterface $logger, Request $request, EntityManagerInterface $entityManager) | |
{ | |
$logger->info('{user} is voting on answer {answer}!', [ | |
... lines 37 - 38 | |
]); | |
... lines 40 - 56 | |
} | |
} |
Pass this a second argument, which is called the logger "context". This is unrelated to security... it's just kind of cool. The second argument is an array of any extra data that you want to store along with the message. For example, we can set answer
to $answer->getId()
:
... lines 1 - 13 | |
class AnswerController extends AbstractController | |
{ | |
... lines 16 - 33 | |
public function answerVote(Answer $answer, LoggerInterface $logger, Request $request, EntityManagerInterface $entityManager) | |
{ | |
$logger->info('{user} is voting on answer {answer}!', [ | |
... line 37 | |
'answer' => $answer->getId(), | |
]); | |
... lines 40 - 56 | |
} | |
} |
And... if you use this nifty {answer}
format, then the answer
context will automatically be put into the message. We'll see that in a minute.
For the user
, get the current user with $this->getUser()
... it's that easy. This will give us the User
object... and then we can call a method on it, like ->getUserIdentifier()
, which we know will be the email:
... lines 1 - 13 | |
class AnswerController extends AbstractController | |
{ | |
... lines 16 - 33 | |
public function answerVote(Answer $answer, LoggerInterface $logger, Request $request, EntityManagerInterface $entityManager) | |
{ | |
$logger->info('{user} is voting on answer {answer}!', [ | |
'user' => $this->getUser()->getUserIdentifier(), | |
'answer' => $answer->getId(), | |
]); | |
... lines 40 - 56 | |
} | |
} |
Sweet! Let's test this thing! First... we need to log in - abraca_admin@example.com
, password tada
. And... got it! It redirected us back to /admin/login
because, a few minutes ago, we tried to access this and were redirected to the login form. So it's technically still in the session as our "target path".
Head to the homepage, click into a question... and vote! On the web debug toolbar, we can see that Ajax call... and we can even open the profiler for that request by clicking the link. Head to Logs
. Sweet!
abraca_admin@example.com
is voting on answer 498
Back in the controller, we know that $this->getUser()
will return our User
object... which means that we can call whatever methods it has. For example, our User
class has a getEmail()
method:
... lines 1 - 13 | |
class AnswerController extends AbstractController | |
{ | |
... lines 16 - 33 | |
public function answerVote(Answer $answer, LoggerInterface $logger, Request $request, EntityManagerInterface $entityManager) | |
{ | |
$logger->info('{user} is voting on answer {answer}!', [ | |
'user' => $this->getUser()->getEmail(), | |
... line 38 | |
]); | |
... lines 40 - 56 | |
} | |
} |
So this will work. But notice that my editor did not auto-complete that. Bummer!
Hold Command
or Ctrl
and click getUser()
. This jumps us to the core AbstractController
. Ah... the method advertises that it returns a UserInterface
, which is true! But, more specifically, we know that this will return our User
entity. Unfortunately, because this method doesn't say that, we don't get nice auto-completion.
I use $this->getUser()
a lot in my controllers... so I like to "fix" this. How? By creating a custom base controller class. Inside of the Controller/
directory, create a new class called BaseController
.
You can make this abstract
... because we won't ever use it directly. Make it extended AbstractController
so that we get the normal shortcut methods:
... lines 1 - 2 | |
namespace App\Controller; | |
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |
abstract class BaseController extends AbstractController | |
{ | |
} |
Creating a custom base controller is... just kind of a nice idea: you can add whatever extra shortcut methods you want. Then, in your real controllers, you extend this and... have fun! I'm only going to do this in AnswerController
right now... just to save time:
... lines 1 - 14 | |
class AnswerController extends BaseController | |
{ | |
... lines 17 - 58 | |
} |
Anyways, if we stopped now... congratulations! This doesn't change anything because BaseController
extends AbstractController
. To solve our problem, we don't need to add a new shortcut method... we just need to give our editor a hint so that it knows that getUser()
returns our User
object... not just a UserInterface
.
To do that, above the class, add @method
then User
then getUser()
:
... lines 1 - 4 | |
use App\Entity\User; | |
... lines 6 - 7 | |
/** | |
* @method User getUser() | |
*/ | |
abstract class BaseController extends AbstractController | |
{ | |
} |
Done! Back in AnswerController
, re-type getEmail()
and... yes! We get auto-completion!
Cool! So the way that you get the current user in a controller is $this->getUser()
. But there are a few other places where we might need to do this, like in Twig or from a service. Let's check those out next.
Hey Rufnex,
Did you put the annotation above the class name? Also, try removing the initial backslash from the return type definition
Cheers!
Hi!
Is it possible to use @method above the class but use attributes php8 instead of annotations ?
Thank you so much for your work.
Hey Magali T.
No because that is not annotation, or something that PHP is caring about. This is just a comment for editor that improves autocompletion.
Cheers!
Hi Ryan,
I think there is a probleme at bottom of this article :
To do that, above the class, add @method then User then getUser():
Array
Thank you, you are the best !
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.3", // v3.3.0
"composer/package-versions-deprecated": "^1.11", // 1.11.99.4
"doctrine/annotations": "^1.0", // 1.13.2
"doctrine/doctrine-bundle": "^2.1", // 2.6.3
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
"doctrine/orm": "^2.7", // 2.10.1
"knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
"knplabs/knp-time-bundle": "^1.11", // v1.16.1
"pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
"pagerfanta/twig": "^3.3", // v3.3.0
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"scheb/2fa-bundle": "^5.12", // v5.12.1
"scheb/2fa-qr-code": "^5.12", // v5.12.1
"scheb/2fa-totp": "^5.12", // v5.12.1
"sensio/framework-extra-bundle": "^6.0", // v6.2.0
"stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
"symfony/asset": "5.3.*", // v5.3.4
"symfony/console": "5.3.*", // v5.3.7
"symfony/dotenv": "5.3.*", // v5.3.8
"symfony/flex": "^1.3.1", // v1.17.5
"symfony/form": "5.3.*", // v5.3.8
"symfony/framework-bundle": "5.3.*", // v5.3.8
"symfony/monolog-bundle": "^3.0", // v3.7.0
"symfony/property-access": "5.3.*", // v5.3.8
"symfony/property-info": "5.3.*", // v5.3.8
"symfony/rate-limiter": "5.3.*", // v5.3.4
"symfony/runtime": "5.3.*", // v5.3.4
"symfony/security-bundle": "5.3.*", // v5.3.8
"symfony/serializer": "5.3.*", // v5.3.8
"symfony/stopwatch": "5.3.*", // v5.3.4
"symfony/twig-bundle": "5.3.*", // v5.3.4
"symfony/ux-chartjs": "^1.3", // v1.3.0
"symfony/validator": "5.3.*", // v5.3.8
"symfony/webpack-encore-bundle": "^1.7", // v1.12.0
"symfony/yaml": "5.3.*", // v5.3.6
"symfonycasts/verify-email-bundle": "^1.5", // v1.5.0
"twig/extra-bundle": "^2.12|^3.0", // v3.3.3
"twig/string-extra": "^3.3", // v3.3.3
"twig/twig": "^2.12|^3.0" // v3.3.3
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
"symfony/debug-bundle": "5.3.*", // v5.3.4
"symfony/maker-bundle": "^1.15", // v1.34.0
"symfony/var-dumper": "5.3.*", // v5.3.8
"symfony/web-profiler-bundle": "5.3.*", // v5.3.8
"zenstruck/foundry": "^1.1" // v1.13.3
}
}
How to use @method with a custom entity for the user. In my case i use an existing table called customer.
Somethink like this doesen work.
/**
*/
But i can use Methode from my customer entitiy (without auto-completion) like $this->getUser()->getSomeFieldValue() ...
Thank you!