Laravel Advent Calendar 2022 19日目の記事です。
ファサードやヘルパーをガッツリ使った方が実装速度は早いので通常の開発では採用しなくて良いと思います。
ファサードを利用しないメリット
- クラスの依存関係がわかりやすくなる
- テストがしやすくなる
- 再利用性が高くなる
参考記事
Laravelのベースコントローラについて
Laravelフレームワークが用意している app/Http/Controllers/Controller.php
を親クラスとして make:controller
でコントローラを作っていきます。
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
- AuthorizesRequests 認可処理はフォームリクエストで行うからいらない
- DispatchesJobs キューを多くのコントローラで使わない。使うコントローラだけトレイト使えばいいのでは?
- ValidatesRequests 検証処理はフォームリクエストで行うからいらない
App\Http\Controllers\Controller は Illuminate\Routing\Controller を継承してます。
<?php
namespace Illuminate\Routing;
use BadMethodCallException;
abstract class Controller
{
/**
* The middleware registered on the controller.
*
* @var array
*/
protected $middleware = [];
/**
* Register middleware on the controller.
*
* @param \Closure|array|string $middleware
* @param array $options
* @return \Illuminate\Routing\ControllerMiddlewareOptions
*/
public function middleware($middleware, array $options = [])
{
foreach ((array) $middleware as $m) {
$this->middleware[] = [
'middleware' => $m,
'options' => &$options,
];
}
return new ControllerMiddlewareOptions($options);
}
/**
* Get the middleware assigned to the controller.
*
* @return array
*/
public function getMiddleware()
{
return $this->middleware;
}
/**
* Execute an action on the controller.
*
* @param string $method
* @param array $parameters
* @return \Symfony\Component\HttpFoundation\Response
*/
public function callAction($method, $parameters)
{
return $this->{$method}(...array_values($parameters));
}
/**
* Handle calls to missing methods on the controller.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.', static::class, $method
));
}
}
コントローラでミドルウェアを実行したくないし、callActionもなくても大丈夫です。
参考
Laravel ファサード、ヘルパーを使わないコントローラを作る
元々用意されている app/Http/Controllers/Controller.php
ファイルを上書きして作成します。
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Contracts\Auth\Factory as AuthFactory;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\Session\Session;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Contracts\View\Factory as ViewFactory;
use Illuminate\Routing\Redirector;
use Illuminate\Routing\ResponseFactory;
abstract class Controller
{
protected const SESSION_SUCCESS = 'success';
protected const SESSION_FAILURE = 'failure';
protected readonly StatefulGuard $auth;
public function __construct(
protected readonly LoggerInterface $logger,
protected readonly Session $session,
protected readonly Translator $translator,
protected readonly ViewFactory $viewFactory,
protected readonly ResponseFactory $responseFactory,
protected readonly Redirector $redirector,
AuthFactory $authFactory,
) {
$this->auth = $authFactory->guard('web');
}
}
利用したいインスタンスのみコンストラクタインジェクションで取得してます。
クラスの依存関係がわかりやすくなり、Laravelに明るくない人にも優しいと思います。
ロガー、セッション、翻訳、ビュー、レスポンス、リダイレクトレスポンス、認証はよく利用すると思うので例として挙げました。
Illuminate\Contracts\Auth\StatefulGuard
についてはこのインターフェースはコンストラクタインジェクションで注入してくれません。
サービスプロバイダーで注入させても良いですが、システムによっては複数の認証方法があるかもしれないのでベースコントローラで選択するのが良さそうです。
Laravel ファサード、ヘルパーを使わないコントローラの使い方
ログインのコントローラ
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Request\Auth\LoginRequest;
use Illuminate\Http\RedirectResponse;
final class LoginController extends Controller
{
public function __invoke(LoginRequest $request): RedirectResponse
{
$credentials = [
'email' => (string) $request->input('email'),
'password' => (string) $request->input('password'),
];
if ($this->auth->attempt($credentials, (bool) $request->input('remember'))) {
$this->session->regenerate();
return $this->redirector->route('dashboard');
}
return $this->redirector->back();
}
}
ユーザー登録のコントローラ
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Request\Register\SignUpRequest;
use Illuminate\Http\RedirectResponse;
use Throwable;
use Todo\Application\Auth\SignUpUseCase;
use Todo\Application\Auth\SignUpUseCaseInput;
final class SignUpController extends Controller
{
public function __invoke(SignUpRequest $request, SignUpUseCase $useCase): RedirectResponse
{
$input = new SignUpUseCaseInput(
(string) $request->input('name'),
(string) $request->input('email'),
(string) $request->input('password'),
);
try {
$output = $useCase->signUp($input);
} catch (Throwable $exception) {
return $this->redirector
->back()
->withErrors([self::SESSION_FAILURE => $exception->getMessage()]);
}
$this->auth->loginUsingId($output->userId);
$this->session->regenerateToken();
$this->session->put(self::SESSION_SUCCESS, $this->translator->get('app.signUp.completed'));
$this->logger->info('Completed sign up process. userId: ' . $output->userId);
return $this->redirector->route('dashboard');
}
}
SignUpRequest
やSignUpUseCase
など個別のアクションで利用するクラスはメソッドインジェクションを利用してインスタンスを取得します。
さいごに
コピペで使えるものが欲しかったので参考例として作っておきます。
必要に応じて追加、削除してご利用ください。
ファサードやヘルパーを無効化する手順は参考記事やスライドを参照ください。