新版特性


新特性概览

声明:Laravel 8 于 2020 年 9 月 8 日正式发布,不是 LTS 版本,所以 bug 修复支持会持续半年,到 2021 年 3 月 8 日,也就是下一个主版本 Laravel 9 发布前后,安全修复则会持续一年,到 2021 年 9 月 8 日。

Laravel 8 在 Laravel 7.x 版本基础上继续进行优化,主要包含了以下更新:

  • Laravel Jetstream
  • 模型工厂类
  • 迁移文件压缩
  • 任务批处理
  • 访问频率限制优化
  • 队列功能优化
  • 动态 Blade 组件
  • Tailwind 分页视图
  • 时间相关的测试辅助函数
  • artisan serve 命令优化
  • 事件监听器优化
  • 其他 bug 修复和可用性优化

下面我们来简单介绍下上述新增的特性及代码优化:

Laravel Jetstream

作者:Taylor Otwell

Laravel Jetstream 是一个经过精美设计的 Laravel UI 脚手架,为你的下一个项目提供了非常好的起点,包含了登录、注册、邮箱验证、双因子认证(2FA)、会话管理、基于 Laravel Sanctum 的 API 支持以及可选的团队管理功能。

Laravel Jetstream

Laravel Jetstream 是在之前版本的 UI 脚手架基础上进行的裁剪和优化,使用了 Tailwind CSS 框架,在 JavaScript 组件开发上,你可以按照自己的喜好选择 Livewire 或者 Inertia

模型类目录

应广大社区开发者压倒性的诉求,Laravel 项目默认将包含 app/Models 目录,用于存放 Eloquent 模型类:

-w599

所有相关的 Artisan 生成器命令生成的模型类以后都将存放到这个目录。当然,如果这个目录不存在,模型类还是会生成到 app 目录下。

模型工厂类

作者:Taylor Otwell

Eloquent 模型工厂被重构为基于类进行管理,并且被优化为直接支持关联关系,例如,Laravel 内置的 UserFactory 现在的示例代码如下:

<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class UserFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = User::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }
}

而由于模型类中包含了 HasFactory Trait:

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable;
    
    ...

因此,可以这样调用模型工厂:

use App\Models\User;

User::factory()->count(50)->create();

由于模型工厂现在是简单的 PHP 类,因此状态转换可以通过类方法的方式实现。此外,你还可以按照需要添加任意其它的辅助类到 Eloquent 模型工厂。

例如,User 模型可能有一个 suspended 状态用于修改某个默认属性值(状态方法名可以随便取),你可以使用工厂基类提供的 state 方法来定义状态转换,最后呈现出来的就是一个典型的 PHP 方法:

/**
 * Indicate that the user is suspended.
 *
 * @return \Illuminate\Database\Eloquent\Factories\Factory
 */
public function suspended()
{
    return $this->state([
        'account_status' => 'suspended',
    ]);
}

定义好状态转换方法后,我们可以这样使用它:

use App\Models\User;

User::factory()->count(5)->suspended()->create();

正如前面所提到的,Laravel 8 的模型工厂包含对关联关系的一等支持,所以,假设 User 模型包含 posts 关联方法,我们可以简单运行如下代码来生成一个拥有三篇文章的用户:

$users = User::factory()
            ->hasPosts(3, [
                'published' => false,
            ])
            ->create();

为了简化升级流程, Laravel 官方还专门发布了 laravel/legacy-factories 包用于在 Laravel 8.x 中兼容之前版本的模型工厂。

Laravel 对模型工厂的重构还包含了很多你可能会喜欢的其它新特性,更多细节,请参考数据库测试文档

迁移文件压缩

作者:Taylor Otwell

在构建 Laravel 应用的过程中,随着时间的推移,可能会累积越来越多的数据库迁移文件,这可能会导致迁移目录越来越臃肿,也变得难以管理(比如上百个迁移文件),Laravel 8 引入了将多个迁移文件压缩到单个 SQL 文件的机制来解决这个问题。只需要执行 schema:dump 命令即可:

php artisan schema:dump

// Dump the current database schema and prune all existing migrations...
php artisan schema:dump --prune

php artisan schema:dump

当你执行这个命令时,Laravel 会创建一个表结构文件到 database/schema 目录下:

mysql-schema.sql

这样一来,执行数据库迁移命令将不再执行迁移文件,而是执行表结构文件中的 SQL 语句。执行完这些 SQL 语句后,如果还有未压缩的迁移文件,再去执行它们。

任务批处理

作者:Taylor OtwellMohamed Said

Laravel 的任务批处理功能允许你轻松运行批量任务,在这些批量任务运行完毕后还可以执行指定动作。

你可以使用 Bus 门面新增的 batch 方法来分发批量任务。当然,批处理只有和完成回调结合起来才最有用,你可以使用 thencatchfinally 方法为批处理定义完成回调,每个回调函数在被调用时都会接收一个 Illuminate\Bus\Batch 实例:

use App\Jobs\ProcessPodcast;
use App\Podcast;
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Batch;
use Throwable;

$batch = Bus::batch([
    new ProcessPodcast(Podcast::find(1)),
    new ProcessPodcast(Podcast::find(2)),
    new ProcessPodcast(Podcast::find(3)),
    new ProcessPodcast(Podcast::find(4)),
    new ProcessPodcast(Podcast::find(5)),
])->then(function (Batch $batch) {
    // 所有任务执行成功...
})->catch(function (Batch $batch, Throwable $e) {
    // 第一个失败的任务...
})->finally(function (Batch $batch) {
    // 批处理任务执行完毕...
})->dispatch();

return $batch->id;

想要了解更多任务批处理的使用,可以参考队列文档

优化访问频率限制

作者:Taylor Otwell

Laravel 的请求频率限制功能在本次新版本中得到了增强,变得更加灵活,功能也更加强大,同时保持了对之前版本 throttle 中间件的向后兼容。

访问频率限制器可以使用 RateLimiter 门面的 for 方法进行定义。该方法接收一个频率限制器名称以及一个返回限制配置(配置会应用到频率限制器分配到的路由上)的闭包作为参数:

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000);
});

由于访问频率限制器回调函数接收的是 HTTP 请求实例,因此你可以基于这个输入请求或者认证用户动态设置相应的访问频率限制:

RateLimiter::for('uploads', function (Request $request) {
    return $request->user()->vipCustomer()
                ? Limit::none()
                : Limit::perMinute(100);
});

有时候,你可能想要使用多个字段值来定义访问频率限制,例如,你可能想要设置指定 IP 用户每分钟访问给定路由不超过 100 次,要实现这个功能,你可以在构建访问频率配置时使用 use 方法:

RateLimiter::for('uploads', function (Request $request) {
    return $request->user()->vipCustomer()
                ? Limit::none()
                : Limit::perMinute(100)->by($request->ip());
});

访问频率限制器可以通过 throttle 中间件分配给指定路由或者路由群组,只需要将限制器的名称通过参数传递给 throttle 中间件即可:

Route::middleware(['throttle:uploads'])->group(function () {
    Route::post('/audio', function () {
        //
    });

    Route::post('/video', function () {
        //
    });
});

想要了解更多关于访问频率限制的细节,请参考路由文档

优化维护模式

作者:Taylor Otwell(灵感来自 Spatie)。

在之前版本的 Laravel 中,通过 php artisan down 命令进入维护模式这一功能可以通过定义允许访问应用的 IP 地址「白名单」绕过,该特性在 Laravel 8 中已经被移除,取而代之的是更加简单的密钥/令牌解决方案。

将系统设置为维护模式的时候,你可以使用 secret 选项指定绕过维护模式的令牌:

php artisan down --secret="1630542a-246b-4b66-afa1-dd72a4c43515"

这样一来,应用处于维护模式后,仍然可以通过带有上述令牌的 URL 访问应用,Laravel 会为其颁发一个绕过维护模式的 Cookie:

https://example.com/1630542a-246b-4b66-afa1-dd72a4c43515

访问上面这个 URL 后会重定向到 / 路由,此时,由于浏览器中已经包含绕开维护模式的令牌,所以你可以像正常模式一样访问应用所有路由,就好像并没有启动维护模式一样。

预渲染维护模式视图

如果你在开发期间已经使用 php artisan down 命令进入维护模式,用户仍然可能偶尔遇到错误 —— 如果访问应用的同时 Composer 依赖或者其他基础设施组件正在更新。这种情况会发生是因为 Laravel 框架的基础组成部分必须启动后才能才能判断应用是否处于维护模式,然后使用模板引擎渲染维护模式视图。

为此,Laravel 现在允许你预渲染维护模式视图,它会在请求生命周期的早期返回,这样一来,该视图就会在所有应用依赖加载前被渲染。你可以使用 down 命令的 render 选项选择要预渲染的视图模板:

php artisan down --render="errors::503"

闭包分发和链式 catch

作者:Mohamed Said

你现在可以使用新的 catch 方法定义一个闭包,该闭包会在为队列任务配置的所有尝试次数用完之后仍然失败的情况下运行:

use Throwable;

dispatch(function () use ($podcast) {
    $podcast->publish();
})->catch(function (Throwable $e) {
    // This job has failed...
});

动态 Blade 组件

作者:Taylor Otwell

有时候你可能需要渲染一个组件,但是这个组件只有到运行时才能确定,在这种情况下,你可以使用 Laravel 内置的 dynamic-component 组件基于运行时的值或者变量渲染特定组件:

<x-dynamic-component :component="$componentName" class="mt-4" />

想要了解更多关于 Blade 组件的细节,请参考 Blade 文档

事件监听器优化

作者:Taylor Otwell

现在只能通过将闭包传递给 Event::listen 方法来注册基于闭包的事件监听器。Laravel 会通过这个闭包来判断监听器处理器的事件类型:

use App\Events\PodcastProcessed;
use Illuminate\Support\Facades\Event;

Event::listen(function (PodcastProcessed $event) {
    //
});

此外,基于闭包的事件监听器现在需要使用 Illuminate\Events\queueable 函数标记为通过队列进行处理:

use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;

Event::listen(queueable(function (PodcastProcessed $event) {
    //
}));

和队列任务一样,你可以使用 onConnectiononQueuedelay 方法来自定义队列化事件监听器的执行:

Event::listen(queueable(function (PodcastProcessed $event) {
    //
})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));

如果你想要捕获异步队列监听器的失败,可以在定义 queueable 监听器时提供一个闭包到 catch 方法进行处理即可:

use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
use Throwable;

Event::listen(queueable(function (PodcastProcessed $event) {
    //
})->catch(function (PodcastProcessed $event, Throwable $e) {
    // 队列监听器处理失败...
}));

时间测试辅助函数

作者:Taylor Otwell(灵感源自 Ruby on Rails)。

进行代码测试时,你可能偶尔需要修改 now() 或者 Illuminate\Support\Carbon::now() 函数返回的时间,Laravel 功能测试基类现在内置了相关的辅助函数,你可以使用它们来操纵当前时间:

public function testTimeCanBeManipulated()
{
    // 穿越到未来...
    $this->travel(5)->milliseconds();
    $this->travel(5)->seconds();
    $this->travel(5)->minutes();
    $this->travel(5)->hours();
    $this->travel(5)->days();
    $this->travel(5)->weeks();
    $this->travel(5)->years();

    // 穿越到过去...
    $this->travel(-5)->hours();

    // 穿越到指定时间...
    $this->travelTo(now()->subHours(6));

    // 回到现在...
    $this->travelBack();
}

Artisan serve 命令优化

作者:Taylor Otwell

从 Laravel 8 开始,当本地 .env 文件中的环境变量修改后,Artisan serve 命令会自动进行服务器重载,以便让对应配置修改生效。在此之前,需要手动停止并重启。

Tailwind 分页视图

Laravel 分页器现在被更新为默认使用 Tailwind CSS 框架,Tailwind CSS 是一个高度可定制的基础层 CSS 框架,它为开发者提供了构建定制化设计所需的所有构建区块,而无需重新覆盖任何内建于框架中的设计风格。当然,Bootstrap 3 和 4 对应的分页视图也依然保留,你可以自行选择想要使用的分页风格。

路由控制器命名空间更新

在之前版本的 Laravel 中,RouteServiceProvider 包含一个 $namespace 属性:

class RouteServiceProvider extends ServiceProvider
{
    /**
     * This namespace is applied to your controller routes.
     *
     * In addition, it is set as the URL generator's root namespace.
     *
     * @var string
     */
    protected $namespace = 'App\Http\Controllers';
    
    ...

该属性的值会被自动应用到定义在路由中的、调用 action 辅助函数或者 URL::action 方法时的控制器命名空间前缀。在 Laravel 8.x 中,移除了属性,这意味着控制器不会再有自动设置的命名空间前缀,因此,在 Laravel 8 项目中,控制器路由定义需要使用标准的 PHP 调用语法:

use App\Http\Controllers\UserController;

Route::get('/users', [UserController::class, 'index']);

调用 action 相关的方法也需要使用同样的调用语法:

action([UserController::class, 'index']);

return Redirect::action([UserController::class, 'index'])

如果你更喜欢 Laravel 7.x 风格的控制器路由前缀,在 RouteServiceProvider 中设置 $namespace 属性值即可。

注:这个更改只影响新创建的 Laravel 8.x 应用,从 7.x 版本升级过来的应用仍然会在 RouteServiceProvider 中保留 $namespace 属性。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 目录索引

>> 下一篇: 升级指南