Если вы видите это, значит, я еще не придумал, что написать.
Паттерн “Фильтр” (Filter Pattern) — это архитектурный шаблон, который помогает отделить логику фильтрации данных от контроллеров и моделей. В Laravel этот паттерн часто используется для построения динамических запросов к базе данных на основе входящих данных из HTTP-запросов. Это позволяет сделать код более чистым, поддерживаемым и легко расширяемым.
Рассмотрим пример использования паттерна “Фильтр” на модели Product. Мы создадим фильтр для поиска продуктов по различным критериям, таким как название, категория, цена и наличие на складе.
Создадим базовый класс Filter и конкретный фильтр для продуктов.
<?php
declare(strict_types=1);
namespace App\Core\Filters;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
/**
* Class Filter
*/
abstract class Filter
{
public const KEYS_TO_BOOL = [];
public const KEYS_TO_INT = [];
public const KEYS_TO_DATE = [];
public const KEYS_STRING_TO_ARRAY = [];
public const KEYS_TO_ARRAY = [];
protected Builder $builder;
/**
* @param FormRequest $request
*/
public function __construct(protected readonly FormRequest $request)
{
}
/**
* Применение фильтров к запросу
*
* @param Builder $builder
* @return Builder
*/
public function apply(Builder $builder): Builder
{
$this->builder = builder;
foreach ($this->request->input() as $method => $value) {
$methodName = Str::camel($method);
if (null === $value) {
continue;
}
if (method_exists($this, $methodName)) {
if (in_array($method, static::KEYS_TO_BOOL, true)) {
$value = (bool)$value;
}
if (in_array($method, static::KEYS_TO_INT, true)) {
$value = (int)$value;
}
if (in_array($method, static::KEYS_TO_DATE, true)) {
$value = CarbonImmutable::parse($value);
}
if (in_array($method, static::KEYS_TO_ARRAY, true)) {
$value = is_array($value) ? $value : [$value];
}
if (in_array($method, static::KEYS_STRING_TO_ARRAY, true)) {
$value = explode(',', $value);
}
$this->builder = $this->{$methodName}($value);
}
}
return $this->builder;
}
}
Создадим конкретный фильтр для продуктов.
<?php
namespace App\Filters;
use Illuminate\Database\Eloquent\Builder;
/**
* Class ProductFilter
*/
class ProductFilter extends Filter
{
/**
* Фильтрация по названию
*
* @param string $value
* @return Builder
*/
protected function name(string $value): Builder
{
return $this->builder->where('name', 'like', '%' . $value . '%');
}
/**
* Фильтрация по категории
*
* @param string $value
* @return Builder
*/
protected function category(string $value): Builder
{
return $this->builder->where('category', $value);
}
/**
* Фильтрация по цене
*
* @param array $value
* @return Builder
*/
protected function price(array $value): Builder
{
return $this->builder->whereBetween('price', [$value['min'], $value['max']]);
}
/**
* Фильтрация по наличию на складе
*
* @param bool $value
* @return Builder
*/
protected function inStock(bool $value): Builder
{
return $this->builder->where('in_stock', $value);
}
}
Создадим FormRequest, который будет использоваться для валидации и передачи данных в фильтр.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
/**
* Class ProductFilterRequest
*/
class ProductFilterRequest extends FormRequest
{
/**
* Правила валидации
*
* @return array
*/
public function rules(): array
{
return [
'name' => 'nullable|string|max:255',
'category' => 'nullable|string|max:255',
'price' => 'nullable|array',
'price.min'=> 'nullable|numeric|min:0',
'price.max'=> 'nullable|numeric|min:0',
'in_stock' => 'nullable|boolean',
];
}
}
Создадим трейт HasFilter, который позволит легко применять фильтры к моделям.
<?php
declare(strict_types=1);
namespace App\Core\Traits;
use App\Core\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;
/**
* Trait HasFilter
*
* @method static Builder filter(Filter $filter)
*/
trait HasFilter
{
/**
* @param Builder $builder
* @param Filter $filter
* @return Builder
*/
public function scopeFilter(Builder $builder, Filter $filter): Builder
{
return $filter->apply($builder);
}
}
Теперь используем наш фильтр в контроллере для получения продуктов.
<?php
namespace App\Http\Controllers;
use App\Filters\ProductFilter;
use App\Http\Requests\ProductFilterRequest;
use App\Models\Product;
use Illuminate\Http\JsonResponse;
/**
* Class ProductController
*/
class ProductController extends Controller
{
/**
* Получение списка продуктов с фильтрацией
*
* @param ProductFilterRequest $request
* @param ProductFilter $filter
* @return JsonResponse
*/
public function index(ProductFilterRequest $request, ProductFilter $filter): JsonResponse
{
$products = Product::filter($filter)->get();
return response()->json($products);
}
}
Паттерн “Фильтр” позволяет легко и элегантно управлять сложными запросами к базе данных, отделяя логику фильтрации от контроллеров и моделей. Это улучшает читаемость кода и упрощает его поддержку. Такой подход также облегчает тестирование и расширение логики фильтрации без необходимости изменения основного кода. Использование трейт HasFilter дополнительно упрощает применение фильтров к моделям, делая код еще более модульным и переиспользуемым.
Если вы видите это, значит, я еще не придумал, что написать.
Одной из мощных возможностей Laravel является функционал под названием “Маршрутизация моделей” (Route Model Binding). Этот функционал позволяет автоматически извлекать модель из базы данных по ее первичному ключу, переданному в маршруте. Это упрощает код и улучшает читаемость и поддерживаемость приложения.
Рассмотрим пример, в котором мы хотим создать маршрут для отображения информации о курсе. Мы будем использовать контроллер для обработки запроса и извлечения данных модели Course.
Для начала определим маршрут в файле маршрутов:
Route::get('{course}', [CourseController::class, 'show'])->name('show');
Теперь создадим метод show в контроллере CourseController, который будет обрабатывать запросы к этому маршруту:
namespace App\Http\Controllers;
use App\Models\Course;
use App\Http\Resources\ShowResource;
use App\Handlers\ShowHandler;
use Illuminate\Http\JsonResponse;
class CourseController extends Controller
{
public function show(Course $course, ShowHandler $handler): JsonResponse
{
$data = $handler->handle($course);
return $this->response(new ShowResource($data));
}
}
Когда маршрут вызывается с идентификатором модели, например /course/1, Laravel автоматически выполняет запрос к базе данных, чтобы найти модель Course с id, равным 1. Найденная модель будет передана в метод контроллера как параметр $course.
Внутри метода show мы можем использовать объект модели Course, так как он уже автоматически загружен Laravel. Далее, мы обрабатываем данные с помощью ShowHandler и возвращаем результат в виде JSON-ответа, используя ресурс ShowResource.
Если модель с указанным идентификатором не найдена, Laravel автоматически вернет страницу ошибки 404. Это происходит благодаря встроенной в Laravel обработке исключений.
В некоторых случаях может понадобиться использовать другой ключ вместо id. Например, если вы хотите извлекать модель по полю slug, вы можете настроить это в модели Course.
Пример с пользовательским ключом
// В модели Course
public function getRouteKeyName()
{
return 'slug';
}
После добавления этого метода, Laravel будет использовать поле slug для поиска модели вместо id. Теперь маршрут /course/some-slug будет искать курс с slug, равным some-slug.
Маршрутизация моделей в Laravel — это мощный инструмент, который упрощает работу с моделями в приложении. Автоматическое извлечение моделей по идентификатору, улучшенная читаемость кода и встроенная обработка ошибок делают этот функционал незаменимым для разработчиков.
Использование маршрутизации моделей позволяет сосредоточиться на бизнес-логике приложения, не беспокоясь о рутинных задачах, таких как извлечение данных из базы. Попробуйте использовать этот функционал в вашем следующем проекте, и вы ощутите все его преимущества.
{message}