Fetching the User In a Service

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

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

Login Subscribe

We know how to get the user in a template:

... line 1
<html lang="en">
... lines 3 - 15
<body>
<nav class="navbar navbar-expand-lg navbar-dark navbar-bg mb-5">
... lines 18 - 21
<div class="collapse navbar-collapse" id="navbarNavDropdown">
... lines 23 - 34
<ul class="navbar-nav ml-auto">
{% if is_granted('ROLE_USER') %}
<li class="nav-item dropdown" style="margin-right: 75px;">
<a class="nav-link dropdown-toggle" href="http://example.com" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="nav-profile-img rounded-circle" src="{{ app.user.avatarUrl(100) }}">
</a>
... lines 41 - 47
</li>
... lines 49 - 52
{% endif %}
</ul>
</div>
</nav>
... lines 57 - 74
</body>
</html>

And... we know how to get the user from a controller with $this->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
}
}

But... what about from inside a service? Because this nice $this->getUser() shortcut will only work in controllers.

To show you what I mean, I need to remind you of a feature we built a long time ago, like 3 screencasts ago. Click on any article. Then, click anywhere on the web debug toolbar to open the profiler. Find the "Logs" section and click on "Info & Errors". There it is!

They are talking about bacon again!

This is a super-informative log message that we added from inside our markdown service: src/Service/MarkdownHelper.php:

... lines 1 - 8
class MarkdownHelper
{
... lines 11 - 23
public function parse(string $source): string
{
if (stripos($source, 'bacon') !== false) {
$this->logger->info('They are talking about bacon again!');
}
// skip caching entirely in debug
if ($this->isDebug) {
return $this->markdown->transform($source);
}
$item = $this->cache->getItem('markdown_'.md5($source));
if (!$item->isHit()) {
$item->set($this->markdown->transform($source));
$this->cache->save($item);
}
return $item->get();
}
}

This code parses the article content through markdown and caches it. But also, if it sees the word "bacon" in the content ... which every article has in our fixtures, it logs this message.

So here's our challenge: I want to add information about who is currently logged in to this message. To do that, we need to answer one question: how can we access the current User object from inside a service?

The Security Service

The answer is... of course - by using another service. The name of the service that gives you access to the User object is easy to remember. Add another argument: Security $security:

... lines 1 - 7
use Symfony\Component\Security\Core\Security;
class MarkdownHelper
{
... lines 12 - 18
public function __construct(AdapterInterface $cache, MarkdownInterface $markdown, LoggerInterface $markdownLogger, bool $isDebug, Security $security)
{
... lines 21 - 25
}
... lines 27 - 48
}

I'll hit Alt+Enter and click "Initialize Fields" to create that property and set it:

... lines 1 - 7
use Symfony\Component\Security\Core\Security;
class MarkdownHelper
{
... lines 12 - 16
private $security;
public function __construct(AdapterInterface $cache, MarkdownInterface $markdown, LoggerInterface $markdownLogger, bool $isDebug, Security $security)
{
... lines 21 - 24
$this->security = $security;
}
... lines 27 - 48
}

So how can we use this service? Well... let's just look inside! Hold Command or Control and click to open the Security class. It has just two important methods: getUser() and isGranted(). Hey! That makes a lot of sense! Remember, once you set up authentication, there are only two things you can do with security: get the user object or figure out whether or not the user should have access to something, like a role. That's what isGranted() does.

Close that and move down to the log message. Ok, we could get the user object, maybe call getEmail() on it, and concatenate that onto the end of the log string. But! There's a cooler way. Add a 2nd argument to info: an array. Give it a user key - I'm just making that up - and set it to the user object: $this->security->getUser():

... lines 1 - 9
class MarkdownHelper
{
... lines 12 - 27
public function parse(string $source): string
{
if (stripos($source, 'bacon') !== false) {
$this->logger->info('They are talking about bacon again!', [
'user' => $this->security->getUser()
]);
}
... lines 35 - 47
}
}

Unrelated to security, every method on the logger, like info(), debug() or alert(), has two arguments. The first is the message string. The second is an optional array called a "context". This is just an array of any extra info that you want to include with the log message. I invented a user key and set it to the User object.

Let's go see what it looks like! Refresh! Then, click back into the profiler, find logs, and check out "Info & Errors". The message looks the same, but now we have a "Show Context" link. Click that! Nice! There is our entire User object in all of its glory. That's pretty sweet. And now, you know how to get the User object from anywhere.

Next, we get to talk about a feature called "role hierarchy". A little feature that will make you love working with roles, especially if you have complex access rules.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.2.0
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.1.4
        "symfony/console": "^4.0", // v4.1.4
        "symfony/flex": "^1.0", // v1.9.10
        "symfony/framework-bundle": "^4.0", // v4.1.4
        "symfony/lts": "^4@dev", // dev-master
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.1.4
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/twig-bundle": "^4.0", // v4.1.4
        "symfony/web-server-bundle": "^4.0", // v4.1.4
        "symfony/yaml": "^4.0", // v4.1.4
        "twig/extensions": "^1.5" // v1.5.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.1.4
        "symfony/dotenv": "^4.0", // v4.1.4
        "symfony/maker-bundle": "^1.0", // v1.7.0
        "symfony/monolog-bundle": "^3.0", // v3.3.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.1.4
    }
}