Heim  >  Artikel  >  PHP-Framework  >  Bringen Sie Ihnen Schritt für Schritt bei, wie Sie einen Laravel-Abfragefilter implementieren

Bringen Sie Ihnen Schritt für Schritt bei, wie Sie einen Laravel-Abfragefilter implementieren

青灯夜游
青灯夜游nach vorne
2023-01-18 21:10:491339Durchsuche

Bringen Sie Ihnen Schritt für Schritt bei, wie Sie einen Laravel-Abfragefilter implementieren

Abfragefilter... Häufige Fragen bei der Entwicklung von Systemen. Aber wenn man anfängt, Code zu schreiben, stellen sich für jeden Entwickler viele bekannte Fragen: „Wo soll ich diese Abfragelogik platzieren? Wie soll ich sie verwalten, um die Verwendung zu vereinfachen?“ Ehrlich gesagt schreibe ich für jedes Projekt, das ich entwickle, in einem anderen Stil, basierend auf meinen Erfahrungen mit früheren Projekten, die ich erstellt habe. Und jedes Mal, wenn ich ein neues Projekt starte, stelle ich mir dieses Mal die gleiche Frage: Wie ordne ich Abfragefilter an? Dieser Artikel kann als schrittweise Entwicklung eines Abfragefiltersystems mit entsprechenden Problemen betrachtet werden.

Context

Zum Zeitpunkt des Schreibens dieses Artikels verwende ich Laravel 9 auf PHP 8.1 und MySQL 8. Ich glaube, dass der Technologie-Stack kein großes Problem darstellt. Hier konzentrieren wir uns hauptsächlich auf den Aufbau eines Abfragefiltersystems. In diesem Artikel werde ich den Aufbau eines Filters für die Benutzertabelle demonstrieren.

<?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');
    }
}

Darüber hinaus verwende ich Laravel Telescope, um Abfragen einfach zu überwachen.

Ausgangspunkt

An meinen ersten Tagen, an denen ich die Verwendung von Laravel erlernte, rief ich oft Filter direkt auf dem Controller auf. Einfach, keine Zauberei, leicht zu verstehen, aber es gibt Probleme mit dieser Methode:

  • Eine Menge Logik im Controller führt dazu, dass der Controller aufbläht
  • Kann nicht wiederverwendet werden
  • Viele der gleichen Arbeiten werden wiederholt
<?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
    }
}

Local Scope verwenden

Um die Logik beim Filtern ausblenden zu können, versuchen wir es mit Laravel’s Local Scope. Konvertieren Sie die Abfrage in einen Funktionsumfang im Benutzermodell:

// 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
}

Mit diesem Setup haben wir die meisten Datenbankoperationen in die Modellklasse verschoben, es gab jedoch viele Codeduplizierungen. Beispiel 2 hat den gleichen Namen und E-Mail-Bereichsfilter, das gleiche Geschlecht am Geburtstag und die Gruppe „is_active/is_admin“. Wir werden ähnliche Abfragefunktionen gruppieren.

// 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
}

An dieser Stelle haben wir die meisten Duplikate gruppiert. Allerdings ist es etwas schwierig, die if-Anweisungen zu entfernen oder diese Filter auf ein anderes Modell zu erweitern. Wir suchen nach einer Möglichkeit, dieses Problem vollständig zu lösen.

Verwendung des Pipeline-Entwurfsmusters

Das Pipeline-Entwurfsmuster ist ein Entwurfsmuster, das die Möglichkeit bietet, eine Abfolge von Vorgängen Schritt für Schritt zu erstellen und auszuführen. Laravel verfügt über eine integrierte Pipeline, die es uns erleichtert, dieses Entwurfsmuster in der Praxis anzuwenden, aber aus irgendeinem Grund ist es nicht in der offiziellen Dokumentation aufgeführt. Auch Laravel selbst nutzt Pipelines als Middleware zwischen Anfragen und Antworten. Im einfachsten Fall können wir zur Verwendung von Pipeline in Laravel

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

verwenden. Für unser Problem können wir die anfängliche Abfrage User:query() über den Filterschritt an die Pipeline übergeben und den Abfrage-Builder zurückgeben, der den Filter anwendet .

// 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();

Jetzt müssen wir den Pipeline-Filter erstellen:

// 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
}

Indem wir jede Abfragelogik in eine separate Klasse verschieben, erschließen wir Anpassungsmöglichkeiten mithilfe von OOP, einschließlich Polymorphismus, Vererbung, Kapselung und Abstraktion. Sie können beispielsweise in der Handle-Funktion der Pipeline sehen, dass nur die Logik in der if-Anweisung unterschiedlich ist. Ich werde sie trennen und abstrahieren, indem ich eine abstrakte Klasse BaseFilter erstelle Um es zu implementieren und sogar zu erweitern, erstellen Sie einfach eine Pipeline, erweitern Sie BaseFilter und deklarieren Sie die Funktion apply, um sie auf die Pipeline anzuwenden.

Lokalen Bereich mit Pipeline kombinieren

An dieser Stelle werden wir versuchen, die Pipeline auf dem Controller auszublenden, um unseren Code sauberer zu machen, indem wir im Modell einen Bereich erstellen, der die Pipeline aufruft.

//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) . "%");
    }
}

Benutzer können jetzt von überall aus Filter aufrufen. Aber auch andere Modelle möchten Filterung implementieren. Wir erstellen ein Trait, das den Bereich enthält, und deklarieren die Pipeline, die am Filterprozess innerhalb des Modells teilnimmt.

// 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
}

Wir haben das Teilen-und-herrschen-Problem gelöst, jede Datei, jede Klasse, jede Funktion hat jetzt eine klare Verantwortung. Der Code ist auch sauber, intuitiv und einfacher wiederzuverwenden, nicht wahr? Ich habe den Code des gesamten Prozesses dieser Beitragsdemo hier eingefügt.

Fazit

Das Obige ist Teil meiner Methode zum Aufbau eines erweiterten Abfragefiltersystems und führt Sie auch in einige Laravel-Programmiermethoden ein, wie z. B. Local Scope und insbesondere das Pipeline-Entwurfsmuster. Um dieses Setup schnell und einfach auf neue Projekte anzuwenden, können Sie das Paket Pipeline Query Collection verwenden, das eine Reihe vorgefertigter Pipelines enthält, die sich einfach installieren und verwenden lassen. Ich hoffe, dass Sie mich alle unterstützen werden!

Originaladresse: https://baro.rezonia.com/blog/building-a-sexy-query-filter

Übersetzungsadresse: https://learnku.com/laravel/t/68762

Mehr für Programmierkenntnisse finden Sie unter:
Programmiervideo

! !

Das obige ist der detaillierte Inhalt vonBringen Sie Ihnen Schritt für Schritt bei, wie Sie einen Laravel-Abfragefilter implementieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:learnku.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen