Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти
Інше
Події Про нас
Новини 17 липня 2026

Створення пошукового ендпоінту Laravel Scout із HTTP-методом QUERY

HTTP-метод QUERY - це нове доповнення до специфікації HTTP (RFC 10008), розроблене саме для одного завдання: запитів. Це безпечний метод із підтримкою кешування, який передає параметри в тілі запиту, надаючи виразність POST-payload без відмови від read-only семантики GET - і без необхідності втискати складні фільтри в URL, який може досягти обмежень довжини.

Laravel 13.19 додав Http::query() до HTTP-клієнта разом із тестовими хелперами query() та queryJson(). Повноцінний хелпер Route::query() вже злито для Laravel 14, але чекати не обов'язково: роутер Laravel вже сьогодні приймає користувацькі HTTP-дієслова через Route::match(). У цьому tutorial ми використаємо його для побудови невеликого пошукового ендпоінту на основі database-драйвера Laravel Scout.

Коли потрібен метод QUERY

Один пошуковий рядок, як у нашому прикладі, не обов'язково потребує QUERY - метод починає виправдовувати себе, коли запит розростається до чогось, що GET не може зручно обробити:

  • Структуровані фільтри з діапазонами, масивами та групами and/or (пошук продуктів чи оголошень)
  • Геопросторові запити, що надсилають координати полігонів ("пошук у цій області карти")
  • Пакетні запити, що отримують сотні записів за ID
  • Пошук за чутливими значеннями, як-от email або телефони, які не повинні потрапляти в URL, логи доступу або історію браузера
  • Ендпоінти звітності з діапазонами дат і group-by, які є читанням, але сьогодні моделюються як POST /reports/run

Налаштування проєкту

Починаючи зі свіжого Laravel 13-додатку, встановіть файл API-маршрутів і Scout:

php artisan install:api
composer require laravel/scout

Database-драйвер Scout не потребує зовнішнього пошукового сервісу - він використовує WHERE LIKE запити до вашої існуючої бази даних, що добре підходить для малих і середніх датасетів. Увімкніть його у файлі .env:

SCOUT_DRIVER=database

Далі створіть модель Article з міграцією та фабрикою:

php artisan make:model Article -mf

Міграція визначає поля title та body:

Schema::create('articles', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('body');
    $table->timestamps();
});

Додайте трейт Searchable до моделі та визначте, які колонки Scout має шукати:

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Article extends Model
{
    use HasFactory, Searchable;

    protected $fillable = ['title', 'body'];

    public function toSearchableArray(): array
    {
        return [
            'title' => $this->title,
            'body' => $this->body,
        ];
    }
}

Визначення QUERY-маршруту

Роутер Laravel не підтримує Route::query() у Laravel 13 (він буде доступний починаючи з Laravel 14). Однак роутер Laravel реєструє маршрути для будь-якого дієслова, яке ви передаєте в Route::match(), включно з QUERY.

Додайте наступне до routes/api.php:

use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::match(['QUERY'], '/articles/search', function (Request $request) {
    $validated = $request->validate([
        'search' => ['required', 'string'],
        'per_page' => ['sometimes', 'integer', 'between:1,50'],
    ]);

    return Article::search($validated['search'])
        ->paginate($validated['per_page'] ?? 15);
});

Зверніть увагу, що валідація та $request->input() зчитують дані з JSON-тіла запиту так само, як вони робили б це для POST-запиту - без додаткової роботи. Виконання php artisan route:list підтверджує, що маршрут зареєстровано з дієсловом QUERY:

QUERY  api/articles/search

Запит до цього ендпоінту виглядає як POST із GET-семантикою - критерії пошуку передаються в тілі:

QUERY /api/articles/search HTTP/1.1
Content-Type: application/json
Accept: application/json

{"search": "scout", "per_page": 10}

Тестування ендпоінту

Тестовий хелпер queryJson() Laravel 13.19 дзеркалить postJson() та подібні методи, тому тестування ендпоінту відчувається знайомо:

namespace Tests\Feature;

use App\Models\Article;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ArticleSearchTest extends TestCase
{
    use RefreshDatabase;

    public function test_it_searches_articles_with_the_query_method(): void
    {
        Article::factory()->create(['title' => 'Getting Started With Laravel Scout']);
        Article::factory()->create(['title' => 'Queues in Depth']);

        $this->queryJson('/api/articles/search', ['search' => 'scout'])
            ->assertOk()
            ->assertJsonCount(1, 'data')
            ->assertJsonPath('data.0.title', 'Getting Started With Laravel Scout');
    }

    public function test_it_validates_the_search_term(): void
    {
        $this->queryJson('/api/articles/search', [])
            ->assertUnprocessable()
            ->assertJsonValidationErrors('search');
    }
}

На стороні клієнта HTTP-клієнт Laravel може викликати QUERY-ендпоінти методом Http::query(), доданим у версії 13.19:

use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()->query('https://example.com/api/articles/search', [
    'search' => 'scout',
]);

$articles = $response->json('data');

Підводний камінь: вбудований сервер PHP

Якщо ви розробляєте з php artisan serve, запити з методом QUERY отримують відповідь 501 Not Implemented ще до того, як потраплять до Laravel - вбудований сервер жорстко кодує методи запитів, які він приймає. Середовища на основі Nginx, як-от Laravel Herd та Valet, передають метод до вашого додатку. Тестові хелпери Laravel HTTP повністю уникають цієї проблеми, оскільки вони відправляють запити безпосередньо через фреймворк.

Використання QUERY на веб-маршрутах

Наш ендпоінт знаходиться в routes/api.php, тому захист CSRF взагалі не виникає. Однак якщо ви зареєструєте QUERY-маршрут у routes/web.php, ви отримаєте помилку 419: middleware PreventRequestForgery Laravel 13 трактує лише GET, HEAD та OPTIONS як запити читання, тому QUERY отримує ту саму перевірку токена, що й POST. RFC 10008 визначає QUERY як безпечний метод, і наступний мажорний реліз Laravel звільняє його відповідно - але до того часу у вас є два варіанти.

Перший варіант переносить майбутню поведінку, розширюючи middleware та додаючи QUERY до списку read-дієслів:

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\PreventRequestForgery as Middleware;

class PreventRequestForgery extends Middleware
{
    /**
     * Визначити, чи HTTP-запит використовує read-дієслово.
     * QUERY є безпечним методом (RFC 10008), тому трактуємо його як GET.
     * Laravel 14 поставляється з цією поведінкою; видаліть це перевизначення після оновлення.
     */
    protected function isReading($request)
    {
        return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS', 'QUERY']);
    }
}

Потім замініть його в групі middleware web у bootstrap/app.php:

use App\Http\Middleware\PreventRequestForgery;
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery as BasePreventRequestForgery;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->web(replace: [
        BasePreventRequestForgery::class => PreventRequestForgery::class,
    ]);
})

Тепер кожен QUERY-маршрут звільнено від CSRF, тоді як POST, PUT, PATCH та DELETE зберігають повний захист, відповідаючи тому, що Laravel 14 робитиме з коробки. Коли ви оновитеся, видаліть клас і рядок replace, і нічого більше не зміниться.

Другий варіант виключає один маршрут із middleware:

use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;

Route::match(['QUERY'], '/articles/search', $handler)
    ->withoutMiddleware(PreventRequestForgery::class);

Це добре працює для разового ендпоінту, але вам потрібно буде пам'ятати про це на кожному QUERY-маршруті, який ви додаєте, і це повністю видаляє middleware з маршруту, а не класифікує дієслово як читання. Оскільки перевизначення middleware - це зміна двох файлів, яка чисто зникає після оновлення, ми б обрали його першим.

Одна примітка щодо перевірки будь-якого підходу: CSRF middleware пропускає перевірку, коли середовище додатка - testing, тому feature-тест не отримає 419, якщо ви спочатку не переназначите середовище (наприклад, $this->app['env'] = 'production'; у методі setUp() вашого тесту).

Нарешті, пам'ятайте, що означає звільнення: фреймворк довіряє, що ваші QUERY-обробники є read-only, так само як він довіряє вашим GET-обробникам. Не змінюйте стан у QUERY-маршруті.

Перед відправкою QUERY у production

Laravel добре обробляє QUERY - маршрути навіть працюють із php artisan route:cache - але фреймворк - це лише один крок у ланцюгу. Кілька речей, які потрібно перевірити перед використанням методу в production API:

  • Кожен проміжний сервіс має пропускати метод. Ми вже бачили, як вбудований сервер PHP повертає 501; CDN, WAF та load balancer можуть бути так само категоричними щодо дієслів, які вони не розпізнають. Тестуйте повний шлях від клієнта до додатка, а не лише додаток.
  • Браузерні виклики мають застереження. fetch() надсилає QUERY без проблем, але HTML-форми говорять лише GET та POST, і QUERY не є CORS-safelisted методом - кожен cross-origin запит викличе preflight.
  • Переваги кешування поки що на папері. RFC визначає QUERY як cacheable, але браузери та CDN ще не реалізують кешування QUERY, тому не приймайте метод лише через цю перевагу сьогодні.
  • Інструменти специфікацій наздоганяють. OpenAPI отримав повноцінну операцію query лише у версії 3.2, випущеній у вересні 2025 року - генератори та SDK-ланцюжки інструментів, які все ще орієнтуються на 3.0 або 3.1, не можуть описати ендпоінт, а деякі HTTP-клієнтські бібліотеки жорстко кодують дієслова, які вони приймають.

Жодне з цього не є dealbreaker для внутрішнього API, де ви контролюєте обидва кінці - що також є найпростішим місцем для першої спроби QUERY.

Що далі

Коли вийде наступна мажорна версія Laravel, виклик Route::match(['QUERY'], ...) може стати Route::query('/articles/search', ...), а перевизначення CSRF вище можна видалити - решта коду в цьому tutorial залишається незмінною.

Між маршрутизацією, HTTP-клієнтом і тестовими хелперами Laravel покриває обидва кінці методу QUERY сьогодні. Ви можете дізнатися більше про метод QUERY у RFC 10008 та слідкувати за підтримкою маршрутизації в Pull Request #60655.

5

Коментарі

Увійдіть, щоб залишити коментар

Будьте першим, хто залишить коментар!

Читайте також

Laracon US 2026: відео першого дня вже доступне Рекомендовано
Новини 29 липня 2026

Laracon US 2026: відео першого дня вже доступне

У Бостоні стартував Laracon US 2026, і запис першого дня вже доступний. Головне з кейноуту: офіційний Laravel LSP для редакторів, Laravel Cloud деплоїть Hono, Flask, Go та Rails, Pest 5 із Tia Engine. Аарон Френсіс приєднався до команди Laravel.

Pest 5: Tia Engine, плагін для AI-агентів і evals для LLM
Новини 29 липня 2026

Pest 5: Tia Engine, плагін для AI-агентів і evals для LLM

На першому дні Laracon US 2026 Нуно Мадуро представив Pest 5. Головне - Tia Engine, що переганяє лише зачеплені змінами тести й скорочує десятихвилинний прогін до кількох секунд, плюс плагіни для AI-агентів, evals, PHPStan і Rector.

Вакансії за темою

Senior Tech Lead PHP Laravel Developer

Розробник PHP Laravel з 8+ років досвіду та 2+ років керівництвом команди. Займатиметься розробкою високопродуктивних API, SPA-додатків та складних систем e-commerce. Потрібні навички AWS, Docker/Kubernetes, TypeScript/Vue/Nuxt, MySQL оптимізації, проведення code review та керівництво розробниками.

Sphise
18 дн. тому

Senior Backend Engineer (PHP&Laravel)

Senior Backend Engineer для розробки healthcare платформи на PHP/Laravel. Основні обов'язки: розробка та оптимізація EHR платформи, створення API, забезпечення HIPAA compliance. Вимоги: 5+ років бекенд-розробки, глибокі знання PHP/Laravel та MySQL, досвід з healthcare системами буде плюсом. Менторство джуніорів, робота в agile команді.