Je souhaite attirer votre attention sur le concept d'organisation du filtrage par demande d'URL. Par exemple, j'utiliserai le langage PHP et le framework Laravel.
Concept
L'idée est de créer une classe
QueryFilter universelle pour travailler avec des filtres.
GET /posts?title=source&status=active
En utilisant cet exemple, nous filtrerons les
publications (modèle de publication) selon les critères suivants:
- La présence du mot "source" dans le champ titre ;
- La valeur "publier" dans le champ d' état ;
Exemple d'application
Modèle de
poste <?php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { protected $fillable = [ 'name', 'id', 'title', 'slug', 'status', 'type', 'published_at', 'updated_at', ]; }
Ajouter un itinéraire:
Route::get('/posts', 'PostController@index');
Créez un fichier
Resource \ Post pour la sortie au
format JSON :
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, ]; } }
Et le contrôleur lui-même avec une seule action:
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); } }
Le filtrage standard est organisé par le code suivant:
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); }
Avec cette approche, nous sommes confrontés à la croissance du contrôleur, ce qui n'est pas souhaitable.
Implémentation de QueryFilter
La signification d'un tel concept est d'utiliser une classe distincte pour chaque entité qui mappe les méthodes à chaque champ pour le filtrage.
Filtrer par demande:
GET /posts?title=source&status=publish
Pour le filtrage, nous aurons la classe
PostFilter et les méthodes
title () et
status () .
PostFilter étendra la classe abstraite
QueryFiler qui est responsable de la correspondance des méthodes de classe avec les paramètres passés.
Méthode applicable ()La classe
QueryFIlter possède une méthode
apply () , qui est chargée d'
appeler les filtres qui se trouvent dans la classe enfant
PostFilter .
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()) ); } }
L'essentiel est que pour chaque champ passé par
Request, nous avons une méthode distincte dans la classe de filtre enfant (classe PostFilter). Cela nous permet de personnaliser la logique de chaque champ de filtre.
Classe PostFilterPassons maintenant à la création d'une classe
PostFilter qui étend
QueryFilter . Comme mentionné précédemment, cette classe doit contenir des méthodes pour chaque champ par lequel nous devons filtrer. Dans notre cas, les méthodes
title ($ value) et
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%"); } }); } }
Ici, je ne vois aucune raison pour une analyse détaillée de chacune des méthodes, des requêtes assez standard. Le fait est que nous avons maintenant une méthode distincte pour chaque champ et que nous pouvons utiliser toute logique dont nous avons besoin pour former la demande.
Créer scopeFilter ()Maintenant, nous devons lier le concepteur de modèle et de requête
public function scopeFilter(Builder $builder, QueryFilter $filter) { $filter->apply($builder); }
Pour effectuer une recherche, nous devons appeler la méthode
filter () et passer une instance de
QueryFilter , dans notre cas
PostFilter .
$filteredPosts = Post::filter($postFilter)->get();
Ainsi, toute la logique de filtrage est traitée en appelant la méthode de
filtrage ($ postFilter) , sauvant le contrôleur d'une logique inutile.
Pour faciliter la réutilisation, vous pouvez mettre la méthode
scopeFilter dans le trait et l'utiliser pour chaque modèle qui doit être filtré.
namespace App\Http\Filters; use Illuminate\Database\Eloquent\Builder; trait Filterable { public function scopeFilter(Builder $builder, QueryFilter $filter) { $filter->apply($builder); } }
Dans la
publication, ajoutez:
class Post extends CorcelPost { use Filterable;
Il reste à ajouter
index () à la méthode du contrôleur en tant
que paramètre
PostFilter et appeler la méthode du modèle
filter () .
class PostController extends Controller { public function index(PostFilter $filter) { $posts = Post::filter($filter)->limit(10)->get(); return PostResource::collection($posts); } }
C’est tout. Nous avons déplacé toute la logique de filtrage vers la classe appropriée, en respectant le principe de responsabilité unique (S dans le système de principes SOLID)
Conclusion
Cette approche de la mise en œuvre des filtres vous permet de vous en tenir au contrôleur fin de remorque, et facilite également la rédaction des tests.
Voici un exemple utilisant PHP et Laravel. Mais comme je l'ai dit, c'est un concept qui peut fonctionner avec n'importe quel langage ou framework.
Les références