[Laravel]LaravelのPipelineを理解しよう

Laravel Laravel

長くなるメソッドをスッキリさせることができる機能なのですが、ほとんどのエンジニアはLaravelのPipelineを知らないと思います。

LaravelのPipelineは公式ドキュメント日本語版)にあるのですが、ほとんど記載がありません。

そこで、この記事はLaravelのPipelineについて解説します。

Pipelineとはどんな機能なの?

ドキュメントには以下のように記載されています。

LaravelのPipelineファサードは、指定する入力を一連の呼び出し可能なクラス、クロージャ、Callableを通して、「パイプ」接続する便利な方法を提供し、各クラスに入力を検査または修正する機会を与え、パイプラインの次のCallableを呼び出します。

Laravel 10.x 日本語訳ドキュメント

Pipelineに任意のものを入れて、それに対してクラスなどで何かをするもののようです。

難しい説明は置いておいて、使い方と実装例を見てみましょう。

基本的な使い方

APIリファレンスには色々メソッドがありますが、以下が基本形となります。

Pipeline::send(/*.パイプを通過させたいオブジェクト */)
    ->through(/* 処理させたいメソッドの配列 */)
    ->then(/* 最終処理 */);
PHP

それぞれの引数は以下の通り

パイプを通過させたいオブジェクト

処理させたい対象のオブジェクト。

文字列だったりQueryBuilderだったり、何でも良いです。

処理させたいメソッドの配列

上記のオブジェクトに対して処理を行う内容のメソッドです。

詳しくは後述します。

最終処理

単純に言えばfinallyです。returnで値を返せば、それが返ります。特に処理をしないのであれば、then()の代わりに、thenReturn()というメソッドを使います。

実装例

文字列を小文字にして、最後に特定の文字を追加する例を作ってみましょう。

実装

まず、artisanコマンドを利用して確認するのが楽そうなので、作っていきます。

php artisan make:command ExammplePipeline
Bash

app/Console/Commands/ExamplePipeline.phpにファイルが作成されるので、以下のように変更します。

app/Console/Commands/ExamplePipeline
+use App\Pipes\AddString;
+use App\Pipes\LowerCase;
+use Illuminate\Support\Facades\Pipeline;

-    protected $signature = 'app:example-pipeline';
+    protected $signature = 'example:pipeline';

    public function handle()
    {
-        //
+        $string = 'ABCDE';
+        $string = Pipeline::send($string)
+            ->through([
+                LowerCase::class,
+                new AddString('HOGE'),
+            ])
+            ->thenReturn();
+        var_dump($string);
    }
Diff

次にパイプクラスを格納するためのディレクトリを作成します。

mkdir App/Pipes
Diff

パイプクラスを作成していきます。

app/Pipes/LowerCase.php
<?php
namespace App\Pipes;

class LowerCase
{
    public function handle($string, $next)
    {
        $string = strtolower($string);
        return $next($string);
    }
}
PHP
app/Pipes/AddString.php
<?php
namespace App\Pipes;

class AddString
{
    private $string = '';

    public function __construct($string)
    {
        $this->string = $string;
    }

    public function handle($string, $next)
    {
        $string .= $this->string;
        return $next($string);
    }
}
PHP

実行

作成したものを実行してみましょう。

php artisan example:pipeline
Bash

実行すると以下のようになると思います。

string(9) "abcdeHOGE"
Bash

ここまでで何となく使い方がわかった方も多いのではないでしょうか。

一応、解説をしてみます。

解説

send()

パイプを通過さて色々と処理をしたいものと引数にします。今回は文字列「ABCDE」としました。

through()

send()で指定したものを処理したいクラスを配列にて指定しました。

指定した配列の通りに順次処理されていきます。

今回は小文字にするLowerCaseクラスと、文字列を追加するAddStringクラスを指定しました。

AddStringは引数が必要なため今回はnewしてオブジェクト化したものを渡しています。

LowerCaseクラス

strtolowerをするだけの処理です。

handle()が処理する本体で、第一引数はsend()で指定したオブジェクト(加工済み)、第二引数は次に渡すためのメソッドです。

AddStringクラス

文字列の最後に指定した文字列を追加するだけの処理です。

through()にクラスを渡してしまうと引数が使えないので、オブジェクト化して渡すようにコンストラクタを追加しています。

引数をhandle()で利用できるように、privateのプロパティに格納しています。

応用

パイプは上から順に実行されます。

なので、順序を変更した場合、結果が変わります。

app/Console/Commands/ExamplePipeline
-                LowerCase::class,
-                new AddString('HOGE'),
+                new AddString('HOGE'),
+                LowerCase::class,
Bash

上記の変更をした場合、結果は以下のようになります。

string(9) "abcdehoge"
Bash

最後の「HOGE」が小文字になりました。

AddString()の後に、LowerCase()が実行されたので、追加された文字列を含めて全部小文字になりました。

実装例2

リクエストパラメータの特定の値によって条件を付与したりする場合もコードをスッキリさせることができます。

以下の例では、リクエストでgenderの指定があった場合にQueryBuilderに条件をつけて抽出するコードです。

class ExampleController
{
    public function index(Request $request)
    {
        $builder = User::query();
        if ($request->get('gender')) {
            $builder->where('gender', $request->get('gender'));
        }
        return $builder->get();
    }
}
PHP

上記は、以下のようにすることができます。

use App\Pipes\ConditionGender;
use Illuminate\Support\Facades\Pipeline;

class ExampleController
{
    public function index(Request $request)
    {
        $builder = User::query();
        $builder = Pipeline::send($builder)
            ->through([
                ConditionGender::class,
            ])
            ->thenReturn();
        return $builder->get();
    }
}
PHP
<?php
namespace App\Pipes;

class ConditionGender
{
    private $request;

    public function __construct($request)
    {
        $this->request = $request;
    }

    public function handle($builder, $next)
    {
        if ($this->request->get('gender')) {
            $builder->where('gender', $this->request->get('gender'));
        }
        return $next($builder);
    }
}
PHP

上記の例ではifは1つなのでコードが増えただけに見えますが、今回のgenderだけではなく、多数の条件があった場合にif文が多数並ぶことになり、コントローラーが肥大化してしまう原因になってしまいます。

まとめ

Pipelineを使うと、変換や条件などが見やすくスッキリしますし、ほぼ同条件の場合にも再利用ができ、コードがスッキリします。。

必ず利用しなければならないor絶対利用した方が良いというものではありませんが、活用できるケースもあると思いますので、Pipelineの利用を検討してみてはいかがでしょうか。

コメント

タイトルとURLをコピーしました