升级指南
高影响更改
中等影响更改
从 8.x 升级到 9.0
预计升级时间:30 分钟
NOTE
我们尝试记录每一个可能的破坏性更改。由于这些破坏性更改有些在框架的偏僻部分,只有一部分更改可能会影响到您的应用程序。想节省时间?您可以使用 Laravel Shift 来帮助自动化您的应用程序升级。
更新依赖
影响可能性:高
PHP 8.0.2 必需
Laravel 现在要求 PHP 8.0.2 或更高版本。
Composer 依赖
您应该在应用程序的 composer.json 文件中更新以下依赖:
laravel/framework更新为^9.0nunomaduro/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。如果适用,您应该在升级之前阅读它们各自的升级指南:
- Vonage 通知通道 (v3.0)(替代 Nexmo)
最后,检查您应用程序中使用的任何其他第三方包,并验证您使用的是适合 Laravel 9 支持的正确版本。
PHP 返回类型
PHP 正在逐步过渡到要求 PHP 方法(如 offsetGet、offsetSet 等)定义返回类型。鉴于此,Laravel 9 在其代码库中实现了这些返回类型。通常,这不会影响用户编写的代码;但是,如果您通过扩展 Laravel 的核心类来覆盖这些方法,您需要在自己的应用程序或包代码中添加这些返回类型:
count(): intgetIterator(): TraversablegetSize(): intjsonSerialize(): arrayoffsetExists($key): booloffsetGet($key): mixedoffsetSet($key, $value): voidoffsetUnset($key): void
此外,返回类型已添加到实现 PHP 的 SessionHandlerInterface 的方法中。同样,这一更改不太可能影响您自己的应用程序或包代码:
open($savePath, $sessionName): boolclose(): boolread($sessionId): string|falsewrite($sessionId, $data): booldestroy($sessionId): boolgc($lifetime): int
应用程序
Application 合同
影响可能性:低
Illuminate\Contracts\Foundation\Application 接口的 storagePath 方法已更新为接受 $path 参数。如果您正在实现此接口,您应该相应地更新您的实现:
public function storagePath($path = '');同样,Illuminate\Foundation\Application 类的 langPath 方法已更新为接受 $path 参数:
public function langPath($path = '');异常处理程序 ignore 方法
影响可能性:低
异常处理程序的 ignore 方法现在是 public 而不是 protected。此方法不包含在默认应用程序骨架中;但是,如果您手动定义了此方法,您应该将其可见性更新为 public:
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 方法。如果您手动实现此接口,您应该更新您的实现以反映此新方法:
public function sole($key = null, $operator = null, $value = null);reduceWithKeys 方法
reduceWithKeys 方法已被删除,因为 reduce 方法提供了相同的功能。您可以简单地更新您的代码以调用 reduce 而不是 reduceWithKeys。
reduceMany 方法
reduceMany 方法已重命名为 reduceSpread,以与其他类似方法保持命名一致。
容器
Container 合同
影响可能性:非常低
Illuminate\Contracts\Container\Container 合同已收到两个方法定义:scoped 和 scopedIf。如果您手动实现此合同,您应该更新您的实现以反映这些新方法。
ContextualBindingBuilder 合同
影响可能性:非常低
Illuminate\Contracts\Container\ContextualBindingBuilder 合同现在定义了一个 giveConfig 方法。如果您手动实现此接口,您应该相应地更新您的实现:
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 能够充分处理这种情况:
/**
* 准备给定值以进行存储。
*
* @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,
];
}属于多的 firstOrNew、firstOrCreate 和 updateOrCreate 方法
影响可能性:中等
belongsToMany 关系的 firstOrNew、firstOrCreate 和 updateOrCreate 方法都接受一个属性数组作为其第一个参数。在 Laravel 的先前版本中,此属性数组与“pivot”/中间表进行比较以查找现有记录。
然而,这种行为是意外的,通常是不希望的。相反,这些方法现在将属性数组与相关模型的表进行比较:
$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();Facades
getFacadeAccessor 方法
影响可能性:低
getFacadeAccessor 方法必须始终返回一个容器绑定键。在 Laravel 的先前版本中,此方法可以返回一个对象实例;然而,这种行为不再受支持。如果您编写了自己的 facades,您应该确保此方法返回一个容器绑定字符串:
/**
* 获取组件的注册名称。
*
* @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"
覆盖现有文件
写操作如 put、write 和 writeStream 现在默认覆盖现有文件。如果您不想覆盖现有文件,您应该在执行写操作之前手动检查文件是否存在。
写入异常
写操作如 put、write 和 writeStream 在写入操作失败时不再抛出异常。相反,返回 false。如果您希望保留之前的行为(抛出异常),您可以在文件系统磁盘的配置数组中定义 throw 选项:
'public' => [
'driver' => 'local',
// ...
'throw' => true,
],读取缺失文件
尝试从不存在的文件中读取现在返回 null。在 Laravel 的先前版本中,会抛出 Illuminate\Contracts\Filesystem\FileNotFoundException。
删除缺失文件
尝试 delete 不存在的文件现在返回 true。
缓存适配器
Flysystem 不再支持“缓存适配器”。因此,它们已从 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
);
});SFTP 私钥-公钥密码短语
如果您的应用程序使用 Flysystem 的 SFTP 适配器和私钥-公钥身份验证,则用于解密私钥的 password 配置项应重命名为 passphrase。
辅助函数
data_get 辅助函数和可迭代对象
影响可能性:非常低
以前,data_get 辅助函数可用于检索数组和 Collection 实例上的嵌套数据;然而,此辅助函数现在可以检索所有可迭代对象上的嵌套数据。
str 辅助函数
影响可能性:非常低
Laravel 9.x 现在包含一个全局 str 辅助函数。如果您在应用程序中定义了全局 str 辅助函数,您应该重命名或删除它,以免与 Laravel 自己的 str 辅助函数冲突。
when / unless 方法
影响可能性:中等
正如您所知,when 和 unless 方法在框架中的各种类中提供。这些方法可用于在第一个参数的布尔值为 true 或 false 时有条件地执行操作:
$collection->when(true, function ($collection) {
$collection->merge([1, 2, 3]);
});因此,在 Laravel 9.x 中,传递给 when 或 unless 方法的闭包将被执行,并且闭包返回的值将被视为用于 when 和 unless 方法的布尔值:
$collection->when(function ($collection) {
// 此闭包被执行...
return false;
}, function ($collection) {
// 不执行,因为第一个闭包返回了 "false"...
$collection->merge([1, 2, 3]);
});HTTP 客户端
默认超时
影响可能性:中等
HTTP 客户端 现在的默认超时为 30 秒。换句话说,如果服务器在 30 秒内没有响应,将抛出异常。以前,HTTP 客户端没有配置默认超时长度,导致请求有时“挂起”无限期。
如果您希望为特定请求指定更长的超时,可以使用 timeout 方法:
$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-mailer 和 symfony/http-client Composer 包:
composer require symfony/mailgun-mailer symfony/http-client应从您的应用程序中删除 wildbit/swiftmailer-postmark Composer 包。相反,您的应用程序应要求 symfony/postmark-mailer 和 symfony/http-client Composer 包:
composer require symfony/postmark-mailer symfony/http-client更新的返回类型
Illuminate\Mail\Mailer 上的 send、html、raw 和 plain 方法不再返回 void。相反,返回一个 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'
);
});WARNING
请彻底审查 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');生成的消息 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 对等验证:
'smtp' => [
// Laravel 8.x...
'stream' => [
'ssl' => [
'verify_peer' => false,
],
],
// Laravel 9.x...
'verify_peer' => false,
],要了解更多可用的配置选项,请查看 Symfony Mailer 文档。
WARNING
尽管上面的示例中提到,通常不建议禁用 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\SerializableClosureFactory 和 Illuminate\Queue\SerializableClosure 类已被删除。如果您直接与 opis/closure 库交互或使用任何已删除的类,您可以使用 Laravel Serializable Closure 代替。
失败作业提供者 flush 方法
影响可能性:低
Illuminate\Queue\Failed\FailedJobProviderInterface 接口定义的 flush 方法现在接受一个 $hours 参数,该参数确定失败作业在 queue:flush 命令被清除之前必须多旧(以小时为单位)。如果您手动实现 FailedJobProviderInterface,您应该确保您的实现更新以反映此新参数:
public function flush($hours = null);会话
getSession 方法
影响可能性:低
Symfony\Component\HttpFoundaton\Request 类(Laravel 自己的 Illuminate\Http\Request 类扩展)提供了一个 getSession 方法来获取当前会话存储处理程序。Laravel 并未记录此方法,因为大多数 Laravel 应用程序通过 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。
受信任的代理
影响可能性:低
如果您通过将现有应用程序代码导入全新的 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 属性定义:
// 之前...
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 依赖:
composer remove fideloper/proxy验证
表单请求 validated 方法
影响可能性:低
表单请求提供的 validated 方法现在接受 $key 和 $default 参数。如果您手动覆盖此方法的定义,您应该更新方法的签名以反映这些新参数:
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 行为:
use Illuminate\Support\Facades\Validator;
/**
* 注册任何应用程序服务。
*
* @return void
*/
public function boot()
{
Validator::includeUnvalidatedArrayKeys();
}杂项
我们还鼓励您查看 laravel/laravel GitHub 仓库 中的更改。虽然许多更改不是必需的,但您可能希望将这些文件与您的应用程序保持同步。这些更改中的一些将在此升级指南中涵盖,但其他更改,例如配置文件或注释的更改,将不会。您可以轻松地使用 GitHub 比较工具 查看更改,并选择哪些更新对您重要。