5
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.

CYBIRDAdvent Calendar 2022

Day 13

Laravel Actionsのご紹介

Last updated at Posted at 2022-12-13

こんにちは!

CYBIRD Advent Calendar 2022の13日目担当の@dave_cです。
前日は@whitemage_yuさんの「Discord用のbotを作ってみた」でした。それも是非ご覧ください!

はじめに

最近、個人的にもアクションクラスを幅広く使いましたので、この記事の形でLukeさんの話を要約するのに良いタイミングでした。

Actionsとは?

簡単に言うと、1つの責任しか持たない分割されたサービスクラスです。 つまり、通常、特定のタスクを実行するためのメソッドが1つだけ含まれており、再利用できます。 当然ですが、これはSeparation of Concerns (関心事の分離)の原則とうまく守る方法です。

うんうん、コードを見たいですね。それでは、例を見ていきましょう!

例: ユーザ登録

例えば、簡単なサインアップフォームがあるとしましょう。 通常、コントローラーでリクエストを受信し、データを検証してから、コーディングスタイルに応じて、モデルを直接呼び出すか、以下のようにユーザーをデータベースに保存するためのリポジトリクラスを呼び出します。

class RegisterController extends Controller
{
    public function store(RegisterRequest $request): RedirectResponse
    {
        $data = request->validated();
        
        $user = User::create([
            ...$data,
            'secret' => Hash::make($data['secret'])
        ]);
        
        $auth->login($user);
        
        return redirect(route('home'))->with('success', true);
    }
}

一般的なアプローチであり、問題なく動いています。新しい要件が入ってくるまで。例えば、コマンドラインからユーザーを登録する必要になるとしましょう。 普段、カスタムのArtisanコマンドを作成します。 そちらのクラスは以下のような例となります。

public function execute(): int
{
    $data = $this->validateData($this->getInput());
    
    $user = User::create([
        ...$data,
        'secret' => Hash::make($data['secret']);
    ]);
    
    $this->line("User {$user->email} has been registered.");
    
    return Command::SUCCESS;
}

protected function validateData(array $data): array
{
    return Validator::make($data, [
        'email' => ['required', 'email', 'unique:users'],
        'name' => ['required', 'string', 'max:255'],
        'secret' => ['required', 'min:6']
    ])->validate();
}

protected function getInput(): array
{
    return [
        'email' => $this->argument('email') ?? $this->ask("What is the user's name?"),
        'name' => $this->argument('email') ?? $this->ask("What is the user's email?"),
        'secret' => $this->argument('secret') ?? $this->secret("What is the user's secret?")
    ];
}

これだけを見ると、特に問題がありません。 ただし、重複したコードを導入しました。 そうすると、データ検証とユーザー作成の部分は、いくつかの場所で保守する必要がありますので、保守性の観点から見るとよろしくないです。

Actionクラスを登場する

エレガントな解決方法の1つは、アクションクラスを使用することです。 上の例にどのように適用されるかを見てみましょう。 そのために、app/Actionsの下にRegisterUser.phpというファイルを作成します。Actionsフォルダーが存在しない場合では、作成するが必要です。

class RegisterUser
{
    public function __invoke(array $data): User
    {
        $data = this->validateData($data);
        
        return User::create([
            ...$data,
            'secret' => Hash::make($data['secret']);
        ]);
     }
     
    protected function validateData(array $data): array
    {
        return Validator::make($data, [
            'email' => ['required', 'email', 'unique:users'],
            'name' => ['required', 'string', 'max:255'],
            'secret' => ['required', 'min:6']
        ])->validate();
    }
}

クラスの名前に注意してください。 動詞+名詞の形式です。 この形にすると、クラスの機能が理解しやすくなります。
ロジックを抽象化ができましたので、コントローラーも、Artisanコマンドも修正しましょう。
すると、コントローラは次のようになります。

class RegisterController extends Controller
{
    public function store(RegisterRequest $request, RegisterUser $registerUser): RedirectResponse
    {
        $user = $registerUser($request->all());
        
        $auth->login($user);
        
        return redirect(route('home'))->with('success', true);
    }
}

そしてArtisanコマンドクラス:

public function execute(RegisterUser $registerUser): int
{
    $user = $registerUser($this->getInput());
    
    $this->line("User {$user->email} has been registered.");
    
    return Command::SUCCESS;
}

protected function getInput(): array
{
    return [
        'name' => $this->argument('name') ?? $this->ask("What is the user's name?"),
        'email' => $this->argument('email') ?? $this->ask("What is the user's email?"),
        'secret' => $this->argument('secret') ?? $this->secret("What is the user's secret?")
    ];
}

これで修正が完了となります。 クリーンな抽象化であり、保守性の向上ができています。

最後に

この記事がお役に立てば幸いです。

CYBIRD Advent Calendar 2022の14日目は@cy-naullさんの「面倒な手作業をシェルスクリプトに任せる」です。
明日の記事でもお楽しみに!

5
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
5
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?