---
title: "Laravel 13.27: маскування параметрів запитів, бінарні порівняння та пасимістичні блокування"
url: https://laravelukraine.com/blog/laravel-1327-maskuvannia-parametriv-zapitiv-binarni-porivniannia-ta-pasimisticni-blokuvannia
date: 2026-08-27
source: https://laravel-news.com/laravel-13-27-0?utm_medium=feed&utm_source=feedpress.me&utm_campaign=Feed%3A+laravelnews
---

# Laravel 13.27: маскування параметрів запитів, бінарні порівняння та пасимістичні блокування

Laravel 13.27 додає опцію для кожного з'єднання, яка виключає параметри запитів з повідомлень про помилки, сімейство методів `whereBinary()` для побайтових порівнянь на MySQL та MariaDB, метод `refreshForUpdate()` для перезавантаження Eloquent-моделі під песимістичним блокуванням та багато іншого.

## Маскування параметрів запитів у повідомленнях про помилки

`QueryException` інтерполює параметри у своє повідомлення, тому невдалий INSERT додає кожне значення параметра в `getMessage()`. Це повідомлення потім потрапляє всюди, куди потрапляють винятки: таблиця `failed_jobs`, лог-файли та APM-траси. Адреси електронної пошти, імена тощо логуються.

Новий ключ конфігурації для кожного з'єднання зупиняє інтерполяцію і залишає плейсхолдери `?` на місці:

```php
'mysql' => [
    'driver' => 'mysql',
    // ...
    'mask_bindings_in_exception_messages' => env('DB_MASK_BINDINGS', false),
],
```

```
// за замовчуванням
SQLSTATE[23000]: ... SQL: insert into `users` (`email`) values (foo@example.com))
// замасковано
SQLSTATE[23000]: ... SQL: insert into `users` (`email`) values (?))
```

Ключ присутній у власному `config/database.php` фреймворку, тому додатки, які ніколи не публікували цей файл, можуть увімкнути його лише через `DB_MASK_BINDINGS=true`. За замовчуванням встановлено `false`, і змінюється лише повідомлення; `getBindings()` все ще повертає значення.

## `whereBinary()` для регістрозалежних порівнянь

Стандартні колації MySQL порівнюють без урахування регістру, тому `where('name', 'John')` також збігається з `john` та `JOHN`. Отримання точного побайтового порівняння означало використання сирого SQL:

```php
DB::table('queues')->whereRaw('name = BINARY ?', [$queueName])->first();
```

`whereBinary()` робить те саме через конструктор запитів, разом з `orWhereBinary()`, `whereNotBinary()` та `orWhereNotBinary()`:

```php
DB::table('queues')->whereBinary('name', $queueName)->first();
// select * from `queues` where `name` = binary ?
DB::table('queues')->whereNotBinary('name', $queueName)->get();
// select * from `queues` where `name` != binary ?
```

MariaDB успадковує граматику MySQL, тому це працює і там. Postgres, SQLite та SQL Server викидають `RuntimeException`, відповідно до того, як `whereLike()` обробляє регістрозалежний пошук на движках, які вже порівнюють з урахуванням регістру.

## `refreshForUpdate()`

Моделі зазвичай резолвляться до початку транзакції через прив'язку моделі до маршруту або payload завдання. Встановлення песимістичного блокування означало викидання цього екземпляра і повторний запит за первинним ключем:

```php
DB::transaction(function () use ($product) {
    $product = Product::query()
        ->lockForUpdate()
        ->findOrFail($product->getKey());
    $product->decrement('stock');
});
```

`refreshForUpdate()` поводиться як `refresh()` з `lockForUpdate()`, застосованим до запиту перезавантаження, тому екземпляр, який у вас уже є, оновлюється на місці і залишається придатним для використання протягом решти транзакції:

```php
DB::transaction(function () use ($product) {
    $product->refreshForUpdate();
    if ($product->stock === 0) {
        throw new RuntimeException('The product is out of stock.');
    }
    $product->decrement('stock');
});
```

Блокування діє лише протягом життя транзакції, тому виклик належить всередині неї; це закриває гонку даних між читанням моделі та записом назад.

## Фасад `Cloud`

Відповідь на питання "чи ми на Laravel Cloud і чи використовуємо керовані черги?" раніше означала поєднання хелпера `laravel_cloud()` з перевірками драйвера. Фасад `Cloud` збирає це в три методи:

```php
use Illuminate\Support\Facades\Cloud;

Cloud::hosted();            // працює на Laravel Cloud?
Cloud::usesManagedQueues(); // чи налаштовано cloud queue з'єднання?
Cloud::queue();             // саме керована черга
```

`Cloud::queue()` викидає `RuntimeException`, коли керовані черги не налаштовані, тому він працює в парі з `usesManagedQueues()`, а не замінює його. Фасад не зареєстрований у стандартних псевдонімах, тому він не конфліктуватиме з існуючим класом `Cloud` у додатку.

## Загальні метрики черг

Існуючі методи `pendingSize()`, `delayedSize()` та `reservedSize()` приймають одну назву черги, тому панель керування, що охоплює кілька черг, мала підсумовувати їх вручну. Три `total` аналоги роблять це за один виклик:

```php
Queue::totalPendingSize();
Queue::totalDelayedSize();
Queue::totalReservedSize();
```

Вони підсумовують по всіх чергах, про які знає з'єднання, і на відміну від методів `allXJobs()` вони лише рахують, тому жодні payload завдань не декодуються. Реалізовано для Redis, database, failover та fake драйверів.

## Векторні запити відстаней на MariaDB

Раніше `whereVectorSimilarTo()`, `whereVectorDistanceLessThan()`, `orderByVectorDistance()` та `selectVectorDistance()` працювали лише на Postgres. Обидва перемістилися в граматику, слідуючи патерну, який вже використовується для `compileRandom()` та `supportsSavepoints()`. MariaDB 11.7+ має нативний тип колонки `VECTOR` та функції відстаней, тому його граматика компілюється в `vec_distance_cosine()`, а підтримка `typeVector()` зі сторони схеми нарешті має відповідну сторону запитів. Звичайний MySQL все ще викидає помилку, оскільки `VECTOR_DISTANCE()` існує лише на MySQL HeatWave.

## Посилення обробки вводу та валідації

Контрибутор [@KIKOmanasijev](https://github.com/KIKOmanasijev) додав кілька виправлень щодо того, як Laravel читає ключі та порівнює значення.

`Request::merge(['*' => 226])` раніше стирав весь масив вводу замість додавання ключа `*`, оскільки редуктор прогонював ключі через `data_set()`, який трактує `*` як підстановочний знак. `Uri::withQuery()` мав ту саму помилку на рівень вище. Обидва тепер використовують `Arr::set()`, який зберігає нотацію з крапкою для справжніх вкладених злиттів, але зберігає `*` буквально.

Відхилення невідомих полів, додане в Laravel 13, згладжувало введення з `Arr::dot()`, що створює той самий ключ для вкладеного поля `profile[name]` і буквального ключа `"profile.name"`. Валідатор екранує буквальні крапки як `profile\.name`, тому ці два порівнювали ключі в різних нотаціях. Ключі вводу тепер згладжуються з тим самим екрануванням, яке використовують ключі правил.

Два правила валідації все ще використовували нестрогі порівняння. `in_array` збігав `"1e0"` з `"1"` та `"0e123456789"` з `"0"`, а `doesnt_contain` провалював масив, що містить `"0e123"`, наче він містив заборонений `"0"`. Обидва тепер використовують строге порівняння, слідуючи тому ж виправленню, застосованому до правила `in` раніше.

Нарешті, `MaintenanceModeBypassCookie::isValid()` перевіряв, що `mac` cookie встановлено, але не те, що це рядок, тому cookie `laravel_maintenance`, що несе масив, передавав цей масив у `hash_equals()` і перетворював очікуваний 503 у неавтентифікований 500. Перевірка `isset()` стала `is_string()`.

## Інші виправлення та покращення

- Postgres-з'єднання приймають ключі конфігурації `keepalives`, `keepalives_idle`, `keepalives_interval` та `keepalives_count`, які досягають DSN замість того, щоб бути відкинутими, тому довгоживуче з'єднання worker не вбивається мовчки тайм-аутом простою файрволу
- SQS-з'єднання можуть ділитися кешованими AWS-облікаими даними між процесами через опцію `credentials_cache`, замінюючи одне отримання облікових даних на PHP-FPM worker одним на ротацію. Laravel Cloud вмикає це за замовчуванням для керованих черг
- `orWhereKey()` та `orWhereKeyNot()` повернулися. Перша спроба була в 13.26 і була відкликана в 13.26.1 через зміну сигнатур `whereKey()`, що фатально зламало підкласи Builder, які їх перевизначають. Ця версія делегує через вкладену where-групу і залишає існуючі методи недоторканими
- `move()` та `copy()` на read-through диску тепер стримлять з fallback, коли primary ще не просунув файл, замість повернення `false` для файлу, про який `exists()` повідомляє як про присутній
- Debounced завдання більше не залишає свою мітку часу `maxWait` після виконання, що змушувало перший dispatch наступного burst повністю пропускати debounce
- Queued завдання, відправлене всередині транзакції, більше не примусово звільняє унікальне блокування, яке воно ніколи не набувало, коли транзакція відкочується
- Замикання обмежень eager-load знову `static`, розриваючи цикл посилання `Builder`, який зберігав builders та їхні завантажені дерева відносин живими до запуску збирача циклів, що найбільше важить у queue workers та Octane
- `Process::quietly()->run(...)->throw()` викидає `ProcessFailedException`, а не `LogicException` про вимкнений вивід, а `FakeInvokedProcess` отримав `stop()` та `ensureNotTimedOut()`, щоб фейкові процеси можна було зупиняти в тестах
- `InteractsWithTestCaseLifecycle::flushState()` надає додаткам задокументований хук для очищення власного статичного стану між тестами замість перевизначення внутрішнього `tearDownTheTestEnvironment()`
- Зображення, створені зі stream, більше не провалюються при другому читанні, оскільки lazy loader тепер резолвиться один раз і ділиться результатом з клонами. `fromUrl()` перестає робити свіжий HTTP-запит на читання з тієї ж причини, і `fromUrl()` викидає помилку при неуспішних відповідях замість трактування тіла 404 як даних зображення
- `PhpRedisConnection::mget()` та `hmget()` захищаються від `false`, яке phpredis повертає при відхиленій команді, що виявлялося як `TypeError` від `array_map()` при CROSSSLOT помилці
- Виняток `Brick\Math` більше не уникає правил числового порівняння. Охоронці `gt` та `gte` ловили неправильний клас `MathException`, а `lt` та `lte` взагалі не мали охоронця
- `Container::scoped()` більше не записує той самий abstract двічі в `$scopedInstances`, `Route::name('x')->post(...)` зберігає своє ім'я для дій контролера масиву, а `maxRelationshipDepth(0)` скидає вкладені includes замість взяття сегментів з кінця масиву
