ホームページ  >  記事  >  PHPフレームワーク  >  Laravelクエリフィルターを実装する方法を段階的に説明します。

Laravelクエリフィルターを実装する方法を段階的に説明します。

青灯夜游
青灯夜游転載
2023-01-18 21:10:491331ブラウズ

Laravelクエリフィルターを実装する方法を段階的に説明します。

クエリ フィルター... システム開発時によくある問題。しかし、コードを書き始めると、「このクエリ ロジックをどこに配置すればよいでしょうか? 使いやすくするためにはどのように管理すればよいですか?」という、すべての開発者にとってよくある質問が数多く発生します。正直に言うと、私は開発するプロジェクトごとに、以前に作成したプロジェクトの経験に基づいて、異なるスタイルで執筆しています。そして、新しいプロジェクトを開始するたびに、今度は同じ質問を自分自身に問いかけます。クエリ フィルターをどのように配置すればよいでしょうか。この記事は、対応する問題を伴うクエリ フィルタリング システムの段階的な開発と考えることができます。

コンテキスト

この記事を書いている時点では、私は PHP 8.1 および MySQL 8 で Laravel 9 を使用しています。テクノロジースタックは大きな問題ではないと思います。ここでは主にクエリフィルターシステムの構築に焦点を当てます。この記事では、users テーブルのフィルターを作成する方法を説明します。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * 运行迁移
     *
     * @return void
     */
    public function up()
    {
        Schema::create(&#39;users&#39;, function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->string('gender', 10)->nullable()->index();
            $table->boolean('is_active')->default(true)->index();
            $table->boolean('is_admin')->default(false)->index();
            $table->timestamp('birthday')->nullable();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * 回退迁移
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

さらに、クエリを簡単に監視するために Laravel Telescope を使用しています。

最初のポイント

Laravel の使い方を学び始めた最初の日、私はコントローラー上でフィルターを直接呼び出しました。シンプルで魔法がなく、理解しやすいですが、このアプローチには問題があります。

  • コントローラーに大量のロジックが配置されると、コントローラーが肥大化します。
  • 再利用できません
  • 多くの同じ作業の繰り返し
  • ##
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function __invoke(Request $request)
    {
        // /users?name=ryder&email=hartman&gender=male&is_active=1&is_admin=0&birthday=2014-11-30
        $query = User::query();

        if ($request->has('name')) {
            $query->where('name', 'like', "%{$request->input('name')}%");
        }

        if ($request->has('email')) {
            $query->where('email', 'like', "%{$request->input('email')}%");
        }

        if ($request->has('gender')) {
            $query->where('gender', $request->input('gender'));
        }

        if ($request->has('is_active')) {
            $query->where('is_active', $request->input('is_active') ? 1 : 0);
        }

        if ($request->has('is_admin')) {
            $query->where('is_admin', $request->input('is_admin') ? 1 : 0);
        }

        if ($request->has('birthday')) {
            $query->whereDate('birthday', $request->input('birthday'));
        }

        return $query->paginate();

        // select * from `users` where `name` like '%ryder%' and `email` like '%hartman%' and `gender` = 'male' and `is_active` = 1 and `is_admin` = 0 and date(`birthday`) = '2014-11-30' limit 15 offset 0
    }
}
#ローカル スコープの使用

##フィルタリング中にロジックを非表示にできるようにするために、Laravel のローカルスコープ。クエリを User モデルの関数スコープに変換します。
// User.php
public function scopeName(Builder $query): Builder
{
    if (request()->has('name')) {
        $query->where('name', 'like', "%" . request()->input('name') . "%");
    }
    return $query;
}

public function scopeEmail(Builder $query): Builder
{
    if (request()->has('email')) {
        $query->where('email', 'like', "%" . request()->input('email') . "%");
    }
    return $query;
}

public function scopeGender(Builder $query): Builder
{
    if (request()->has('gender')) {
        $query->where('gender', request()->input('gender'));
    }
    return $query;
}

public function scopeIsActive(Builder $query): Builder
{
    if (request()->has('is_active')) {
        $query->where('is_active', request()->input('is_active') ? 1 : 0);
    }
    return $query;
}

public function scopeIsAdmin(Builder $query): Builder
{
    if (request()->has('is_admin')) {
        $query->where('is_admin', request()->input('is_admin') ? 1 : 0);
    }
    return $query;
}

public function scopeBirthday(Builder $query): Builder
{
    if (request()->has('birthday')) {
        $query->where('birthday', request()->input('birthday'));
    }
    return $query;
}

// UserController.php
public function __invoke(Request $request)
{
    // /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11

     $query = User::query()
             ->name()
            ->email()
            ->gender()
            ->isActive()
            ->isAdmin()
            ->birthday();

    return $query->paginate();

    // select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}
この設定では、ほとんどのデータベース操作をモデル クラスに移動しますが、多くのコードが重複しています。例 2 には、同じ名前と電子メール範囲フィルター、同じ性別の誕生日、および is_active/is_admin グループがあります。類似したクエリ関数をグループ化します。

// User.php
public function scopeRelativeFilter(Builder $query, $inputName): Builder
{
    if (request()->has($inputName)) {
        $query->where($inputName, 'like', "%" . request()->input($inputName) . "%");
    }
    return $query;
}

public function scopeExactFilter(Builder $query, $inputName): Builder
{
    if (request()->has($inputName)) {
        $query->where($inputName, request()->input($inputName));
    }
    return $query;
}

public function scopeBooleanFilter(Builder $query, $inputName): Builder
{
    if (request()->has($inputName)) {
        $query->where($inputName, request()->input($inputName) ? 1 : 0);
    }
    return $query;
}

// UserController.php
public function __invoke(Request $request)
{
    // /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11

    $query = User::query()
        ->relativeFilter('name')
        ->relativeFilter('email')
        ->exactFilter('gender')
        ->booleanFilter('is_active')
        ->booleanFilter('is_admin')
        ->exactFilter('birthday');

    return $query->paginate();

    // select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}

この時点で、ほとんどの重複がグループ化されました。ただし、if ステートメントを削除したり、これらのフィルターを別のモデルに拡張したりするのは少し困難です。私たちはこの問題を完全に解決する方法を探しています。

パイプライン設計パターンの使用

パイプライン設計パターンは、一連の操作ステップを構築および実行する機能を提供する設計パターンです。一歩ずつ。 Laravel には、この設計パターンを実際に簡単に適用できるようにするパイプラインが組み込まれていますが、何らかの理由で公式ドキュメントには記載されていません。 Laravel 自体も、リクエストとレスポンスの間のミドルウェアとしてパイプラインを使用しています。最も基本的なところでは、Laravel でパイプラインを使用するには、

app(\Illuminate\Pipeline\Pipeline::class)
    ->send($intialData)
    ->through($pipes)
    ->thenReturn(); // data with pipes applied

を使用できます。この問題では、最初のクエリ User:query() をフィルター ステップを介してパイプラインに渡し、適用するために戻すことができます。フィルタークエリビルダー。

// UserController
public function __invoke(Request $request)
{
    // /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11

    $query = app(Pipeline::class)
        ->send(User::query())
        ->through([
            // filters
        ])
        ->thenReturn();

    return $query->paginate();
次に、パイプライン フィルターを構築する必要があります。

// File: app/Models/Pipes/RelativeFilter.php

<?php

namespace App\Models\Pipes;

use Illuminate\Database\Eloquent\Builder;

class RelativeFilter
{
    public function __construct(protected string $inputName)
    {
    }

    public function handle(Builder $query, \Closure $next)
    {
        if (request()->has($this->inputName)) {
            $query->where($this->inputName, 'like', "%" . request()->input($this->inputName) . "%");
        }
        return $next($query);
    }
}

// File: app/Models/Pipes/ExactFilter.php

<?php

namespace App\Models\Pipes;

use Illuminate\Database\Eloquent\Builder;

class ExactFilter
{
    public function __construct(protected string $inputName)
    {
    }

    public function handle(Builder $query, \Closure $next)
    {
        if (request()->has($this->inputName)) {
            $query->where($this->inputName, request()->input($this->inputName));
        }
        return $next($query);
    }
}

//File: app/Models/Pipes/BooleanFilter.php
<?php

namespace App\Models\Pipes;

use Illuminate\Database\Eloquent\Builder;

class BooleanFilter
{
    public function __construct(protected string $inputName)
    {
    }

    public function handle(Builder $query, \Closure $next)
    {
        if (request()->has($this->inputName)) {
            $query->where($this->inputName, request()->input($this->inputName) ? 1 : 0);
        }
        return $next($query);
    }
}

// UserController
public function __invoke(Request $request)
{
    // /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11

    $query = app(Pipeline::class)
        ->send(User::query())
        ->through([
            new \App\Models\Pipes\RelativeFilter('name'),
            new \App\Models\Pipes\RelativeFilter('email'),
            new \App\Models\Pipes\ExactFilter('gender'),
            new \App\Models\Pipes\BooleanFilter('is_active'),
            new \App\Models\Pipes\BooleanFilter('is_admin'),
            new \App\Models\Pipes\ExactFilter('birthday'),
        ])
        ->thenReturn();

    return $query->paginate();

    // select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}

各クエリ ロジックを別のクラスに移動することで、多態性、継承、カプセル化、抽象化など、OOP を使用したカスタマイズの可能性が解放されます。たとえば、パイプラインのハンドル関数では、if ステートメントのロジックだけが異なることがわかります。抽象クラス BaseFilter を作成してそれを分離し、抽象化します。

//File: app/Models/Pipes/BaseFilter.php

<?php

namespace App\Models\Pipes;

use Illuminate\Database\Eloquent\Builder;

abstract class BaseFilter
{
    public function __construct(protected string $inputName)
    {
    }

    public function handle(Builder $query, \Closure $next)
    {
        if (request()->has($this->inputName)) {
            $query = $this->apply($query);
        }
        return $next($query);
    }

    abstract protected function apply(Builder $query): Builder;
}

// BooleanFilter
class BooleanFilter extends BaseFilter
{
    protected function apply(Builder $query): Builder
    {
        return $query->where($this->inputName, request()->input($this->inputName) ? 1 : 0);
    }
}

// ExactFilter
class ExactFilter extends BaseFilter
{
    protected function apply(Builder $query): Builder
    {
        return $query->where($this->inputName, request()->input($this->inputName));
    }
}

// RelativeFilter
class RelativeFilter extends BaseFilter
{
    protected function apply(Builder $query): Builder
    {
        return $query->where($this->inputName, 'like', "%" . request()->input($this->inputName) . "%");
    }
}

これで、フィルタは直感的で高度な機能になりました。再利用可能で、実装や拡張も簡単です。パイプラインを作成し、BaseFilter を拡張し、関数 apply を宣言して Pipeline に適用するだけです。

ローカル スコープとパイプラインの結合

この時点で、パイプラインを呼び出すモデル内にスコープを作成して、コントローラー上のパイプラインを非表示にします。簡潔。
// User.php
public function scopeFilter(Builder $query)
{
    $criteria = $this->filterCriteria();
    return app(\Illuminate\Pipeline\Pipeline::class)
        ->send($query)
        ->through($criteria)
        ->thenReturn();
}

public function filterCriteria(): array
{
    return [
        new \App\Models\Pipes\RelativeFilter('name'),
        new \App\Models\Pipes\RelativeFilter('email'),
        new \App\Models\Pipes\ExactFilter('gender'),
        new \App\Models\Pipes\BooleanFilter('is_active'),
        new \App\Models\Pipes\BooleanFilter('is_admin'),
        new \App\Models\Pipes\ExactFilter('birthday'),
    ];
}

// UserController.php
public function __invoke(Request $request)
{
    // /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11

    return User::query()
        ->filter()
        ->paginate()
        ->appends($request->query()); // 将所有当前查询附加到分页链接中

    // select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}
ユーザーはどこからでもフィルターを呼び出すことができるようになりました。ただし、他のモデルでもフィルタリングを実装する必要があるため、スコープを含むトレイトを作成し、モデル内のフィルタリング プロセスに関与するパイプラインを宣言します。

// User.php

use App\Models\Concerns\Filterable;

class User extends Authenticatable {
        use Filterable;

        protected function getFilters()
        {
            return [
                new \App\Models\Pipes\RelativeFilter('name'),
                new \App\Models\Pipes\RelativeFilter('email'),
                new \App\Models\Pipes\ExactFilter('gender'),
                new \App\Models\Pipes\BooleanFilter('is_active'),
                new \App\Models\Pipes\BooleanFilter('is_admin'),
                new \App\Models\Pipes\ExactFilter('birthday'),
            ];
        }

        // 其余代码

// File: app/Models/Concerns/Filterable.php

namespace App\Models\Concerns;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pipeline\Pipeline;

trait Filterable
{
    public function scopeFilter(Builder $query)
    {
        $criteria = $this->filterCriteria();
        return app(Pipeline::class)
            ->send($query)
            ->through($criteria)
            ->thenReturn();
    }

    public function filterCriteria(): array
    {
        if (method_exists($this, 'getFilters')) {
            return $this->getFilters();
        }

        return [];
    }
}

私たちは分割統治問題を解決し、すべてのファイル、すべてのクラス、すべての関数が明確な責任を持つようになりました。コードもすっきりしていて直感的で、再利用しやすいですよね。この投稿デモのプロセス全体のコードをここに置きます。

結論

上記は、高度なクエリ フィルター システムを構築する私の方法の一部であり、ローカル スコープなどのいくつかの Laravel プログラミング手法も紹介しています。パイプライン設計パターン。このセットアップを新しいプロジェクトに迅速かつ簡単に適用するには、パイプライン クエリ コレクション パッケージを使用できます。このパッケージには、事前に構築されたパイプラインのセットが含まれており、インストールと使用が簡単になります。皆さんも応援していただければ幸いです!

元のアドレス: https://baro.rezonia.com/blog/building-a-sexy-query-filter

翻訳アドレス: https://learnku.com/ laravel/t/68762

プログラミング関連の知識については、

プログラミング ビデオ

をご覧ください。 !

以上がLaravelクエリフィルターを実装する方法を段階的に説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlearnku.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。