В Laravel 13.25 добавлен глобальный переключатель паузы, который останавливает каждую очередь на каждом соединении одной командой, заменён процесс-раннер для  artisan dev  на терминальный UI с вкладками, а также добавлена возможность возвращать экземпляр  Image  напрямую из маршрута. Команда Laravel выпустила версию v13.25.0 11 августа 2026 года.

  •  queue:pause --all  и  queue:resume --all , а также  Queue::pauseAll()  и  Queue::resumeAll() 
  •  artisan dev  работает через  @laravel/multiplex  с режимами вкладок (tabs), потока (stream) и встроенным (inline)
  •  Image  реализует  Responsable , получает  Image::fromStream() , а метод  toFormat()  теперь публичный
  • Событие  UniqueJobSkipped , значение таймаута в  JobTimedOut  и обработка сбоя при таймауте (fail-on-timeout) для уведомлений
  •  withoutCookies()  для ответов и вспомогательный метод схемы  foreignUlidFor() 
  •  Request::all()  теперь отдаёт приоритет инпуту перед файлами при их совпадении

#Что нового

#Приостановка всех очередей на всех соединениях

В Laravel уже давно можно было приостановить отдельную очередь, но такая гранулярность мешала при деплое. Приложению с дюжиной воркеров, распределённых по нескольким именованным очередям, приходилось приостанавливать каждую пару из соединения и очереди по имени, а эти имена меняются по мере добавления и удаления фич. Использование режима обслуживания (maintenance mode) вместо этого отключает весь сайт, хотя всё, что вам нужно, — это чтобы воркеры перестали резервировать задачи.

Обе консольные команды теперь принимают флаг  --all :

php artisan queue:pause --all
php artisan queue:resume --all
 

Аргумент очереди стал необязательным, чтобы освободить для этого место, и тот же переключатель доступен в фасаде:

use Illuminate\Support\Facades\Queue;
 
Queue::pauseAll();
Queue::resumeAll();
 

Глобальный переключатель — это единый ключ кэша, который воркеры проверяют наряду с ключами отдельных очередей, поэтому  isPaused()  и  getPausedQueues()  сообщают, что очередь приостановлена, если включён любой из переключателей. Они независимы:  resumeAll()  сбрасывает глобальный флаг и оставляет всё, что вы приостановили индивидуально, в приостановленном состоянии — это сделано намеренно, чтобы скрипт деплоя случайно не перезапустил очередь, которую кто-то специально остановил.

Два новых события,  QueuesPaused  и  QueuesResumed , срабатывают наряду с существующими для отдельных очередей  QueuePaused  и  QueueResumed . Внесено @jackbayliss в #61126.

Читать далее: Приостановка всех очередей Laravel во время деплоя

# artisan dev  работает через  @laravel/multiplex 

Команда  artisan dev  выставляла процессы наружу через  concurrently , который переплетал вывод каждого процесса в одну прокручиваемую ленту. Когда пересборка Vite, воркер очереди и Pail пишут одновременно, поиск нужной строки превращается в бесконечную прокрутку.

Теперь она работает через  @laravel/multiplex  — интерфейс терминала, в котором каждый процесс находится на своей вкладке. Доступен поиск, перезапуск отдельных процессов, очистка логов и автоматический перезапуск упавшего процесса. Предусмотрено три режима:  tabs  (по умолчанию),  stream  (единый переплетённый поток с возможностью прокрутки и поиска) и  inline  (простой вывод, используемый автоматически при отсутствии TTY). Режим можно выбрать для каждого запуска:

php artisan dev --stream
php artisan dev --timestamps --no-restart
 

Или задать значение по умолчанию для проекта в сервис-провайдере:

use Illuminate\Foundation\DevCommands;
 
DevCommands::stream();
DevCommands::withTimestamps();
DevCommands::disableAutoRestart();
DevCommands::bufferSize(5000);
 

При завершении работы команды буферизованные логи выводятся в основной терминал, поэтому ничего не теряется. В Windows происходит откат к  concurrently , так как multiplex на данный момент поддерживает только macOS и Linux, а минимальная версия Node для нового механизма — v22.13. Полный список режимов, флагов и методов регистрации доступен в статье о терминальном UI для artisan dev. Внесено @joetannenbaum в #61100.

#Изображения в качестве HTTP-ответов

Встроенный Image API мог трансформировать изображение и сохранять его, но для отдачи его по HTTP приходилось вызывать  toBytes()  и собирать ответ вручную. Теперь  Image  реализует  Responsable , поэтому маршрут может напрямую возвращать экземпляр:

use Illuminate\Support\Facades\Image;
 
Route::get('/avatars/{user}', function (User $user) {
    return Image::fromStorage($user->avatar_path)
        ->cover(200, 200)
        ->toWebp()
        ->quality(80);
});
 

 toResponse()  возвращает код 200 с обработанными байтами и заголовком  Content-Type , прочитанным из выходных данных, поэтому заголовок соответствует итоговому формату пайплайна, а не исходному файлу.

Ещё два небольших дополнения в этой же области.  Image::fromStream()  создаёт экземпляр из ресурса потока, читая его лениво и выбрасывая  ImageException , если поток ничего не возвращает:

$image = Image::fromStream(Storage::disk('s3')->readStream($path));
 

А метод  toFormat()  теперь сделан публичным. Он заменяет конструкцию  match , которую вам иначе пришлось бы писать для преобразования полученной от пользователя строки формата в нужный вызов  toWebp()  или  toAvif() :

return Image::fromUpload($request->file('photo'))
    ->toFormat($request->string('format'))
    ->quality(80);
 

Неподдерживаемый формат выбрасывает  ImageException  вместо тихой ошибки. Все три улучшения внесены @calebdw в #61111, #61109 и #61110.

#Дополнения для наблюдаемости очередей

Задача, которая не отправляется из-за удержания блокировки  ShouldBeUnique , бесследно исчезает, из-за чего трудно отличить работающее ограничение уникальности от блокировки, которая никогда не снимается. Новое событие  UniqueJobSkipped  содержит пропущенную задачу:

use Illuminate\Queue\Events\UniqueJobSkipped;
 
Event::listen(function (UniqueJobSkipped $event) {
    Log::info('Skipped unique job', ['job' => $event->job::class]);
});
 

Оно вызывается из  PendingDispatch , когда не удаётся получить уникальную блокировку, наряду с существующим событием  JobDebounced  (#61039).

Событие  JobTimedOut  получило третье свойство,  $timeout , содержащее превышенное количество секунд. Воркер, запущенный с  queue:work --timeout=120 , применяет собственный таймаут к каждой выполняемой задаче, поэтому без этого значения в событии было невозможно отличить собственный таймаут задачи от таймаута воркера (#61060).

Уведомления теперь учитывают fail-on-timeout.  SendQueuedNotifications  считывает свойство  $failOnTimeout  или атрибут  #[FailOnTimeout]  из уведомления, что важно, когда из-за таймаута вы не уверены, доставила ли сторонняя система сообщение (#61072):

use Illuminate\Queue\Attributes\FailOnTimeout;
 
#[FailOnTimeout]
class OrderShipped extends Notification implements ShouldQueue
{
    //
}
 

Наконец,  QueueFake  теперь присваивает UUID фейковым задачам, благодаря чему методы инспекции очередей возвращают ту же структуру, что и при работе с реальным драйвером, а код приложения, читающий  $job->uuid , становится тестируемым (#60966).

# withoutCookies()  в ответах

Удаление нескольких cookie ранее требовало цепочки вызовов  withoutCookie()  для каждого имени. Форма во множественном числе принимает массив:

return response('OK')->withoutCookies(['session', 'tracking', 'preferences']);
 

Метод проходится циклом по  withoutCookie() , поэтому необязательные аргументы  $path  и  $domain  применяются к каждой cookie в массиве, причём поддерживаются как имена, так и экземпляры cookie. Внесено @xurshudyan в #61115.

#Вспомогательный метод схемы  foreignUlidFor() 

 foreignIdFor()  уже умеет определять трейт  HasUlids  и создавать колонку ULID, но не было явного метода, парного для  foreignUuidFor() . Теперь тройка полна:

$table->foreignUlidFor(User::class)->constrained();
 

Этот метод определяет имя колонки, связанную таблицу и ссылочный ключ на основе модели, создавая колонку  char(26)  и соответствующее ограничение внешнего ключа (foreign key constraint). Внесено @talaridisTh в #61036.

# Request::all()  отдаёт приоритет инпуту перед файлами

 Request::all()  объединял массив инпута и массив файлов с помощью  array_replace_recursive() , причём файлы применялись последними, из-за чего файловое поле и поле инпута с одинаковым именем резолвились в  UploadedFile . Теперь порядок изменён, и инпут имеет приоритет:

// POST with input email=taylor@laravel.com and a file also named email
$request->all();        // ['email' => 'taylor@laravel.com']
$request->email;        // 'taylor@laravel.com'
$request->file('email') // still the UploadedFile
 

Вложенные ключи объединяются аналогичным образом: и файл  profile.avatar , и инпут  profile.name  по-прежнему будут присутствовать, а приоритет инпута проявится только там, где ключи действительно пересекаются. Метод  file()  это не затрагивает. Внесено @taylorotwell в #61099.

#Другие исправления и улучшения

  •  Http::globalOptions()  и глобальные middleware применялись к long poll через unix-сокет собственного облачного агента фреймворка, где опции вроде  force_ip_resolve => v4  ломали соединение сокета. Новый замыкающий метод  Factory::withoutGlobalConfiguration()  изолирует трафик агента от конфигурации клиента на уровне приложения (#61064, #61068)
  • Помещённые в очередь события вещания (broadcast events) теряли значение  true  по умолчанию для  $deleteWhenMissingModels , из-за чего модель, удалённая до того, как воркер подхватит задачу, приводила к ошибке  ModelNotFoundException  вместо отбрасывания задачи (#61074)
  •  Gate::forUser()  копировал abilities, политики и коллбэки, но не настроенный по умолчанию ответ с отказом доступа, из-за чего гейт, настроенный с  Response::denyAsNotFound() , откатывался к дефолту фреймворка (#61087)
  •  Str::substrReplace()  выбрасывал  TypeError  при передаче аргументов-массивов после того, как в результате переработки поддержки многобайтовых строк они стали передаваться напрямую в  mb_substr() . Вызовы с массивами теперь делегируются нативному  substr_replace()  в PHP (#61105)
  • Учитываются имена очередей на базе backed enum при отправке mailable-классов в очередь (#61066), драйверы очередей соответствуют фейку для enum-имен очередей (#61116),  MailFake  сохраняет разрешение очередей для отправляемых в очередь mailable (#61114), а массовые отправки в  DatabaseQueue  учитывают отправку после коммита (after-commit) (#60996)
  • Деактивация режима обслуживания на базе кэша между двумя обращениями middleware к кэшу приводила к  TypeError ; теперь middleware повторно проверяет состояние так же, как делал это для файлового драйвера (#61121)
  •  Container::call  оставлял записи в стеке сборки, если зависимость выбрасывала исключение, из-за чего последующие разрешения видоизменяли устаревший стек (#61041)
  • Типизированные геттеры кэша подставляли enum-ключ в сообщение о несоответствии типов, вызывая  Error  вместо предполагаемого  InvalidArgumentException  (#61056)
  • Файлы HEIC сообщали некорректные размеры (#61010),  LazyCollection::flip()  пропускает значения, которые не могут быть ключами массива (#61081), а атрибут  #[WithoutTimestamps]  проверяется, когда модель решает, нужно ли обновлять таймстампы (#61073)
  • Коллбэки повторных попыток (retry callbacks) в асинхронных HTTP-запросах получают HTTP-метод в качестве третьего аргумента, аналогично синхронным запросам, что ранее вызывало  ArgumentCountError  (#61106)
  • Непотоковые ресурсы отклоняются в телах фейковых HTTP-ответов (#61047), драйвер логов Cloud устанавливает таймаут сокета (#61065, #61082), а поддержка подписанных URL была скорректирована для Vapor (#61129)
  •  schedule:list  правильно конвертирует часовые пояса для диапазонов, шагов и wildcard-выражений cron (#60913),  Factory::insert()  обрабатывает нулевое количество (#60911), а сбой при логировании устаревшего кода (deprecation) больше не приводит к фатальной ошибке (#60907)
  • Исправлены аннотации типов для  Arr::prependKeysWith()  (#61034),  Str::numbers()  (#61053),  getMigrationBatches()  (#60973),  getRememberToken()  (#61067), регистратора связывания маршрутов (route binding registrar) (#61124) и  ColumnDefinition::unsigned()  (#61123)
  • Поддержка  brick/math  ^0.19 (#61133)

 Ссылки