Ich möchte Sie auf das Konzept der Filterung nach URL-Anforderungen aufmerksam machen. Zum Beispiel werde ich die PHP-Sprache und das Laravel-Framework verwenden.
Konzept
Die Idee ist, eine universelle
QueryFilter- Klasse für die Arbeit mit Filtern zu erstellen.
GET /posts?title=source&status=active
In diesem Beispiel werden
Beiträge (Beitragsmodell) nach den folgenden Kriterien gefiltert:
- Das Vorhandensein des Wortes "Quelle" im Titelfeld;
- Der Wert "Publizieren" im Statusfeld ;
Anwendungsbeispiel
Beitragsmodell
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { protected $fillable = [ 'name', 'id', 'title', 'slug', 'status', 'type', 'published_at', 'updated_at', ]; }
Route hinzufügen:
Route::get('/posts', 'PostController@index');
Erstellen Sie eine
Resource \ Post- Datei für die Ausgabe im
JSON-Format :
namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class Post extends JsonResource { public function toArray($request) { return [ 'id' => $this->ID, 'title' => $this->post_title, 'slug' => $this->post_name, 'status' => $this->post_status, 'type' => $this->post_type, 'published_at' => $this->post_date, 'updated_at' => $this->post_modified, ]; } }
Und die Steuerung selbst mit einer einzigen Aktion:
namespace App\Http\Controllers; use App\Http\Resources\Post as PostResource; use App\Post; class PostController extends Controller { public function index() { $posts = Post::limit(10)->get(); return PostResource::collection($posts); } }
Die Standardfilterung ist nach folgendem Code organisiert:
public function index(Request $request) { $query = Post::limit(10); if ($request->filled('status')) { $query->where('post_status', $request->get('status')); } if ($request->filled('title')) { $title = $request->get('title'); $query->where('post_title', 'like', "%$title%"); } $posts = $query->get(); return PostResource::collection($posts); }
Bei diesem Ansatz sehen wir uns mit dem unerwünschten Wachstum des Controllers konfrontiert.
Implementieren von QueryFilter
Die Bedeutung eines solchen Konzepts besteht darin, für jede Entität eine eigene Klasse zu verwenden, die jedem Feld Methoden zum Filtern zuordnet.
Auf Anfrage filtern:
GET /posts?title=source&status=publish
Zum Filtern stehen die
PostFilter- Klasse sowie die Methoden
title () und
status () zur Verfügung.
PostFilter erweitert die abstrakte Klasse
QueryFiler, die für den Abgleich von Klassenmethoden mit übergebenen Parametern verantwortlich ist.
Methode anwenden ()Die
QueryFIlter- Klasse verfügt über eine
apply () -Methode, die für das
Aufrufen von Filtern verantwortlich ist, die sich in der
PostFilter-Kindklasse befinden .
namespace App\Http\Filters; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; abstract class QueryFilter { protected $request; protected $builder; public function __construct(Request $request) { $this->request = $request; } public function apply(Builder $builder) { $this->builder = $builder; foreach ($this->fields() as $field => $value) { $method = camel_case($field); if (method_exists($this, $method)) { call_user_func_array([$this, $method], (array)$value); } } } protected function fields(): array { return array_filter( array_map('trim', $this->request->all()) ); } }
Im Endeffekt haben wir für jedes Feld, das über
Request übergeben wird , eine separate Methode in der untergeordneten Filterklasse (PostFilter-Klasse). Auf diese Weise können wir die Logik für jedes Filterfeld anpassen.
PostFilter- KlasseNun wollen wir eine
PostFilter- Klasse
erstellen, die
QueryFilter erweitert . Wie bereits erwähnt, muss diese Klasse Methoden für jedes Feld enthalten, nach denen gefiltert werden muss. In unserem Fall die Methoden
title ($ value) und
status ($ value) namespace App\Http\Filters; use Illuminate\Database\Eloquent\Builder; class PostFilter extends QueryFilter { public function status(string $status) { $this->builder->where('post_status', strtolower($status)); } public function title(string $title) { $words = array_filter(explode(' ', $title)); $this->builder->where(function (Builder $query) use ($words) { foreach ($words as $word) { $query->where('post_title', 'like', "%$word%"); } }); } }
Hier sehe ich keinen Grund für eine detaillierte Analyse der einzelnen Methoden, ganz normale Abfragen. Der Punkt ist, dass wir jetzt für jedes Feld eine separate Methode haben und jede Logik verwenden können, die wir zum Bilden der Anfrage benötigen.
ScopeFilter () erstellenJetzt müssen wir den Modell- und Abfrage-Designer binden
public function scopeFilter(Builder $builder, QueryFilter $filter) { $filter->apply($builder); }
Um zu suchen, müssen wir die
filter () -Methode aufrufen und eine Instanz von
QueryFilter übergeben , in unserem Fall
PostFilter .
$filteredPosts = Post::filter($postFilter)->get();
Daher wird die gesamte
Filterlogik durch Aufrufen der
Filtermethode ($ postFilter) verarbeitet , wodurch der Controller vor unnötiger Logik
geschützt wird .
Um die Wiederverwendung zu vereinfachen, können Sie die
scopeFilter- Methode in das Merkmal
einfügen und für jedes Modell verwenden, das gefiltert werden muss.
namespace App\Http\Filters; use Illuminate\Database\Eloquent\Builder; trait Filterable { public function scopeFilter(Builder $builder, QueryFilter $filter) { $filter->apply($builder); } }
In
Post hinzufügen:
class Post extends CorcelPost { use Filterable;
Es bleibt übrig, der Controller-Methode
index () als
PostFilter- Parameter
hinzuzufügen und die
Modellmethode filter () aufzurufen.
class PostController extends Controller { public function index(PostFilter $filter) { $posts = Post::filter($filter)->limit(10)->get(); return PostResource::collection($posts); } }
Das ist alles. Wir haben die gesamte Filterlogik in die entsprechende Klasse verschoben und dabei das Prinzip der Einzelverantwortung beachtet (S im SOLID-Prinzipensystem).
Fazit
Dieser Ansatz zur Implementierung von Filtern ermöglicht es Ihnen, sich an den Trailer Thin Controller zu halten, und erleichtert auch das Schreiben von Tests.
Hier ist ein Beispiel mit PHP und Laravel. Aber wie gesagt, das ist ein Konzept und kann mit jeder Sprache oder jedem Framework funktionieren.
Referenzen