Руководство по обновлению до Laravel 9

laravel 9 upgrade

Мы постарались задокументировать все возможные критические изменения. Поскольку некоторые из них находятся в малоизвестных частях фреймворка, только часть этих изменений может повлиять на ваше приложение. Приблизительное время обновления: 30 минут.

Требуется PHP 8.0.2

Минимальная поддерживаемая версия PHP Laravel теперь 8.0.2.

PHP Типы возвращаемого значения

Язык начинает требовать определения типа возвращаемого значения для методов, таких как offsetGet, offsetSet, и т.п. По этой причине Laravel 9 реализовал эти типы в своей кодовой базе. Как правило, это не должно влиять на написанный пользователем код, однако, если вы переопределяете один из этих методов, расширяя базовые классы Laravel, то вам нужно будет добавить типы возвращаемых значений в код вашего приложения или пакета:

  • count(): int
  • getIterator(): Traversable
  • getSize(): int
  • jsonSerialize(): array
  • offsetExists($key): bool
  • offsetGet($key): mixed
  • offsetSet($key, $value): void
  • offsetUnset($key): void

Кроме того, типа возвращаемых значений были добавлены в методы, реализующие PHP SessionHandlerInterface. Опять же, маловероятно, что это изменение повлияет на ваше приложение или пакет:

  • open($savePath, $sessionName): bool
  • close(): bool
  • read($sessionId): string|false
  • write($sessionId, $data): bool
  • destroy($sessionId): bool
  • gc($lifetime): int

Зависимости Composer

Вы должны обновить следующие зависимости в файле composer.json вашего приложения:

  • laravel/framework к ^9.0
  • nunomaduro/collision к ^6.0

Кроме того, замените facade/ignition на "spatie/laravel-ignition": "^1.0" в этом же файле.

Не забудьте также проверить все сторонние пакеты, используемые в вашем приложении, и убедитесь, что они поддерживают Laravel 9.

Контракт Application

Метод storagePath интерфейса Illuminate\Contracts\Foundation\Application был обновлен для использования аргумента $path. Если вы реализуете этот интерфейс, вы должны соответствующим образом обновить свою реализацию:

public function storagePath($path = '');

Метод ignore обработчика исключений

Метод ignore теперь public вместо protected. Этот метод не включен в дефолтный каркас приложения. Однако, если вы задавали этот метод вручную, то должны обновить его видимость до public:

public function ignore(string $class);

Blade — Ленивые коллекции и переменная $loop

При итерации экземпляра LazyCollection в Blade-шаблоне переменная $loop больше недоступна, так как доступ к этой переменной приводит к полной загрузке LazyCollection в память, что делает использование ленивых коллекций бессмысленным в данном сценарии.

Collections — Контракт Enumerable

Контракт Illuminate\Support\Enumerable теперь определяет метод sole. Если вы реализуете этот интерфейс вручную, то обновите свою реализацию, добавив новый метод:

public function sole($key = null, $operator = null, $value = null);

Контракт Container

Контракт Illuminate\Contracts\Container\Container получил два новых метода: scoped и scopedIf. Если вы реализуете этот контракт вручную, то обновите свою реализацию, добавив их.

Контракт ContextualBindingBuilder

Контракт Illuminate\Contracts\Container\ContextualBindingBuilder получил новый метод giveConfig. Если вы реализуете этот контракт вручную, то обновите свою реализацию, добавив его.

public function giveConfig($key, $default = null);

Postgres конфигурация «Schema»

Параметр конфигурации schema, используемый для настройки путей поиска соединений Postgres в файле конфигурации config/database.php вашего приложения, должен быть переименован в search_path.

Кастомные касты и null

В предыдущих версиях Laravel метод set кастомных кастов не вызывался, если для атрибута приведения было установлено значение null. Однако такое поведение не соответствовало документации Laravel. В Laravel 9.x метод set класса каста будет вызываться с null в аргументе $value. Поэтому вы должны убедиться, что ваши касты смогут правильно отработать этот сценарий:

/**
 * Prepare the given value for storage.
 *
 * @param  \Illuminate\Database\Eloquent\Model  $model
 * @param  string  $key
 * @param  AddressModel  $value
 * @param  array  $attributes
 * @return array
 */
public function set($model, $key, $value, $attributes)
{
    if (! $value instanceof AddressModel) {
        throw new InvalidArgumentException('The given value is not an Address instance.');
    }

    return [
        'address_line_one' => $value->lineOne,
        'address_line_two' => $value->lineTwo,
    ];
}

Методы Belongs To Many firstOrNew, firstOrCreate и updateOrCreate

Методы отношения belongsToMany: firstOrNew, firstOrCreate и updateOrCreate принимают массив атрибутов в качестве первого аргумента. В предыдущих версиях Laravel этот массив сравнивался со «сводной»/промежуточной таблицей для существующих записей.

Однако такое поведение было неожидаемым и, как правило, нежелательным. Вместо этого эти методы теперь сравнивают массив атрибутов с таблицей связанной модели:

$user->roles()->updateOrCreate([
    'name' => 'Administrator',
]);

Кроме того, метод firstOrCreate теперь принимает массив $values в качестве второго аргумента. Этот массив будет объединен с первым аргументом метода ( $attributes) при создании связанной модели, если она еще не существует. Это изменение делает этот метод совместимым с методом firstOrCreate, предлагаемыми другими типами отношений:

$user->roles()->firstOrCreate([
    'name' => 'Administrator',
], [
    'created_by' => $user->id,
]);

Метод touch

Теперь метод touch принимает атрибут. Если вы ранее переопределяли этот метод, то вам следует обновить его, добавив новый аргумент:

public function touch($attribute = null);

Контракт Encrypter

В контракт Illuminate\Contracts\Encryption\Encrypter добавлен метод getKey. Если вы реализуете этот интерфейс вручную, то обновить свою реализацию:

public function getKey();

Фасады — метод getFacadeAccessor

Метод getFacadeAccessor всегда должен возвращать ключ привязки контейнера. В предыдущих версиях Laravel этот метод мог возвращать экземпляр объекта, однако такое поведение больше не поддерживается. Если вы создавали свои собственные фасады, то убедитесь, что этот метод возвращает строку привязки контейнера:

/**
 * Get the registered name of the component.
 *
 * @return string
 */
protected static function getFacadeAccessor()
{
    return Example::class;
}

Переменная окружения FILESYSTEM_DRIVER

Переменная FILESYSTEM_DRIVER была переименована в FILESYSTEM_DISK для более точного отражения ее назначения. Это изменение затрагивает только каркас приложения, однако вы можете обновить переменные окружения своего приложения, если хотите.

Flysystem 3.x

Laravel 9 переходит с Flysystem 1.x на 3.x. Под капотом Flysystem поддерживает все методы работы с файлами, предоставляемые фасадом Storage. В связи с этим в вашем приложении могут потребоваться некоторые изменения; однако мы постарались сделать этот переход максимально плавным.

Требования

Перед использованием драйверов S3 или SFTP необходимо установить соответствующий пакет через Composer:

Amazon S3
composer require --with-all-dependencies league/flysystem-aws-s3-v3 "^3.0"
SFTP
composer require league/flysystem-sftp-v3 "^3.0"

Перезапись существующих файлов

Операции записи put, write, writeStream теперь перезаписывают существующие файлы по умолчанию. Если вы не хотите перезаписывать существующие файлы, то вам следует вручную проверить существование файла перед выполнением операции записи.

Чтение несуществующих файлов

Попытка чтения из несуществующего файла теперь возвращает null. В предыдущих версиях Laravel выбрасывалось исключение Illuminate\Contracts\Filesystem\FileNotFoundException.

Удаление несуществующих файлов

Попытка применения метода delete к несуществующему файлу теперь возвращает true.

Кэшированные адаптеры

Flysystem больше не поддерживает «cached adapters». Поэтому они были удалены из Laravel, и любая соответствующая конфигурация (например, ключ cache в конфигурации диска) может быть удалена.

Кастомные файловые системы

Небольшие изменения были внесены в шаги, необходимые для регистрации кастомных драйверов файловой системы. Если вы создавали свои собственные драйверы или использовали пакеты, создающие таковые, то необходимо обновить свой код и зависимости. Например, в Laravel 8.x кастомный драйвер файловой системы может быть зарегистрирован следующим образом:

use Illuminate\Support\Facades\Storage;
use League\Flysystem\Filesystem;
use Spatie\Dropbox\Client as DropboxClient;
use Spatie\FlysystemDropbox\DropboxAdapter;

Storage::extend('dropbox', function ($app, $config) {
    $client = new DropboxClient(
        $config['authorization_token']
    );

    return new Filesystem(new DropboxAdapter($client));
});

А в Laravel 9.x колбэк метода Storage::extend должен возвращать экземпляр Illuminate\Filesystem\FilesystemAdapter напрямую:

use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\Filesystem;
use Spatie\Dropbox\Client as DropboxClient;
use Spatie\FlysystemDropbox\DropboxAdapter;

Storage::extend('dropbox', function ($app, $config) {
    $adapter = new DropboxAdapter(new DropboxClient(
        $config['authorization_token']
    ););

    return new FilesystemAdapter(
        new Filesystem($adapter, $config),
        $adapter,
        $config
    );
});

Хелперы

data_get & итерируемые объекты

Раньше хелпер data_get можно было использовать для извлечения вложенных данных только из массивов и экземпляров Collection. А теперь этот хелпер может извлекать вложенные данные из всех итерируемых объектов.

str

Laravel 9 теперь включает глобальныйх хелпер str. Если вы задаете глобальный хелпер str в своем приложении, то вы должны переименовать или удалить его, чтобы он не конфликтовал с основным Laravel-хелпером.

Методы when/unless

Как вы, возможно, знаете, методы when и unless предлагаются различными классами по всему фреймворку. Эти методы можно использовать для условного выполнения действия, если логическое значение первого аргумента метода оценивается как true или false:

$collection->when(true, function ($collection) {
    $collection->merge([1, 2, 3]);
});

Таким образом, в предыдущих версиях Laravel передача замыкания методам when или unless означала, что условная операция всегда будет выполняться, поскольку нечеткое сравнение с объектом замыкания (или любым другим объектом) всегда оценивается как true. Это часто приводило к неожиданным результатам, поскольку разработчики ожидали, что результат замыкания будет использоваться в качестве логического значения, определяющего, выполняется ли условное действие.

В Laravel 9 любые замыкания, переданные методам when или unless, будут выполнены, а значение, возвращаемое замыканием, будет считаться логическим значением, используемым методами when и unless:

$collection->when(function ($collection) {
    // Это замыкание выполняется...
    return false;
}, function ($collection) {
    // Не выполняется, так как первое замыкание вернуло false...
    $collection->merge([1, 2, 3]);
});

HTTP Client

Дефолтный таймаут

HTTP-клиент теперь имеет дефолтный тайм-аут в 30 секунд. Другими словами, если сервер не отвечает в течение 30 секунд, то будет выброшено исключение. Раньше дефолтного таймаута не было, из-за чего запросы иногда подвисали на неопределенное время.

Если вы хотите указать более длительный тайм-аут для запроса, то можете сделать это с помощью метода timeout:

$response = Http::timeout(120)->get(...);

Symfony Mailer

Одно из самых больших изменений в Laravel 9 это переход от SwiftMailer, который не поддерживается с декабря 2021 года, к Symfony Mailer. Однако мы постарались сделать этот переход как можно более плавным для ваших приложений. Внимательно ознакомьтесь со списком изменений ниже, чтобы убедиться, что ваше приложение полностью совместимо.

Требования

Пакет aws/aws-sdk-php больше не потребуется при использовании Amazon SES, и его можно удалить, если он не требуется для других частей вашего приложения. Вместо этого вашему приложению нужен пакет symfony/amazon-mailer:

composer require symfony/amazon-mailer

Чтобы продолжить использование Mailgun, вашему приложению потребуется пакет symfony/mailgun-mailer:

composer require symfony/mailgun-mailer

Пакет wildbit/swiftmailer-postmark должен быть удален из вашего приложения. Вместо этого вашему приложению нужен пакет symfony/postmark-mailer:

composer require symfony/postmark-mailer

Обновленные типы возвращаемых значений

Методы send, html, text и plain больше не возвращают количество получателей. Вместо этого возвращается экземпляр Illuminate\Mail\SentMessage. Этот объект содержит экземпляр Symfony\Component\Mailer\SentMessage, доступный через метод getSymfonySentMessage.

Переименованные «Swift» методы

Различные методы, связанные со SwiftMailer, некоторые из которых не были задокументированы, сейчас переименованы в их аналоги Symfony Mailer. Например, метод withSwiftMessage переименован в withSymfonyMessage:

// Laravel 8.x...
$this->withSwiftMessage(function ($message) {
    $message->getHeaders()->addTextHeader(
        'Custom-Header', 'Header Value'
    );
});

// Laravel 9.x...
use Symfony\Component\Mime\Email;

$this->withSymfonyMessage(function (Email $message) {
    $message->getHeaders()->addTextHeader(
        'Custom-Header', 'Header Value'
    );
});

Пожалуйста, внимательно изучите документацию Symfony Mailer для всех возможных взаимодействий с объектом Symfony\Component\Mime\Email.

Нижерасположенный список содержит более подробный обзор переименованных методов. Многие из них являются низкоуровневыми, используемыми для прямого взаимодействия со SwiftMailer/Symfony Mailer, поэтому могут не использоваться в большинстве приложений Laravel:

Message::getSwiftMessage();
Message::getSymfonyMessage();

Mailable::withSwiftMessage($callback);
Mailable::withSymfonyMessage($callback);

MailMessage::withSwiftMessage($callback);
MailMessage::withSymfonyMessage($callback);

Mailer::getSwiftMailer();
Mailer::getSymfonyTransport();

Mailer::setSwiftMailer($swift);
Mailer::setSymfonyTransport(TransportInterface $transport);

MailManager::createTransport($config);
MailManager::createSymfonyTransport($config);

Прокси-методы Illuminate\Mail\Message

Обычно отсутствующие методы в Illuminate\Mail\Message проксировались базовому Swift_Message. Но теперь, вместо этого, они будут проксироваться в Symfony\Component\Mime\Email. Таким образом, любой код, который ранее полагался на SwiftMailer, должен быть обновлен до соответствующих аналогов Symfony Mailer.

Опять же, многие приложения могут и не взаимодействовать с этими методами, поскольку они не описаны в документации Laravel:

// Laravel 8.x...
$message
    ->setFrom('taylor@laravel.com')
    ->setTo('example@example.org')
    ->setSubject('Order Shipped')
    ->setBody('<h1>HTML</h1>', 'text/html')
    ->addPart('Plain Text', 'text/plain');

// Laravel 9.x...
$message
    ->from('taylor@laravel.com')
    ->to('example@example.org')
    ->subject('Order Shipped')
    ->html('<h1>HTML</h1>')
    ->text('Plain Text');

Генерируемые идентификаторы сообщений

SwiftMailer предлагал возможность задать собственный домен для включения в сгенерированные идентификаторы сообщений с помощью параметра конфигурации mime.idgenerator.idright. Но это не поддерживается Symfony Mailer. Вместо этого он автоматически генерирует идентификатор сообщения на основе отправителя.

Принудительные переподключения

Теперь больше невозможно принудительно переподключиться к транспорту (например, когда мейлер работает через демона). Вместо этого Symfony Mailer попытается автоматически переподключиться к транспорту и выбросит исключение, если это не удастся.

Параметры SMTP-stream

Определение параметров потока для SMTP больше не поддерживается. Вместо этого вы должны задать соответствующие параметры непосредственно в конфигурации, если они поддерживаются. Например, чтобы отключить проверку пира TLS:

'smtp' => [
    // Laravel 8.x...
    'stream' => [
        'ssl' => [
            'verify_peer' => false,
        ],
    ],

    // Laravel 9.x...
    'verify_peer' => false,
],

Чтобы узнать больше о доступных параметрах конфигурации, ознакомьтесь с документацией Symfony Mailer.

Несмотря на вышеприведенный пример, не рекомендуется отключать проверку SSL, поскольку это создает возможность атак типа «man-in-the-middle».

SMTP auth_mode

Определение SMTP auth_mode в файле конфигурации mail больше не требуется. Режим аутентификации будет автоматически согласован между Symfony Mailer и SMTP-сервером.

Неудавшиеся отправления

Теперь невозможно получить список получателей, которым не получилось отправить письмо. Вместо этого, , если сообщение не будет отправлено, то выбросится исключение Symfony\Component\Mailer\Exception\TransportExceptionInterface. Вместо того, чтобы полагаться на получение невалидных адресов электронной почты после отправки сообщения, мы рекомендуем вам проверять их перед отправкой сообщения.

Пакеты

Каталог lang

В новых приложениях Laravel папка resources/lang теперь находится в корневой папке проекта (lang). Если ваш пакет публикует языковые файлы в этот каталог, то вы должны убедиться, что ваш пакет использует app()->langPath(), а не захардкоженный старый путь.

Очереди

Библиотека opis/closure

Зависимость Laravel от opis/closure была заменена на laravel/serializable-closure. Это не должно привести к каким-либо критическим изменениям в вашем приложении, если только вы не взаимодействуете с библиотекой opis/closure напрямую. Кроме того, были удалены классы Illuminate\Queue\SerializableClosureFactory и Illuminate\Queue\SerializableClosure, ранее объявленные устаревшими. Если вы взаимодействуете с библиотекой opis/closure напрямую или используете любой из удаленных классов, то вместо этого используйте Laravel Serializable Closure.

Метод failed провайдера Failed Job

Метод flush, заданный интерфейсом Illuminate\Queue\Failed\FailedJobProviderInterface, теперь принимает аргумент $age, определяющий, сколько в днях должно просуществовать невыполненное задание, прежде чем оно будет сброшено командой queue:flush. Если вы реализуете вручную интерфейс FailedJobProviderInterface, то убедитесь, что ваша реализация обновлена для использования нового аргумента:

public function flush($age = null);

Сессия

Метод getSession

Класс Symfony\Component\HttpFoundaton\Request, расширяемым Laravel-классом Illuminate\Http\Request, предлагает метод getSession для получения текущего обработчика хранилища сессии. Этот метод не задокументирован, так как большинство приложений Laravel взаимодействуют с сессией через метод session.

Ранее метод getSession возвращал экземпляр Illuminate\Session\Store или null. Однако из-за того, что в релизе Symfony 6.x используется тип возвращаемого значения Symfony\Component\HttpFoundation\Session\SessionInterface, теперь метод getSession корректно возвращает SessionInterface или выбрасывает исключение \Symfony\Component\HttpFoundation\Exception\SessionNotFoundException, если сессия недоступна.

Тестирование

Метод assertDeleted

Все вызовы метода assertDeleted должны быть заменены на assertModelMissing.

Доверенные прокси

Если вы обновляете свой проект c Laravel 8 до Laravel 9, импортируя имеющийся код в совершенно новый каркас приложения на Laravel 9, то вам может потребоваться обновить мидлвар «trusted proxy».

В файле app/Http/Middleware/TrustProxies.php обновите use Fideloper\Proxy\TrustProxies as Middleware до use Illuminate\Http\Middleware\TrustProxies as Middleware.

Валидация

Метод validated из FormRequest

Метод validated теперь принимает аргументы $key и $default. Если вы вручную переопределяли этот метод, то вам следует обновить его:

public function validated($key = null, $default = null)

Правило password

Правило password, проверяющее соответствие введенного пароля текущему, было переименовано в current_password.

Непроверенные ключи массива

В предыдущих версиях Laravel приходилось вручную указывать валидатору исключать непроверенные ключи массива из проверенных данных, особенно в сочетании с правилом array, в котором не указан список разрешенных ключей.

В Laravel 9 непроверенные ключи теперь всегда исключаются из проверенных данных, даже если в правиле array не указаны разрешенные ключи. Как правило, такое поведение и является наиболее ожидаемым. Предыдущий метод excludeUnvalidatedArrayKeys был добавлен в Laravel 8 только как временная мера для сохранения обратной совместимости.

Хотя это и не рекомендуется, но вы можете вернуться к предыдущего поведению, как в Laravel 8.x, вызвав новый метод includeUnvalidatedArrayKeys в методе boot одного из провайдеров вашего приложения:

use Illuminate\Support\Facades\Validator;

/**
 * Register any application services.
 *
 * @return void
 */
public function boot()
{
    Validator::includeUnvalidatedArrayKeys();
}

Разное

Мы также рекомендуем вам просмотреть все изменения в репозитории laravel/laravel на GitHub. Некоторые из этих изменений описаны в данном руководстве по обновлению, некоторые, такие как изменения в файлах конфигурации или комментарии, не включены. Вы можете легко просмотреть их с помощью инструмента сравнения GitHub и выбрать, какие обновления важны именно для вас.

Автор: Laravel
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.