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

Валідація ключів масиву в Laravel: нове правило array_keys

Ендпоінти, які приймають набір опцій, мають тихий режим відмови. Клієнт надсилає ?filter[stat us]=draft з друкарською помилкою, ваш код читає $filters['status'], нічого не знаходить і повертає нефільтрований список. Ніхто не отримує помилку, відповідь виглядає нормально, а баг спливає пізніше як "фільтр іноді не працює".

Laravel 13.24 додає правило валідації array_keys саме для цього: масив може містити ці ключі і нічого більше, з повідомленням про помилку, яке вказує, що пішло не так.

Правило валідації

Працюють як builder-форма, так і рядкова:

use Illuminate\Validation\Rule;
$request->validate([
    'filter' => Rule::arrayKeys(['status', 'author', 'tag']),
]);
// Еквівалент
$request->validate([
    'filter' => 'array_keys:status,author,tag',
]);

Якщо передано ['status' => 'draft', 'stat us' => 'draft'], валідація завершиться з помилкою:

The filter field must only contain the following keys: status, author, tag.

Ключі є дозволеними, але не обов'язковими. Правило обмежує те, що може з'явитися, але не вимагає обов'язкової присутності. Якщо потрібна друга половина, required_array_keys все ще покриває це, і обидва правила можна комбінувати:

'coordinates' => [
    'required_array_keys:lat,lng',
    Rule::arrayKeys(['lat', 'lng']),
],

Ця пара читається як "точно ці ключі, не більше і не менше".

Чому не array:key_1,key_2?

Rule::array() вже деякий час приймає список ключів і справді відхиляє неочікувані ключі. Різниця полягає в тому, що відбувається при помилці. Правило array відповідає на два запитання: "це масив?" і "він містить тільки ці ключі?", і повідомляє про обидва одним повідомленням:

Правило Повідомлення для ['status' => 'draft', 'colour' => 'red']
array:status,author The filter field must be an array.
array_keys:status,author The filter field must only contain the following keys: status, author.

Перше повідомлення активно вводить в оману, оскільки значення є масивом. Також немає placeholder для проблемних ключів, тому неможливо написати власне повідомлення, яке їх називає.

Обидва правила звітуються окремо в $validator->failed() як Array та ArrayKeys, тому можна застосовувати обидва, коли потрібно розрізняти кожну помилку:

'filter' => ['array', Rule::arrayKeys(['status', 'author', 'tag'])],

Rule::array() та його повідомлення залишаються незмінними, тому нічого з уже написаного коду не поводиться інакше.

Вказування проблемних ключів

Правило постачається з двома placeholder. :values містить прийняті ключі і використовується в стандартному повідомленні. :unexpected містить ключі, які фактично спричинили помилку, і хоча він не використовується в стандартному повідомленні, зазвичай це кориснішша половина у відповіді API:

$request->validate(
    ['filter' => Rule::arrayKeys(['status', 'author', 'tag'])],
    ['filter.array_keys' => 'The :attribute field may not contain :unexpected.'],
);
// The filter field may not contain colour, sort.

Вказуючи клієнту, який ключ він зробив неправильно, ви перетворюєте гру в здогадки на виправлення в один рядок, що варте власного повідомлення в публічному API.

Приклад фільтрованого ендпоінту

Об'єднуючи все разом на маршруті, який приймає фільтри, сортування та include з власним набором дозволених ключів:

namespace App\Http\Requests;

use App\Enums\PostStatus;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class IndexPostRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'filter' => ['sometimes', 'array', Rule::arrayKeys(['status', 'author', 'tag'])],
            'filter.status' => ['sometimes', Rule::enum(PostStatus::class)],
            'filter.author' => ['sometimes', 'integer', 'exists:users,id'],
            'filter.tag' => ['sometimes', 'string', 'max:50'],
            'sort' => ['sometimes', 'string', Rule::in(['title', '-title', 'published_at', '-published_at'])],
        ];
    }

    public function messages(): array
    {
        return [
            'filter.array_keys' => 'Unknown filter: :unexpected. Allowed filters are :values.',
        ];
    }
}

Контролер може тоді читати набір фільтрів без захисних перевірок, оскільки все, що до нього дійшло, є ключем, який ви вказали:

public function index(IndexPostRequest $request)
{
    $filters = $request->validated('filter', []);
    return PostResource::collection(
        Post::query()
            ->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
            ->when($filters['author'] ?? null, fn ($q, $author) => $q->where('user_id', $author))
            ->when($filters['tag'] ?? null, fn ($q, $tag) => $q->whereRelation('tags', 'slug', $tag))
            ->paginate()
    );
}

Без цього правила ?filter[autor]=3 повертає всі пости і виглядає як робочий запит. З ним клієнт отримує 422 з вказівкою на autor.

Валідація JSON-колонки

Те саме правило корисне при записі даних, де колонка налаштувань або уподобань схильна накопичувати все, що випадково надіслав frontend:

'preferences' => ['sometimes', 'array', Rule::arrayKeys(['theme', 'timezone', 'digest_frequency'])],
'preferences.theme' => ['sometimes', Rule::in(['light', 'dark', 'system'])],
'preferences.timezone' => ['sometimes', 'timezone'],
'preferences.digest_frequency' => ['sometimes', Rule::in(['daily', 'weekly', 'never'])],

Перейменоване поле frontend тепер явно падає під час розгортання замість того, щоб записувати ключ, який ніхто не читає, у кожен рядок.

Деталі, які варто знати

Кілька особливостей поведінки, які не очевидні з повідомлення:

  • Значення, яке не є масивом, не проходить правило. Передача 'filter' => 'draft' створює повідомлення "must only contain the following keys", яке читається дивно для рядка. Поєднуйте його з array, коли вхідні дані можуть взагалі не бути масивом, щоб помилка типу повідомлялася окремо.

  • Правило потребує принаймні одного ключа. Написання 'array_keys' без ключів викликає InvalidArgumentException під час виконання валідації, а не провалює поле. Немає форми "не дозволяти нічого"; цей випадок покриває prohibited.

  • Ключі можуть походити з будь-якого Arrayable. Працюють колекція або список на основі enum, а backed enum розв'язуються до їхніх значень:

Rule::arrayKeys(FilterKey::cases());
Rule::arrayKeys(collect(config('filters.allowed')));
  • Варіативна форма теж працює. Rule::arrayKeys('status', 'author') еквівалентна передачі масиву, що зручно для inline-використання.

Правило було внесено @nebarg у #60918.

0

Коментарі

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

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

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

Saga Lara Flow
Новини 08 серпня 2026

Saga Lara Flow: тривалі воркфлоу та компенсуючі транзакції на основі черг Laravel

Saga Lara Flow - пакет для Laravel, що дозволяє писати довготривалі бізнес-процеси як звичайні PHP-методи поверх черг Laravel. Підтримує автоматичні відкати транзакцій, сигнали, паралельні блоки та вкладені воркфлоу із записом кожного кроку до бази даних.

HEIC в Laravel
Новини 08 серпня 2026

Валідація та конвертація HEIC зображень у Laravel

Laravel 13.24 навчився приймати HEIC, HEIF та AVIF формати, додав метод toHeic() для виводу та розширив правило валідації image. Розглядаємо, як приймати фото з iPhone і конвертувати їх у формати, які розуміють браузери.

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

Middle QA Engineer, Manual QA, Fintech

Ручне тестування фінтех-продукту (web, mobile, API) з фокусом на функціональне, регресійне та модульне тестування. Вимоги: досвід тест-дизайну, знання SDLC/STLC, робота з Jira/Postman/DevTools, створення тестової документації. Преферуються знання фінансових систем та досвід з fintech-продуктами.

Пакети за темою

Aimeos Laravel

aimeos/aimeos-laravel

Cloud-native, API-first Laravel пакет для електронної комерції з інтегрованою штучною інтелектуальністю для надшвидких онлайн-магазинів, маркетплейсів та складних B2B проектів.

8,669 2026.04.1 13 4

Laravel Query Builder

spatie/laravel-query-builder

Легко будуйте Eloquent-запити на основі запитів від API.

4,461 7.3.0 13 9