Команда Laravel выпустила версию v13.33.0 с поддержкой тегов для мемоизированного кэша, атрибутом модели  Refreshes , который перезагружает сгенерированные колонки после записи, и многим другим:

#Мемоизированный кэш с тегами

Joost de Bruijn добавил метод  tags()  в хранилище мемоизированного кэша, так что  Cache::memo()  теперь работает с кэшем, содержащим теги. Чтения в рамках одного запроса обращаются к бэкенду кэша один раз, а после этого берутся из памяти:

// Two calls, one round trip to Redis
Cache::memo()->tags(['permissions'])->get("permissions:{$user->id}");
Cache::memo()->tags(['permissions'])->get("permissions:{$user->id}");
 
// Writes go to the tagged cache and drop the memoized copy
Cache::memo()->tags(['permissions'])->put("permissions:{$user->id}", $permissions);
 
// Flushing the tag also clears the memoized entries
Cache::memo()->tags(['permissions'])->flush();
 

До этого релиза тот же паттерн требовал ручного оборачивания вызова кэша с тегами во второй поиск через  Cache::store('array') . Laravel News рассказывали о версии без тегов, когда она появилась в Laravel 12.9.

Читайте подробнее в статье Memoize Tagged Cache Reads in Laravel. Дополнительные сведения см. в PR #61593.

#Обновление атрибутов модели после записи

Caleb White добавил атрибут  #[Refreshes]  (и соответствующее свойство  $refreshes ), который перезагружает определенные колонки из базы данных после создания или обновления модели. Он предназначен для колонок  virtualAs ,  storedAs  и других значений, устанавливаемых базой данных, которые в противном случае отсутствуют или являются устаревшими в модели в памяти до вызова  refresh() :

use Illuminate\Database\Eloquent\Attributes\Refreshes;
 
#[Refreshes('name')]
class User extends Model
{
    //
}
 
$user = User::create(['first_name' => 'Taylor', 'last_name' => 'Otwell']);
 
$user->name; // "Taylor Otwell" without calling refresh()
 

Обновление выполняет один запрос только для перечисленных колонок через соединение для записи. Атрибут принимает массив или список имен колонок в качестве аргументов переменной длины, а свойство  $refreshes  работает аналогично, если вы не используете атрибуты. Читайте подробнее в статье Refresh Generated Columns After Saving an Eloquent Model.

Дополнительные сведения см. в PR #61523.

#Сохранение реального NULL с помощью AsCollection и AsArrayObject

Zein Ahmad добавил опциональный режим  nullable()  для кастов  AsCollection  и  AsArrayObject . Присвоение значения  null  одному из таких кастов сохраняет JSON-строку  "null"  в колонке, из-за чего  whereNull()  никогда не находит строку. С флагом  nullable()  каст сохраняет настоящий database  NULL :

protected function casts(): array
{
    return [
        'items' => AsCollection::class,      // unchanged: stores "null"
        'meta' => AsCollection::nullable(),  // stores NULL
        'options' => AsArrayObject::nullable(),
    ];
}
 

Дополнительные сведения см. в PR #61596.

#Модификаторы inplace() и lock() для миграций индексов

Sander Muller добавил модификатор  inplace()  для операций с индексами и внешними ключами вместе с модификатором  lock() , так что миграция для MySQL может запросить  ALGORITHM=INPLACE  и  LOCK=NONE  вместо выполнения блокирующей перестройки таблицы:

Schema::table('video_sessions', function (Blueprint $table) {
    $table->index('foo', 'foo_index')->inplace()->lock('none');
});
 

Этот модификатор работает со следующими методами:

  •  index() 
  •  unique() 
  •  primary() 
  •  fullText() 
  •  spatialIndex() 
  •  foreign() 

Он повторяет поведение модификатора  instant() , который уже доступен при определении колонок.

Дополнительные сведения см. в PR #61602.

#Атрибуты Form Request из родительских классов

Md Sukkur Ali исправил работу атрибутов в формах запросов (form requests), чтобы дочерний запрос наследовал их из родительского класса. PHP не наследует атрибуты, поэтому такие атрибуты, как  #[StopOnFirstFailure] , на общем базовом запросе игнорировались всеми подклассами:

#[StopOnFirstFailure]
#[FailOnUnknownFields]
abstract class ApiRequest extends FormRequest
{
    //
}
 
// Both attributes now apply
class StorePostRequest extends ApiRequest
{
    //
}
 

Дополнительные сведения см. в PR #61586.

#Отказ от завершения воркера при тайм-ауте

Jack Bayliss добавил флаг  Worker::$killOnTimeout . По умолчанию задача, превысившая тайм-аут, приводит к завершению всего процесса воркера, после чего supervisor запускает новый экземпляр. Если установить флаг в значение  false , воркер вместо этого выбросит исключение  TimeoutExceededException , что позволит задаче выполнить очистку, а воркеру — продолжить работу:

use Illuminate\Queue\Worker;
 
Worker::$killOnTimeout = false;
 

Предупреждение: задача, оборачивающая свое тело  handle()  в широкий блок  try/catch , может проглотить это исключение.

Связанный PR от Kieran Brown добавляет  Worker::killUsing()  — хук, который выполняется перед  SIGKILL , позволяя передать супервайзеру пользовательский код завершения (#61622).

Дополнительные сведения см. в PR #61591.

#Тестовые хелперы mockArtisan() и realArtisan()

Jenthe Noordsij добавил два тестовых хелпера, разделяющих две задачи  $this->artisan() . Метод  mockArtisan()  всегда возвращает  PendingCommand  для создания ожиданий, а  realArtisan()  всегда запускает команду и возвращает код завершения в виде  int , так что вам больше не нужно переключать  $mockConsoleOutput  для переключения между этими режимами:

$this->mockArtisan('inspire')
    ->expectsOutputToContain('Simplicity')
    ->assertExitCode(0);
 
$code = $this->realArtisan('migrate', ['--force' => true]);
 

Дополнительные сведения см. в PR #61588.

#Примечания к релизу

Полный список новых функций и обновлений представлен ниже, а разницу между версиями 13.32.0 и 13.33.0 можно посмотреть на GitHub. Следующие примечания к релизу взяты напрямую из списка изменений (changelog):

#v13.33.0