<? phpukraine ЕКОСИСТЕМА
⌕Пошук по платформі
LARAVEL · ЩО НОВОГО

Що нового в Laravel 12

  1. 01Реліз про оновлення залежностей: breaking changes мінімальні, більшість застосунків переходить без правок коду.
  2. 02Стартові набори на React, Svelte, Vue і Livewire, зокрема з WorkOS AuthKit. Breeze і Jetstream більше не оновлюються.
  3. 03Уже під час циклу 12.x додались автоматичний eager loading, Cache::memo(), атрибут #[Scope] і failover для черг.
СТАТУС Лише безпека
Реліз
24 лютого 2025
Останній патч
12.69.3
Активна підтримка до
13.08.2026
Безпека до
24.02.2027
НОВІ МОЖЛИВОСТІ · 4

Нові можливості

1.1 · 12.0

Нові стартові набори

Застосунок з реєстрацією, входом, скиданням пароля й підтвердженням email тепер стартує з одного з чотирьох наборів. React, Svelte і Vue працюють через Inertia 2, TypeScript і Tailwind, а Livewire-версія зібрана на Flux UI. Кожен має варіант з WorkOS AuthKit, де вже є вхід через соцмережі, passkeys і SSO.

+ LARAVEL 12
composer global update laravel/installer
laravel new my-app
# інсталятор спитає, який стартовий набір узяти
− LARAVEL 11
composer require laravel/breeze --dev
php artisan breeze:install
1.2 · 12.0

UUIDv7 у HasUuids

У Laravel 11 трейт брав Str::orderedUuid(), тобто впорядкований UUIDv4. Тепер ключі генерує Str::uuid7() за специфікацією UUID версії 7. Такі ключі теж сортуються, тому добре лягають в індекс, а стару поведінку повертає трейт HasVersion4Uuids.

+ LARAVEL 12
use Illuminate\Database\Eloquent\Concerns\HasUuids;

class Article extends Model
{
    use HasUuids; // Str::uuid7()
}

// впорядкований UUIDv4, як у Laravel 11
use Illuminate\Database\Eloquent\Concerns\HasVersion4Uuids as HasUuids;
− LARAVEL 11
use Illuminate\Database\Eloquent\Concerns\HasUuids;

class Article extends Model
{
    use HasUuids; // Str::orderedUuid()
}
1.3 · 12.x

Http::batch()

Http::pool() і раніше відправляв запити паралельно, але реакцію на результат доводилось писати після нього вручну. batch() приймає колбеки before, progress, then, catch і finally, а concurrency() обмежує, скільки запитів летить одночасно. Метод зʼявився вже під час циклу, у Laravel 12.32.

+ LARAVEL 12
$responses = Http::batch(fn (Batch $batch) => [
    $batch->as('users')->get('http://localhost/users'),
    $batch->as('orders')->get('http://localhost/orders'),
])->then(function (Batch $batch, array $results) {
    // усі запити успішні
})->catch(function (Batch $batch, int|string $key, $response) {
    // запит $key завершився помилкою
})->concurrency(5)->send();
− LARAVEL 11
$responses = Http::pool(fn (Pool $pool) => [
    $pool->as('users')->get('http://localhost/users'),
    $pool->as('orders')->get('http://localhost/orders'),
]);
1.4 · 12.x

Failover для черг

Драйвер failover перебирає зʼєднання за списком: скажімо, Redis недоступний, і задача потрапляє в базу. Під час перемикання Laravel відправляє подію QueueFailedOver, на яку зручно повісити лог чи алерт. Драйвер додали в Laravel 12.34, воркер потрібен для кожного зʼєднання зі списку, крім sync.

+ LARAVEL 12
// config/queue.php
'failover' => [
    'driver' => 'failover',
    'connections' => [
        'redis',
        'database',
        'sync',
    ],
],

// .env
QUEUE_CONNECTION=failover
СИНТАКСИС · 1

Синтаксис

2.1 · 12.x

#[Scope] для локальних скоупів

Локальний скоуп називався scopePopular(), а викликався як popular(), і пошук по коду не повʼязував одне з іншим. Тепер метод має ту саму назву, що й виклик, а Laravel впізнає його за атрибутом #[Scope]. Атрибут зʼявився в Laravel 12.4, префікс scope теж працює.

+ LARAVEL 12
use Illuminate\Database\Eloquent\Attributes\Scope;

#[Scope]
protected function popular(Builder $query): void
{
    $query->where('votes', '>', 100);
}

$users = User::popular()->get();
− LARAVEL 11
public function scopePopular(Builder $query): void
{
    $query->where('votes', '>', 100);
}
ФУНКЦІЇ · 3

Нові функції

3.1 · 12.x

toResource()

$user->toResource() шукає UserResource у найближчому до моделі просторі імен Http\Resources, а колекції мають toResourceCollection(). Якщо клас названо інакше, його вказують атрибутом #[UseResource] на моделі або передають прямо в метод. Зʼявилось у Laravel 12.7.

+ LARAVEL 12
return User::findOrFail($id)->toResource();

return User::all()->toResourceCollection();
− LARAVEL 11
return new UserResource(User::findOrFail($id));
3.2 · 12.x

whereAttachedTo()

Запит на кшталт «усі користувачі з цією роллю» раніше збирали з whereHas() і замикання. whereAttachedTo() приймає модель або колекцію моделей і сам будує умову по звʼязку many-to-many. Метод додали в Laravel 12.7.

+ LARAVEL 12
$users = User::whereAttachedTo($role)->get();

$posts = Post::whereAttachedTo($tags)->get();
− LARAVEL 11
$users = User::whereHas('roles', fn (Builder $query) => $query->whereKey($role->id))->get();
3.3 · 12.x

Rule::anyOf()

Буває, що поле приймає два різні формати, скажімо email або логін. Rule::anyOf() перевіряє кілька наборів правил і пропускає значення, якщо підійшов хоч один, тож окремий клас правила під це не потрібен. Правило зʼявилось у Laravel 12.8.

+ LARAVEL 12
use Illuminate\Validation\Rule;

'username' => [
    'required',
    Rule::anyOf([
        ['string', 'email'],
        ['string', 'alpha_dash', 'min:6'],
    ]),
],
ПРОДУКТИВНІСТЬ · 2

Продуктивність

4.1 · 12.x

Автоматичний eager loading

Після Model::automaticallyEagerLoadRelationships() перше звернення до звʼязку однієї моделі довантажує його для всіх моделей колекції, тож вкладені цикли по posts і comments не роблять запит на кожен рядок. Функція зʼявилась у Laravel 12.8 і в документації досі позначена як beta: поведінка може змінитися навіть у патч-релізі.

+ LARAVEL 12
// AppServiceProvider::boot()
Model::automaticallyEagerLoadRelationships();

// або лише для однієї колекції
$users = User::where('vip', true)->get()->withRelationshipAutoloading();
− LARAVEL 11
$users = User::all();

foreach ($users as $user) {
    foreach ($user->posts as $post) { // окремий запит на кожного користувача
        // ...
    }
}
4.2 · 12.x

Cache::memo()

Коли той самий ключ читається кілька разів за запит, кожне читання йде в Redis чи інше сховище. Cache::memo() з Laravel 12.9 бере значення лише раз, а далі віддає його з памʼяті. Запис через put(), increment() чи remember() скидає запамʼятоване значення.

+ LARAVEL 12
$settings = Cache::memo()->get('settings'); // запит до кешу
$settings = Cache::memo()->get('settings'); // значення з памʼяті

$settings = Cache::memo('redis')->get('settings');
− LARAVEL 11
$settings = Cache::get('settings'); // запит до Redis
$settings = Cache::get('settings'); // ще один запит до Redis
БЕЗПЕКА · 1

Безпека

5.1 · 12.0

SVG поза правилом image

SVG може містити JavaScript, тож картинка від користувача відкривала шлях до XSS. Тепер image пропускає jpg, jpeg, png, bmp, gif і webp, а SVG лише з директивою allow_svg. В обʼєктному правилі для цього є File::image(allowSvg: true).

+ LARAVEL 12
'photo' => 'required|image', // SVG відхиляється

'logo' => 'required|image:allow_svg',
// або
'logo' => ['required', File::image(allowSvg: true)],
− LARAVEL 11
'photo' => 'required|image', // SVG проходить
DEPRECATED

Застаріло

Ще працює, але дає попередження і зникне в одній із наступних версій.

Laravel Breeze і JetstreamБільше не отримують оновлень. Нові проєкти починайте з нових стартових наборів.
Blueprint::getPrefix()Префікс таблиць беріть зі зʼєднання через DB::getTablePrefix().
Grammar::getTablePrefix() і setTablePrefix()Замість них DB::getTablePrefix() і DB::setTablePrefix().
BREAKING

Ламає сумісність

Перевірте це до оновлення: такий код перестане працювати або поводитиметься інакше.

Нові версії залежностейПотрібні laravel/framework ^12.0, phpunit/phpunit ^11.0 і pestphp/pest ^3.0.
Laravel installerЩоб створювати проєкти на 12.x з новими стартовими наборами, оновіть laravel/installer.
UUIDv7 у HasUuidsНові ключі генеруються як UUIDv7. Для UUIDv4 є HasVersion4Uuids, а трейт HasVersion7Uuids видалено.
Carbon 3Підтримку Carbon 2 прибрано, застосунок і пакети мають працювати з Carbon 3.
SVG у правилі imageБез image:allow_svg завантажений SVG більше не проходить валідацію.
Корінь диска localЯкщо диск не описано в config/filesystems.php, файли лягають у storage/app/private замість storage/app.
Значення за замовчуванням у контейнеріКонтейнер враховує дефолт параметра, тож для ?Carbon $date = null підставить null, а не новий обʼєкт.

Як оновитися

0 / 6