Laravel 13.27 додає опцію для кожного з'єднання, яка виключає параметри запитів з повідомлень про помилки, сімейство методів whereBinary() для побайтових порівнянь на MySQL та MariaDB, метод refreshForUpdate() для перезавантаження Eloquent-моделі під песимістичним блокуванням та багато іншого.
Маскування параметрів запитів у повідомленнях про помилки
QueryException інтерполює параметри у своє повідомлення, тому невдалий INSERT додає кожне значення параметра в getMessage(). Це повідомлення потім потрапляє всюди, куди потрапляють винятки: таблиця failed_jobs, лог-файли та APM-траси. Адреси електронної пошти, імена тощо логуються.
Новий ключ конфігурації для кожного з'єднання зупиняє інтерполяцію і залишає плейсхолдери ? на місці:
'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:
DB::table('queues')->whereRaw('name = BINARY ?', [$queueName])->first();
whereBinary() робить те саме через конструктор запитів, разом з orWhereBinary(), whereNotBinary() та orWhereNotBinary():
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 завдання. Встановлення песимістичного блокування означало викидання цього екземпляра і повторний запит за первинним ключем:
DB::transaction(function () use ($product) {
$product = Product::query()
->lockForUpdate()
->findOrFail($product->getKey());
$product->decrement('stock');
});
refreshForUpdate() поводиться як refresh() з lockForUpdate(), застосованим до запиту перезавантаження, тому екземпляр, який у вас уже є, оновлюється на місці і залишається придатним для використання протягом решти транзакції:
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 збирає це в три методи:
use Illuminate\Support\Facades\Cloud;
Cloud::hosted(); // працює на Laravel Cloud?
Cloud::usesManagedQueues(); // чи налаштовано cloud queue з'єднання?
Cloud::queue(); // саме керована черга
Cloud::queue() викидає RuntimeException, коли керовані черги не налаштовані, тому він працює в парі з usesManagedQueues(), а не замінює його. Фасад не зареєстрований у стандартних псевдонімах, тому він не конфліктуватиме з існуючим класом Cloud у додатку.
Загальні метрики черг
Існуючі методи pendingSize(), delayedSize() та reservedSize() приймають одну назву черги, тому панель керування, що охоплює кілька черг, мала підсумовувати їх вручну. Три total аналоги роблять це за один виклик:
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 додав кілька виправлень щодо того, як 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 замість взяття сегментів з кінця масиву