升级指南
高影响更改
中等影响更改
从 8.x 升级到 9.0
预计升级时间:30 分钟
我们尝试记录每一个可能的破坏性更改。由于这些破坏性更改有些在框架的偏僻部分,只有一部分更改可能会影响到您的应用程序。想节省时间?您可以使用 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。如果适用,您应该在升级之前阅读它们各自的升级指南:
- Vonage 通知通道 (v3.0)(替代 Nexmo)
最后,检查您应用程序中使用的任何其他第三方包,并验证您使用的是适合 Laravel 9 支持的正确版本。
PHP 返回类型
PHP 正在逐步过渡到要求 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
应用程序
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'
);
});
请彻底审查 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 文档。
尽管上面的示例中提到,通常不建议禁用 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 比较工具 查看更改,并选择哪些更新对您重要。