Skip to content

升级指南

高影响更改

中等影响更改

从 8.x 升级到 9.0

预计升级时间:30 分钟

lightbulb

我们尝试记录每一个可能的破坏性更改。由于这些破坏性更改有些在框架的偏僻部分,只有一部分更改可能会影响到您的应用程序。想节省时间?您可以使用 Laravel Shift 来帮助自动化您的应用程序升级。

更新依赖

影响可能性:高

PHP 8.0.2 必需

Laravel 现在要求 PHP 8.0.2 或更高版本。

Composer 依赖

您应该在应用程序的 composer.json 文件中更新以下依赖:

  • laravel/framework 更新为 ^9.0
  • nunomaduro/collision 更新为 ^6.1

此外,请将 facade/ignition 替换为 "spatie/laravel-ignition": "^1.0",并将 pusher/pusher-php-server(如果适用)替换为 "pusher/pusher-php-server": "^5.0" 在您的应用程序的 composer.json 文件中。

此外,以下第一方包已获得新主要版本以支持 Laravel 9.x。如果适用,您应该在升级之前阅读它们各自的升级指南:

最后,检查您应用程序中使用的任何其他第三方包,并验证您使用的是适合 Laravel 9 支持的正确版本。

PHP 返回类型

PHP 正在逐步过渡到要求 PHP 方法(如 offsetGetoffsetSet 等)定义返回类型。鉴于此,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

应用程序

Application 合同

影响可能性:低

Illuminate\Contracts\Foundation\Application 接口的 storagePath 方法已更新为接受 $path 参数。如果您正在实现此接口,您应该相应地更新您的实现:

php
public function storagePath($path = '');

同样,Illuminate\Foundation\Application 类的 langPath 方法已更新为接受 $path 参数:

php
public function langPath($path = '');

异常处理程序 ignore 方法

影响可能性:低

异常处理程序的 ignore 方法现在是 public 而不是 protected。此方法不包含在默认应用程序骨架中;但是,如果您手动定义了此方法,您应该将其可见性更新为 public

php
public function ignore(string $class);

异常处理程序合同绑定

影响可能性:非常低

以前,为了覆盖默认的 Laravel 异常处理程序,自定义实现通过 \App\Exceptions\Handler::class 类型绑定到服务容器中。然而,您现在应该使用 \Illuminate\Contracts\Debug\ExceptionHandler::class 类型绑定自定义实现。

Blade

懒惰集合和 $loop 变量

影响可能性:低

在 Blade 模板中迭代 LazyCollection 实例时,$loop 变量不再可用,因为访问此变量会导致整个 LazyCollection 被加载到内存中,从而使懒惰集合的使用在这种情况下毫无意义。

Checked / Disabled / Selected Blade 指令

影响可能性:低

新的 @checked@disabled@selected Blade 指令可能与同名的 Vue 事件冲突。您可以使用 @@ 来转义指令以避免此冲突:@@selected

集合

Enumerable 合同

影响可能性:低

Illuminate\Support\Enumerable 合同现在定义了一个 sole 方法。如果您手动实现此接口,您应该更新您的实现以反映此新方法:

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

reduceWithKeys 方法

reduceWithKeys 方法已被删除,因为 reduce 方法提供了相同的功能。您可以简单地更新您的代码以调用 reduce 而不是 reduceWithKeys

reduceMany 方法

reduceMany 方法已重命名为 reduceSpread,以与其他类似方法保持命名一致。

容器

Container 合同

影响可能性:非常低

Illuminate\Contracts\Container\Container 合同已收到两个方法定义:scopedscopedIf。如果您手动实现此合同,您应该更新您的实现以反映这些新方法。

ContextualBindingBuilder 合同

影响可能性:非常低

Illuminate\Contracts\Container\ContextualBindingBuilder 合同现在定义了一个 giveConfig 方法。如果您手动实现此接口,您应该相应地更新您的实现:

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

数据库

Postgres "Schema" 配置

影响可能性:中等

用于配置 Postgres 连接搜索路径的 schema 配置选项应重命名为 search_path,在您应用程序的 config/database.php 配置文件中。

Schema Builder registerCustomDoctrineType 方法

影响可能性:低

registerCustomDoctrineType 方法已从 Illuminate\Database\Schema\Builder 类中删除。您可以使用 DB facade 上的 registerDoctrineType 方法,或者在 config/database.php 配置文件中注册自定义 Doctrine 类型。

Eloquent

自定义类型和 null

影响可能性:中等

在 Laravel 的先前版本中,如果将 cast 属性设置为 null,则自定义 cast 类的 set 方法不会被调用。然而,这种行为与 Laravel 文档不一致。在 Laravel 9.x 中,cast 类的 set 方法将被调用,并将 null 作为提供的 $value 参数。因此,您应该确保您的自定义 casts 能够充分处理这种情况:

php
/**
 * 准备给定值以进行存储。
 *
 * @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('给定值不是 Address 实例。');
    }

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

属于多的 firstOrNewfirstOrCreateupdateOrCreate 方法

影响可能性:中等

belongsToMany 关系的 firstOrNewfirstOrCreateupdateOrCreate 方法都接受一个属性数组作为其第一个参数。在 Laravel 的先前版本中,此属性数组与“pivot”/中间表进行比较以查找现有记录。

然而,这种行为是意外的,通常是不希望的。相反,这些方法现在将属性数组与相关模型的表进行比较:

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

此外,firstOrCreate 方法现在接受一个 $values 数组作为其第二个参数。如果相关模型尚不存在,则此数组将与方法的第一个参数($attributes)合并。这一更改使此方法与其他关系类型提供的 firstOrCreate 方法保持一致:

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

touch 方法

影响可能性:低

touch 方法现在接受一个要触发的属性。如果您之前覆盖了此方法,您应该更新您的方法签名以反映此新参数:

php
public function touch($attribute = null);

加密

Encrypter 合同

影响可能性:低

Illuminate\Contracts\Encryption\Encrypter 合同现在定义了一个 getKey 方法。如果您手动实现此接口,您应该相应地更新您的实现:

php
public function getKey();

Facades

getFacadeAccessor 方法

影响可能性:低

getFacadeAccessor 方法必须始终返回一个容器绑定键。在 Laravel 的先前版本中,此方法可以返回一个对象实例;然而,这种行为不再受支持。如果您编写了自己的 facades,您应该确保此方法返回一个容器绑定字符串:

php
/**
 * 获取组件的注册名称。
 *
 * @return string
 */
protected static function getFacadeAccessor()
{
    return Example::class;
}

文件系统

FILESYSTEM_DRIVER 环境变量

影响可能性:低

FILESYSTEM_DRIVER 环境变量已重命名为 FILESYSTEM_DISK,以更准确地反映其用法。此更改仅影响应用程序骨架;但是,如果您希望,可以更新您自己应用程序的环境变量以反映此更改。

"Cloud" 磁盘

影响可能性:低

cloud 磁盘配置选项在 2020 年 11 月从默认应用程序骨架中删除。此更改仅影响应用程序骨架。如果您在应用程序中使用 cloud 磁盘,您应该在自己的应用程序骨架中保留此配置值。

Flysystem 3.x

影响可能性:高

Laravel 9.x 已从 Flysystem 1.x 迁移到 3.x。在底层,Flysystem 驱动所有由 Storage facade 提供的文件操作方法。鉴于此,您的应用程序中可能需要进行一些更改;但是,我们已尽力使此过渡尽可能无缝。

驱动程序先决条件

在使用 S3、FTP 或 SFTP 驱动程序之前,您需要通过 Composer 包管理器安装适当的包:

  • Amazon S3: composer require -W league/flysystem-aws-s3-v3 "^3.0"
  • FTP: composer require league/flysystem-ftp "^3.0"
  • SFTP: composer require league/flysystem-sftp-v3 "^3.0"

覆盖现有文件

写操作如 putwritewriteStream 现在默认覆盖现有文件。如果您不想覆盖现有文件,您应该在执行写操作之前手动检查文件是否存在。

写入异常

写操作如 putwritewriteStream 在写入操作失败时不再抛出异常。相反,返回 false。如果您希望保留之前的行为(抛出异常),您可以在文件系统磁盘的配置数组中定义 throw 选项:

php
'public' => [
    'driver' => 'local',
    // ...
    'throw' => true,
],

读取缺失文件

尝试从不存在的文件中读取现在返回 null。在 Laravel 的先前版本中,会抛出 Illuminate\Contracts\Filesystem\FileNotFoundException

删除缺失文件

尝试 delete 不存在的文件现在返回 true

缓存适配器

Flysystem 不再支持“缓存适配器”。因此,它们已从 Laravel 中删除,任何相关配置(例如磁盘配置中的 cache 键)可以删除。

自定义文件系统

注册自定义文件系统驱动程序所需的步骤发生了轻微变化。因此,如果您定义了自己的自定义文件系统驱动程序,或者使用定义自定义驱动程序的包,您应该更新您的代码和依赖项。

例如,在 Laravel 8.x 中,自定义文件系统驱动程序可能如下注册:

php
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 的实例:

php
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
    );
});

SFTP 私钥-公钥密码短语

如果您的应用程序使用 Flysystem 的 SFTP 适配器和私钥-公钥身份验证,则用于解密私钥的 password 配置项应重命名为 passphrase

辅助函数

data_get 辅助函数和可迭代对象

影响可能性:非常低

以前,data_get 辅助函数可用于检索数组和 Collection 实例上的嵌套数据;然而,此辅助函数现在可以检索所有可迭代对象上的嵌套数据。

str 辅助函数

影响可能性:非常低

Laravel 9.x 现在包含一个全局 str 辅助函数。如果您在应用程序中定义了全局 str 辅助函数,您应该重命名或删除它,以免与 Laravel 自己的 str 辅助函数冲突。

when / unless 方法

影响可能性:中等

正如您所知,whenunless 方法在框架中的各种类中提供。这些方法可用于在第一个参数的布尔值为 truefalse 时有条件地执行操作:

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

因此,在 Laravel 9.x 中,传递给 whenunless 方法的闭包将被执行,并且闭包返回的值将被视为用于 whenunless 方法的布尔值:

php
$collection->when(function ($collection) {
    // 此闭包被执行...
    return false;
}, function ($collection) {
    // 不执行,因为第一个闭包返回了 "false"...
    $collection->merge([1, 2, 3]);
});

HTTP 客户端

默认超时

影响可能性:中等

HTTP 客户端 现在的默认超时为 30 秒。换句话说,如果服务器在 30 秒内没有响应,将抛出异常。以前,HTTP 客户端没有配置默认超时长度,导致请求有时“挂起”无限期。

如果您希望为特定请求指定更长的超时,可以使用 timeout 方法:

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

HTTP Fake 和中间件

影响可能性:低

以前,Laravel 在“伪造” HTTP 客户端 时不会执行任何提供的 Guzzle HTTP 中间件。然而,在 Laravel 9.x 中,即使在伪造 HTTP 客户端时,也会执行 Guzzle HTTP 中间件。

HTTP Fake 和依赖注入

影响可能性:低

在 Laravel 的先前版本中,调用 Http::fake() 方法不会影响注入到类构造函数中的 Illuminate\Http\Client\Factory 实例。然而,在 Laravel 9.x 中,Http::fake() 将确保通过依赖注入传递到其他服务的 HTTP 客户端返回伪造的响应。这种行为与其他 facade 和 fake 的行为更一致。

Symfony Mailer

影响可能性:高

Laravel 9.x 中最大的变化之一是从 SwiftMailer 迁移到 Symfony Mailer,后者自 2021 年 12 月起不再维护。然而,我们已尽力使此过渡对您的应用程序尽可能无缝。尽管如此,请彻底审查以下更改列表,以确保您的应用程序完全兼容。

驱动程序先决条件

要继续使用 Mailgun 传输,您的应用程序应要求 symfony/mailgun-mailersymfony/http-client Composer 包:

shell
composer require symfony/mailgun-mailer symfony/http-client

应从您的应用程序中删除 wildbit/swiftmailer-postmark Composer 包。相反,您的应用程序应要求 symfony/postmark-mailersymfony/http-client Composer 包:

shell
composer require symfony/postmark-mailer symfony/http-client

更新的返回类型

Illuminate\Mail\Mailer 上的 sendhtmlrawplain 方法不再返回 void。相反,返回一个 Illuminate\Mail\SentMessage 的实例。此对象包含一个 Symfony\Component\Mailer\SentMessage 的实例,可以通过 getSymfonySentMessage 方法访问,或通过动态调用对象上的方法访问。

重命名的 "Swift" 方法

各种与 SwiftMailer 相关的方法(其中一些未记录)已重命名为其 Symfony Mailer 对应的方法。例如,withSwiftMessage 方法已重命名为 withSymfonyMessage

php
// 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'
    );
});
exclamation

请彻底审查 Symfony Mailer 文档 以了解与 Symfony\Component\Mime\Email 对象的所有可能交互。

以下列表包含更全面的重命名方法概述。许多这些方法是用于直接与 SwiftMailer / Symfony Mailer 交互的低级方法,因此在大多数 Laravel 应用程序中可能不常用:

php
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 文档中未记录:

php
// 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');

生成的消息 ID

SwiftMailer 提供了定义自定义域以包含在生成的消息 ID 中的能力,通过 mime.idgenerator.idright 配置选项。Symfony Mailer 不支持此功能。相反,Symfony Mailer 将根据发件人自动生成消息 ID。

MessageSent 事件更改

Illuminate\Mail\Events\MessageSent 事件的 message 属性现在包含 Symfony\Component\Mime\Email 的实例,而不是 Swift_Message 的实例。此消息表示发送前的电子邮件。

此外,MessageSent 事件中添加了一个新属性 sent。此属性包含 Illuminate\Mail\SentMessage 的实例,并包含有关已发送电子邮件的信息,例如消息 ID。

强制重新连接

不再可能强制传输重新连接(例如,当邮件发送器通过守护进程运行时)。相反,Symfony Mailer 将尝试自动重新连接到传输,并在重新连接失败时抛出异常。

SMTP 流选项

不再支持为 SMTP 传输定义流选项。相反,您必须直接在配置中定义相关选项(如果支持)。例如,要禁用 TLS 对等验证:

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

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

要了解更多可用的配置选项,请查看 Symfony Mailer 文档

exclamation

尽管上面的示例中提到,通常不建议禁用 SSL 验证,因为这会引入“中间人”攻击的可能性。

SMTP auth_mode

mail 配置文件中定义 SMTP auth_mode 不再是必需的。身份验证模式将在 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\SerializableClosureFactoryIlluminate\Queue\SerializableClosure 类已被删除。如果您直接与 opis/closure 库交互或使用任何已删除的类,您可以使用 Laravel Serializable Closure 代替。

失败作业提供者 flush 方法

影响可能性:低

Illuminate\Queue\Failed\FailedJobProviderInterface 接口定义的 flush 方法现在接受一个 $hours 参数,该参数确定失败作业在 queue:flush 命令被清除之前必须多旧(以小时为单位)。如果您手动实现 FailedJobProviderInterface,您应该确保您的实现更新以反映此新参数:

php
public function flush($hours = null);

会话

getSession 方法

影响可能性:低

Symfony\Component\HttpFoundaton\Request 类(Laravel 自己的 Illuminate\Http\Request 类扩展)提供了一个 getSession 方法来获取当前会话存储处理程序。Laravel 并未记录此方法,因为大多数 Laravel 应用程序通过 Laravel 自己的 session 方法与会话交互。

getSession 方法以前返回 Illuminate\Session\Storenull;然而,由于 Symfony 6.x 版本强制要求返回类型为 Symfony\Component\HttpFoundation\Session\SessionInterface,因此 getSession 现在正确返回 SessionInterface 实现,或者在没有可用会话时抛出 \Symfony\Component\HttpFoundation\Exception\SessionNotFoundException 异常。

测试

assertDeleted 方法

影响可能性:中等

所有对 assertDeleted 方法的调用应更新为 assertModelMissing

受信任的代理

影响可能性:低

如果您通过将现有应用程序代码导入全新的 Laravel 9 应用程序骨架来将 Laravel 8 项目升级到 Laravel 9,您可能需要更新应用程序的“受信任代理”中间件。

在您的 app/Http/Middleware/TrustProxies.php 文件中,将 use Fideloper\Proxy\TrustProxies as Middleware 更新为 use Illuminate\Http\Middleware\TrustProxies as Middleware

接下来,在 app/Http/Middleware/TrustProxies.php 中,您应该更新 $headers 属性定义:

php
// 之前...
protected $headers = Request::HEADER_X_FORWARDED_ALL;

// 之后...
protected $headers =
    Request::HEADER_X_FORWARDED_FOR |
    Request::HEADER_X_FORWARDED_HOST |
    Request::HEADER_X_FORWARDED_PORT |
    Request::HEADER_X_FORWARDED_PROTO |
    Request::HEADER_X_FORWARDED_AWS_ELB;

最后,您可以从应用程序中删除 fideloper/proxy Composer 依赖:

shell
composer remove fideloper/proxy

验证

表单请求 validated 方法

影响可能性:低

表单请求提供的 validated 方法现在接受 $key$default 参数。如果您手动覆盖此方法的定义,您应该更新方法的签名以反映这些新参数:

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

password 规则

影响可能性:中等

验证给定输入值是否与经过身份验证的用户当前密码匹配的 password 规则已重命名为 current_password

未验证的数组键

影响可能性:中等

在 Laravel 的先前版本中,您需要手动指示 Laravel 的验证器从其返回的“验证”数据中排除未验证的数组键,尤其是在与未指定允许键列表的 array 规则结合使用时。

然而,在 Laravel 9.x 中,即使在未通过 array 规则指定允许键的情况下,未验证的数组键也始终会从“验证”数据中排除。通常,这种行为是最预期的行为,之前的 excludeUnvalidatedArrayKeys 方法仅在 Laravel 8.x 中添加,以保持向后兼容性。

尽管不推荐,您可以通过在应用程序的一个服务提供者的 boot 方法中调用新的 includeUnvalidatedArrayKeys 方法来选择加入之前的 Laravel 8.x 行为:

php
use Illuminate\Support\Facades\Validator;

/**
 * 注册任何应用程序服务。
 *
 * @return void
 */
public function boot()
{
    Validator::includeUnvalidatedArrayKeys();
}

杂项

我们还鼓励您查看 laravel/laravel GitHub 仓库 中的更改。虽然许多更改不是必需的,但您可能希望将这些文件与您的应用程序保持同步。这些更改中的一些将在此升级指南中涵盖,但其他更改,例如配置文件或注释的更改,将不会。您可以轻松地使用 GitHub 比较工具 查看更改,并选择哪些更新对您重要。