12
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravel ファサード、ヘルパーを使わないコントローラを作る

Last updated at Posted at 2022-12-18

Laravel Advent Calendar 2022 19日目の記事です。

ファサードやヘルパーをガッツリ使った方が実装速度は早いので通常の開発では採用しなくて良いと思います。

ファサードを利用しないメリット

  • クラスの依存関係がわかりやすくなる
  • テストがしやすくなる
  • 再利用性が高くなる

参考記事

Laravelのベースコントローラについて

Laravelフレームワークが用意している app/Http/Controllers/Controller.php を親クラスとして make:controller でコントローラを作っていきます。

app/Http/Controllers/Controller.php
<?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\ControllerIlluminate\Routing\Controller を継承してます。

vendor/laravel/framework/src/Illuminate/Routing/Controller.php
<?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 ファイルを上書きして作成します。

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 ファサード、ヘルパーを使わないコントローラの使い方

ログインのコントローラ

app/Http/Controllers/LoginController.php
<?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();
    }
}

ユーザー登録のコントローラ

app/Http/Controllers/SignUpController.php
<?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');
    }
}

SignUpRequestSignUpUseCase など個別のアクションで利用するクラスはメソッドインジェクションを利用してインスタンスを取得します。

さいごに

コピペで使えるものが欲しかったので参考例として作っておきます。
必要に応じて追加、削除してご利用ください。

ファサードやヘルパーを無効化する手順は参考記事やスライドを参照ください。

12
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?