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

Laravel 13.39: моделі без первинного ключа, session:garbage-collect і тривалість спроб завдань

Laravel 13.39.0 вийшов 27 жовтня і вмістив близько 55 змін. Попередній тиждень ми підсумували оглядом 13.36-13.38 про репліки бази даних, а цього разу спільної теми немає - зате є кілька невеликих API, які закривають давні незручності в Eloquent, чергах і тестах. Нижче - лише нове, з прикладами, і короткий список виправлень, які варто знати.

Моделі без первинного ключа

Атрибут #[Table] уміє задавати ім'я таблиці, ключ, тип ключа й часові мітки. Але key: null означає «бери з властивості», а властивість за замовчуванням дорівнює id. Тож для таблиці без первинного ключа (журнал подій, проміжна таблиця, представлення в базі) доводилося поруч з атрибутом лишати protected $primaryKey = null.

Тепер достатньо key: false (#62113):

use Illuminate\Database\Eloquent\Attributes\Table;

#[Table(name: 'activity_log', key: false)]
class Activity extends Model
{
}

getKeyName() такої моделі повертає null. У першій версії модель лишалася автоінкрементною, тож Activity::create() вставляв рядок і одразу падав з ArgumentCountError, намагаючись записати згенерований ключ. У межах того самого випуску це виправили (#62205): з key: false модель тепер і неінкрементна. Якщо ви явно передасте incrementing в атрибуті, ваше значення має пріоритет.

Пам'ятайте, що без ключа не працює все, що спирається на getKey(): find(), save() для вже наявного запису, delete() на екземплярі, прив'язка моделей до маршрутів. Для журналів, куди лише пишуть і з яких читають запитами, цього й не треба.

session:garbage-collect замість лотереї

Драйвери сесій file і database чистять прострочені сесії за лотереєю: на кожному запиті з ймовірністю з session.lottery (за замовчуванням 2 зі 100) фреймворк запускає збирач сміття. Платить за це користувач, якому «пощастило», вночі прибирання майже не відбувається, а під піковим навантаженням воно може запускатися кілька разів паралельно.

Нова команда (#62104) викликає той самий gc() поточного драйвера з session.lifetime і дозволяє винести прибирання в планувальник:

// routes/console.php
use Illuminate\Support\Facades\Schedule;

Schedule::command('session:garbage-collect')->hourly();

А лотерею вимкнути в config/session.php:

'lottery' => [0, 100],

Команду спершу забули зареєструвати, тож php artisan session:garbage-collect відповідав «Command is not defined»; реєстрацію додали ще до випуску (#62150). Для Redis і Memcached команда нічого не змінює: там сесії й так зникають за TTL.

Тривалість у JobAttempted

JobProcessed уже мав властивість duration, але лише для успішних завдань. Подія JobAttempted, яка спрацьовує після кожної спроби, успішної чи ні, тепер теж її має (#62118): час виконання в мілісекундах як float, виміряний у finally, тобто і тоді, коли завдання кинуло виняток. Друга зміна (#62155) рахує тривалість і для sync-черги, тож dispatchSync() теж передає duration в обидві події.

use Illuminate\Queue\Events\JobAttempted;

Event::listen(function (JobAttempted $event) {
    Metrics::histogram('queue.attempt_ms', $event->duration, [
        'job' => $event->job->resolveName(),
        'ok' => $event->successful(),
    ]);
});

Тут Metrics - умовний клієнт вашої системи метрик. Практична користь: завдання, яке звертається до зовнішнього API і падає за таймаутом, тепер видно в метриках, а не лише в failed_jobs. duration може бути null, якщо завдання впало ще до запуску, тож у слухачі це варто врахувати.

WithoutMigrationEvents для тестів

З LazilyRefreshDatabase міграції запускаються на першому запиті до бази всередині тесту. Тому DB::listen() чи expectsDatabaseQueryCount() цього тесту бачать і всі запити migrate:fresh. Тест проходить, коли до нього хтось уже змігрував базу, і падає, коли запущений сам, через --filter або першим у паралельному воркері.

Атрибут #[WithoutMigrationEvents] (#62201) на методі чи класі тесту знімає диспетчер подій зі з'єднань на час міграції:

use Illuminate\Foundation\Testing\Attributes\WithoutMigrationEvents;

class UserTest extends TestCase
{
    use LazilyRefreshDatabase;

    #[WithoutMigrationEvents]
    public function test_it_runs_one_query()
    {
        $this->expectsDatabaseQueryCount(1);

        User::first();
    }
}

Атрибут читається і з батьківських класів, тож його можна поставити один раз на базовий TestCase.

Валідація і зв'язки

Розміри в Rule::array() (#62179). Після того як у 13.36 конструктори правил отримали майже всі правила, розмір масиву все ще доводилося дописувати рядком 'min:1'. Тепер є min(), max(), between() і exactly(), як у Rule::string():

'photos' => [Rule::required(), Rule::array()->min(1)->max(10)],
'answers' => [Rule::array(['id', 'value'])->exactly(3)],

Зверніть увагу, що метод називається exactly(), а в рядок він перетворюється на size:3.

forceCreate() і forceCreateQuietly() для BelongsToMany (#62136). Раніше $team->users()->forceCreate([...]) проходив до конструктора запитів Eloquent: модель створювалася, але до команди не прив'язувалася. Тепер запис у проміжній таблиці з'являється, як і з create().

restoreOrCreate() і createOrRestore() на зв'язках (#62146, #62204). Це були макроси конструктора запитів, тож на зв'язку вони його ігнорували: на hasMany створювали модель без зовнішнього ключа, на belongsToMany не прив'язували нову модель, а на hasManyThrough могли відновити чужий запис, бо id приходив із проміжної таблиці. Тепер обидва методи є на HasOneOrMany, BelongsToMany, HasOneThrough і HasManyThrough і працюють через сам зв'язок. Якщо ви колись обходили це вручну, обхід можна прибрати.

Фейки, які нарешті фейкові

Три виправлення в тестових фейках з тих, що непомітно ламають тести:

  • Mail::html() і Mail::plain() під Mail::fake() ішли повз фейк до справжнього mailer і надсилали реальні листи, а assertNothingOutgoing() при цьому проходив (#62162). Якщо ви користуєтеся цими методами, перевірте, куди йшла пошта з CI.
  • Process::fake(['ls' => 1]) - скорочення з кодом виходу чи винятком - працювали лише з run(), а з start() і pool() кидали LogicException (#62200).
  • Bus::withoutDispatchingAfterResponses() тепер є і на BusFake (#62130): можна сфейкати частину завдань і водночас виконати те, що запускається через dispatchAfterResponse().

Помітні виправлення

  • Атомарний запис кешу (#62117). config:cache, route:cache і event:cache спершу обрізали файл, потім заповнювали його, тож запит посеред розгортання міг прочитати порожній конфіг. З opcache.validate_timestamps=0 OPcache запам'ятовував порожній файл до перезапуску PHP-FPM. Тепер файл пишеться в тимчасовий і перейменовується.
  • RefreshDatabase відкочує всі рівні транзакцій (#62194). Тест, що лишив відкриту транзакцію, більше не валить усі наступні з cannot start a transaction within a transaction на SQLite у пам'яті.
  • phar:// у Filesystem::exists() і isFile() (#62164) тепер повертає false, а get() кидає FileNotFoundException. Це прибирає ризик десеріалізації через phar, коли шлях приходить від користувача.
  • queue:retry з видаленою моделлю (#62124). Раніше одне завдання, чия модель зникла, зупиняло всю команду, і решта лишалася у failed_jobs. Тепер таке завдання повертається в чергу як є, а воркер уже видаляє його чи знову позначає як невдале.
  • Попередній маршрут в Octane (#62123). На 404 Route::currentRouteName() і Route::input() повертали маршрут і параметри попереднього запиту того самого воркера - разом із прив'язаною моделлю іншого користувача.

Чи варто оновлюватися

Так, і без особливих приготувань: нові можливості опційні, а виправлення не змінюють поведінку робочого коду. Першочергово - якщо ви кешуєте конфіг під час деплою без зупинки трафіку, працюєте на Octane або тестуєте листи через Mail::html(). Після оновлення варто зробити три речі: перенести прибирання сесій у планувальник, якщо у вас драйвер file чи database; прибрати protected $primaryKey = null там, де вже стоїть #[Table]; і перевірити слухачі JobAttempted, якщо хочете метрики тривалості для невдалих спроб.

15

Читати в документації

Коментарі

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

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

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

Новини 30 жовтня 2026

Laracon AU 2026 у Брисбені: програма і що варто подивитися

4-6 листопада в Брисбені пройде Laracon AU 2026 - Day 0 з двома воркшопами і два дні доповідей. Марсель Поціот про приховану архітектуру Laravel-застосунків, Метт Стауффер про смак розробника в епоху AI, Джоел Клермонт про тести, яким можна довіряти, Filament поза адмінками.

11

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

Intetics
9 днів тому

Senior Full-Stack PHP Developer (Laravel + React/Vue)

Senior Full-Stack PHP розробник створює й підтримує масштабовані веб-застосунки: бекенд на Laravel та фронтенд на React або Vue. Відповідає за архітектурні рішення (дизайн сервісів, моделювання даних, кешування, черги), проєктує REST API та інтеграції, рефакторить legacy-код і оптимізує запити. Вимоги: 5+ років досвіду з сучасним PHP 8.x, 3+ роки з Laravel, 3+ роки з React або Vue, сильний TypeScript, досвід з Redis і чергами, автотести та CI/CD.

Senior PHP Developer / Software Architect

Senior PHP-розробник та архітектор для розвитку й модернізації платформи публічних закупівель з фінансовими процесами та інтеграціями. Роль поєднує проєктування складних backend-систем, рефакторинг legacy-компонентів і безпосередню розробку на PHP 8+ із Symfony або Laravel. Ключові вимоги: 10+ років PHP-розробки, 5+ років у FinTech, активне використання AI-інструментів у розробці, досвід REST API, Redis/RabbitMQ, Docker і CI/CD.

EpicentrK Нова
2 дні тому

Backend PHP Developer (Middle+/Senior)

Розробник бекенду на PHP, який інтегрує внутрішні та зовнішні системи, проєктує й оптимізує складні SQL-запити та бере участь у плануванні архітектури сервісів. Потрібен досвід комерційної розробки на PHP 7.x/8.x від 4 років, робота з Symfony та/або Laravel, REST API, PostgreSQL/MySQL, MongoDB/Redis, RabbitMQ та Elasticsearch, а також глибоке знання OOP, SOLID і проєктування індексів.

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

Kalimera

welldigit/kalimera

Interactive installer that scaffolds Laravel applications the Well Digit way.

v1.0.0 16

Bagisto

bagisto/bagisto

Bagisto — це платформа для електронної комерції, побудована на Laravel. Вона надає готове рішення для створення та управління інтернет-магазинами з підтримкою каталогу товарів, замовлень, платежів та клієнтів.

28,284 v2.5.0 13 26