Любите загадки? Событие еще доступно на сайте.

Паттерн "Фильтр" в Laravel

Паттерн “Фильтр” (Filter Pattern) — это архитектурный шаблон, который помогает отделить логику фильтрации данных от контроллеров и моделей. В Laravel этот паттерн часто используется для построения динамических запросов к базе данных на основе входящих данных из HTTP-запросов. Это позволяет сделать код более чистым, поддерживаемым и легко расширяемым.

Пример использования

Рассмотрим пример использования паттерна “Фильтр” на модели Product. Мы создадим фильтр для поиска продуктов по различным критериям, таким как название, категория, цена и наличие на складе.

Шаг 1: Создание фильтра

Создадим базовый класс 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);
    }
}

Шаг 2: Создание запроса

Создадим 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',
        ];
    }
}

Шаг 3: Использование трейт для применения фильтра

Создадим трейт 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);
    }
}

Шаг 4: Применение фильтра в контроллере

Теперь используем наш фильтр в контроллере для получения продуктов.

<?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 дополнительно упрощает применение фильтров к моделям, делая код еще более модульным и переиспользуемым.

Inn_100_gramm

Если вы видите это, значит, я еще не придумал, что написать.

4

Вакансии

Спонсоры

Помощь в разработке вашего проекта на Laravel

Независимо от сложности проекта эти кампании помогают сообществу и всем его участникам воплощать идеи в элегантные приложения.

Присоединиться

Инструменты для управления эмоциями, которые помогают людям контролировать свою жизнь и лучше понимать себя.

Перейти

Подкасты c зажигательными эпизодами, которые заставят задуматься и приведут к новым перспективам.

Перейти