Symfony Advent Calendar 2017 13日目の記事です。
枠が空いてたので埋めていこうという気持ちで頑張って書いてみました。
Symfonyのコントローラクラスでログイン中のユーザー情報を取得する場合に、よくこのようなコードを書いているかと思います。
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class UserController extends Controller
{
public function index()
{
$user = $this->getUser();
}
}
しかし僕はControllerクラスを継承したくない。
Contorllerを継承しないということは$this->getUser()
メソッドを使うことができない。
$this->getUser()はどのような実装になっているのか?
https://git.io/vbgwG
実装を見てみるとsecurity.token_storage
からtokenを取得してそのトークンからgetUser()をしているだけ。
コントローラを継承しない場合は以下のように実装すればユーザー情報が取得できる。
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
class UserController extends Controller
{
/**
* @var TokenStorage
*/
private $tokenStorage;
/**
* @param TokenStorage $tokenStorage
*/
public function __construct(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function index()
{
$uesr = null;
if (null !== $token = $this->tokenStorage->getToken()) {
$user = $token->getUser();
}
}
}
しかしTokenStorageをInjectしたり、tokenがnullかどうかの判定もしなければいけない・・・
正直これならControllerクラスを継承したほうがいいような・・・って気持ちになってしまう。
ArgumentValueResolverを使ってログインユーザーの情報を取得する
ArgumentResolverはコントローラのアクションの引数をみて、適切なオブジェクトを引数に渡してくれます。
例えば、以下のようなRequestオブジェクトを引数として受け取るコード。
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Request;
class UserController
{
public function index(Request $request)
{
$page = $request->query->getInt('page', 1);
}
}
Requestの場合はRequestValueResolverのおかげで、引数として受け取れるようです。
https://git.io/vbgrU
ArgumentValueResolverInterfaceをimplementsすれば、自分でカスタムArgumentResolverを用意することができます。
つまり以下のようにArgumentResolverを用意すればコントローラの引数でログインしているユーザーを取得できる。
<?php
// src/ArgumentResolver/UserValueResolver.php
namespace App\ArgumentResolver;
use App\Entity\User;
use Prophecy\Argument\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
final class UserValueResolver implements ArgumentValueResolverInterface
{
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function supports(Request $request, ArgumentMetadata $argument)
{
if (User::class !== $argument->getType()) {
return false;
}
$token = $this->tokenStorage->getToken();
if (!$token instanceof TokenInterface) {
return false;
}
return $token->getUser() instanceof User;
}
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $this->tokenStorage->getToken()->getUser();
}
}
<?php
namespace App\Controller
use Symfony\Component\HttpFoundation\Request;
use App\Entity\User;
class UserController
{
public function index(User $user)
{
return ['user' => $user];
}
}
このようにArgumentValueResolver
を利用すれば、どのコントローラでもアクションの引数としてログインしているユーザーの情報を取得できますね。
まあでもControllerクラスを継承したほうが楽ですよね・・・。
ただ、ログインユーザー以外でも使えるし、便利なので積極的に使っていきたい気持ちあります。