Skip to content

Laravel 11

Laravel 11 现已发布,包括最低 PHP v8.2、Sqlite 3.35.0+、新的 Laravel Reverb 包、简化的目录结构等等。

下面是 Laravel 版本的支持周期表,可以点击这里查看官方说明

版本PHP发布错误修复直至安全修复直至
108.1 - 8.32023 年 2 月 14 日2024 年 8 月 6 日2025 年 2 月 4 日
118.2 - 8.32024 年 3 月 12 日2025 年 9 月 3 日2026 年 3 月 12 日
128.2 - 8.32025 年第一季度2026 年第三季度2027 年第一季度

更多发行说明请参考官方说明

简化目录结构

配置文件 config

Laravel 项目默认的配置文件在 config 目录下,可以看到相比 Laravel 10 的项目配置文件,Laravel 11 移除了部分默认的配置文件。

查看 config 默认配置差异

如果需要随时可以通过 config:publish 命令重新发布。

php
# 发布 broadcasting 配置文件
php artisan config:publish broadcasting

# 发布 view 配置文件 
php artisan config:publish view

服务提供者 Providers

Laravel 10 中可以在 config/app.php 中的数组配置中注册自定义的服务提供者。

Laravel 11config/app.php 中并没有自定义服务提供者的配置项。

提示

默认情况下,Laravel 11 中通过 make:provider 命令创建的服务提供者文件自动加载,而无需手动进行配置。

因为 Laravel 11 中的服务器提供者的配置文件在 bootstrap/providers.php 中以数组的形式返回。

可以通过下面的流程来检验上面的说法:

  1. 创建自定义服务提供者

    通过命令行创建一个服务提供者,并通过运行一个命令来触发它。

    bash
    php artisan make:provider TestingServiceProvider
  2. 添加代码逻辑

    修改 app/Providers/TestingServiceProvider.php 文件,添加以下代码:

    php
    public function register(): void
    {
        dd('Hi there');
    }
  3. 运行命令

    最后,在命令行中运行命令 php artisan inspire 可以看到命令行会输出上面的 dd 信息。

  4. 验证自动注册服务提供者

    bootstrap/providers.php 中自动注册的 App\Providers\TestingServiceProvider::class, 注释,然后再次运行 php artisan inspire 命令,可以看到命令行不再输出上面的 dd 信息。

所以,在 Laravel 11 中自定义的服务提供者文件不在 config/app.php 中进行配置,而是在 bootstrap/providers.php 中进行配置,并且自定义的服务提供者如果是通过 make:provider 命令创建的则无需手动配置,Laravel 框架会自动注册。

install:api

Laravel 11 项目默认移除了对 API 的支持,如果需要配置 API 路由,可以通过下面的命令安装它。

bash
php artisan install:api

运行完上面的命令后,需要手动在用户模型 app/Models/User.php 中添加 use Laravel\Sanctum\HasApiTokens

  1. 下载并安装 laravel/sanctum 扩展包。

  2. 自动生成 routes/api.php 路由文件

  3. 自动开启 api.php 路由文件配置

    php
    return Application::configure(basePath: dirname(__DIR__))
        ->withRouting(
            // ...
            // api: __DIR__.'/../routes/api.php',
            api: __DIR__.'/../routes/api.php', 
            // ...
        )

install:broadcasting

Laravel 11 项目默认移除了对广播系统的支持,如果需要配置广播系统,可以通过下面的命令安装它。

php
php artisan install:broadcasting

更多广播相关使用可以参考 Broadcasting 文档

中间件 Middlewares

Laravel 框架自带了很多中间件来处理不同的请求,比如 TrimStrings 中间件用来去除请求参数和请求头中的空格。

自定义默认中间件配置

相比于 Laravel 10 的中间件,在 Laravel 11 中默认的中间件 app/Http/Middleware 已被移除,如果需要修改默认中间件的配置,可以在服务器提供者 app/Providers/AppServiceProvider.php 中进行配置。

php
public function boot(): void
{
    # 自定义默认中间件配置
    TrimStrings::except(['secret']);
    ## 配置不加密的cookie 
    EncryptCookies::except(['some_cookie_name']);
    ## 登录成功后跳转的路由
    RedirectIfAuthenticated::redirectUsing(fn () => route('dashboard'));
    ## 未登录的用户跳转的路由
    Authenticate::redirectUsing(fn () => route('login'));
}

自定义中间件

Laravel 10 中注册自定义中间件的位置在 app/Http/Kernel.php 文件,而在 Laravel 11 中这个文件被移除了。

Laravel 11 中通过在配置文件 bootstrap/app.phpwithMiddleware() 方法中进行配置。

  1. 创建自定义中间件 通过命令行终端 make:middleware 来创建自定义中间件,创建成功后,可以在 app/Http/Middleware/LogRequestEndPoint.php 中找到自定义中间件的文件。

    bash
    php artisan make:middleware LogRequestEndPoint
  2. 添加自定义中间件逻辑

    php
    use Illuminate\Support\Facades\Log;
    
    public function handle(Request $request, Closure $next): Response
    {
      Log::info($request->url()); 
    
      return $next($request);
    }
  3. bootstrap/app.php 中添加自定义中间件

    php
    return Application::configure(basePath: dirname(__DIR__))
        //...
        ->withMiddleware(function (Middleware $middleware) {
             $middleware->web(append: LogRequestEndPoint::class); // 追加 web 中间件
             // $middleware->append(); // 追加全局中间件
             // $middleware->api(...); // 设置 api 中间件
             // $middleware->alias(...); // 设置中间件别名
        })
  4. 此刻,当访问任何路由时,都会在日志中记录下访问的 URL。

定时任务 Schedule

在 Laravel 10 中,如果需要配置项目的定时任务可以在 app/Console/Kernel.phpschedule() 方法中进行配置。

Laravel 11 中 app/Console/Kernel.php 文件被删除,取而代之的是在 routes/console.php 中进行配置。

php
use Illuminate\Support\Facades\Schedule;

// ...
# 每天删除模型过期数据
Schedule::command('model:prune')->daily();

也可以通过下面的命令对当前项目的定时任务进行查看:

bash
php artisan schedule:list

# 输出如下:
# 0 * * * *  php artisan inspire ............ Next Due: 45 minutes from now
# 0 0 * * *  php artisan model:prune .......... Next Due: 18 hours from now

异常处理 Exceptions

与路由和中间件一样,现在可以从应用程序的 bootstrap/app.php 文件而不是单独的异常处理程序类来自定义异常处理,从而减少新 Laravel 应用程序中包含的文件总数:

php
use App\Exceptions\MissedFlightException;
use App\Exceptions\InvalidOrderException;

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->dontReport(MissedFlightException::class);
 
    $exceptions->report(function (InvalidOrderException $e) {
        // ...
    });
})

新特性

Laravel 11 除了简化项目配置、目录和文件外,还带来了一些新特性,它们包括 Dumpable 特性,

Dumpable

框架类

php
# 现在可以在模型链式调用时调用 `dump()` 或者 `dd()` 方法输出执行 SQL,方便调试。

User::query()->latest()->limit(5)->dd()->get();

# "select * from "users" order by "created_at" desc limit 5"
php
# 在 `Carbon` 实例的链式调时使用 `dump()` 或 `dd()` 方法输出运行结果。

Carbon::now()->subDays(5)->dump()->toString();
php
# 在 `Stringable` 实例的链式调时使用 `dump()` 或 `dd()` 方法输出运行结果

Str::of('hello ')->append('world')->apa()->dump()->toString();

# Hello World

更多使用的类可以参考 laravel/framework 源代码。

自定义类

在自定义的类中直接使用 Dumpable

php
use Illuminate\Support\Traits\Dumpable;

class User
{
    use Dumpable; 

    public function __construct()
    {
        $this->name = 'Curder';
    }

    public function getName()
    {
        return $this->name;
    }
}

$user = new User;

$user->getName()->dump();
$user->getName()->dd();

限制惰性加载数量

Laravel 11 将允许本地限制惰性加载的条目数量。

比如当查询用户的同时需要查询对应的 10 条文章记录。

php
use App\Models\User;
use Illuminate\Database\Eloquent\Relations\HasMany;

User::with([
    'posts' => fn (HasMany $query) => $query->latest()->limit(10) 
])->get();
php
<?php

class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}
php
<?php

class Post extends Model
{
    // ...
}
php
<?php

namespace Database\Seeders;

use App\Models\Post;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
//        User::factory()
//            ->has(Post::factory()->count(100))
//            ->count(10)
//            ->create();

        User::factory()->times(10)->hasPosts(100)->create();
    }
}

也可以通过这个 PR 了解更多详情。

once() 方法

once() 函数确保可调用对象仅被调用一次,并在后续调用中返回相同的结果。

php
class Demo
{
  public function uuid(): UuidInterface
  {
      return Str::uuid();
  }

  public function uuidOnce(): UuidInterface
  {
      return once(fn (): UuidInterface => Str::uuid()); 
  }
}

$demo = new Demo();

foreach(range(1, 5) as $_) {
// echo $demo->uuid() . "<br />";
  echo $demo->uuidOnce() . "<br />"; // 每次调用 `uuidOnce()` 方法都返回相同的结果
}

也可以通过这个 PR 了解更多关于 once() 方法的详情。

casts() 方法

在 Laravel 11 中支持模型转换现在被定义为方法而不是属性。

当定义为方法时,可以做其他事情,例如直接从强制转换中调用其他方法。

php
use App\Enums\ServerStatus;
use App\Enums\UserOption;
use App\Collection\StatusesCollection;

protected function casts(): array
{
    return [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
        'status' => ServerStatus::class, 
        'options'=> AsEnumCollection::of(UserOption::class), 
        'statuses' => AsCollection::of(StatusesCollection::class), 
    ];
}

Enum

数据在写入时会检查是否符合枚举值,如果不符合会抛出异常。

php
use App\Models\User;

User::query()->first()->status; // 获取枚举类实例

User::query()->first()->status->name; // 获取枚举值

User::query()->first()->status->value; // 获取枚举值

User::query()->first()->status->description(); // 枚举类中自定义方法
php
<?php
namespace App\Models;

use App\Enums\Status;

class User
{
   // ...
   protected function casts(): array
   {
     return [
         'status' => Status::class, 
     ];
   }
}
php
<?php

namespace App\Enums;

enum Status: string
{
    case Pending = 'Pending';
    case Ready = 'Ready';
    case Deleted = 'Deleted';

    public function description(): string
    {
        return match($this) {
            self::Pending => '代办',
            self::Ready => '已就绪',
            self::Deleted => '已删除',
        };
    }
}

AsEnumCollection

数据库中存储的是枚举值的数组,数组的单元者是自定义枚举类的值。

php
use App\Models\User;

User::query()->first()->statuses; // 返回枚举类实例集合
php
<?php
namespace App\Models;

use App\Enums\Status;
use Illuminate\Database\Eloquent\Casts\AsEnumCollection;

class User
{
   // ...
   protected function casts(): array
   {
     return [
         'statuses' => AsEnumCollection::of(Status::class), 
     ];
   }
}
php
<?php

namespace App\Enums;

enum Status: string
{
    case Pending = 'Pending';
    case Ready = 'Ready';
    case Deleted = 'Deleted';

    public function description(): string
    {
        return match($this) {
            self::Pending => '代办',
            self::Ready => '已就绪',
            self::Deleted => '已删除',
        };
    }
}

AsCollection

数据库中存储的是数组,获取的时候返回的得是自定义的集合类实例。

php
$user = \App\Models\User::query()->first();

$user->options; // 返回自定义集合类实例
    
$user->options->valid(); // 调用自定义集合类实例的方法
php
<?php
namespace App\Models;

use App\Collections\OptionsCollection;
use Illuminate\Database\Eloquent\Casts\AsCollection;

class User
{
   // ...
   protected function casts(): array
   {
     return [
         'options' => AsCollection::using(OptionsCollection::class), 
     ];
   }
}
php
<?php

namespace App\Collections;

use Illuminate\Support\Collection;

class OptionsCollection extends Collection
{
    public function valid(): self
    {
        $this->items = $this->toBase()->filter(
            fn ($item) => in_array($item, range('a', 'o'))
        )->toArray();

        return $this;
    }
}

也可以通过这个 PR 了解更多详情。

每秒速率限制

Laravel 11 新增了 perSecond() 每秒速率限制的功能,可以用来限制请求的速率。

php
// 在 `AppServiceProvider.php` 的 `boot()` 方法中添加以下代码:

public function boot(): void
{
    RateLimiter::for(
       'api',
       fn ($request) => Limit::perSecond(10)->by($request->user()?->id ?: $request->ip())
    });
}
php
// 在 api.php 路由文件中定义路由

Route::get('rate-limit', fn() => 'ok')->name('rate-limit');
php
// 在 bootstrap/app.php 文件中添加如下代码使用中间件

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        api: __DIR__.'/../routes/api.php', 
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->api(append: 'throttle:api'); 
    })
php
<?php

use Illuminate\Foundation\Testing\LazilyRefreshDatabase;

uses(LazilyRefreshDatabase::class);

it('rate limit is active', function () {
    $this->getJson(route('rate-limit'))
        ->assertOk()
        ->assertHeader('x-ratelimit-limit', 10)
        ->assertHeader('x-ratelimit-remaining', 9);
});

it('rate limit decreases remaining', function () {
    foreach(range(1, 10) as $i) {
        $this->get(route('rate-limit'))
            ->assertOk()
            ->assertHeader('X-Ratelimit-Remaining', 10 - $i);
    }

    $this->get(route('rate-limit'))
        ->assertStatus(429)
        ->assertHeader('Retry-After', 1);
});

健康检查

新的 Laravel 11 应用程序包含一个 health 路由指令,它指示 Laravel 定义一个简单的运行状况检查端点,该端点可以由第三方应用程序运行状况监控服务或 Kubernetes 等编排系统调用。

默认情况下,该路由的服务地址为 /up

php
# bootstrap/app.php

->withRouting(
    web: __DIR__.'/../routes/web.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up', 
)

当向此路由发出 HTTP 请求时,Laravel 还将调度 DiagnosingHealth 事件,允许执行与应用程序相关的其他运行状况检查。

Prompts 验证

Laravel Prompts 用于向命令行应用程序添加美观且用户友好的表单,具有类似浏览器的功能,包括占位符文本和验证。

php
$name = text('What is your name?', validate: [
    'name' => 'required|min:3|max:255',
]);
php
$name = text(
    label: 'What is your name?',
    validate: fn (string $value) => match (true) {
        strlen($value) < 3 => 'The name must be at least 3 characters.',
        strlen($value) > 255 => 'The name must not exceed 255 characters.',
        default => null
    }
);

异步请求的重试

Laravel 11 中,可以使用 retry() 方法来处理异步请求的重试。

php
it('retrying asynchronous requests', function () {
    Http::fakeSequence('example.com')
        ->pushStatus(500)
        ->pushStatus(404)
        ->pushStatus(403)
        ->push('example.com')
        ->whenEmpty(Http::response());
    Http::fakeSequence('example2.com')
        ->pushStatus(500)
        ->pushStatus(404)
        ->pushStatus(403)
        ->push('example2.com')
        ->whenEmpty(Http::response());
    Http::fakeSequence('example3.com')
        ->pushStatus(500)
        ->pushStatus(404)
        ->pushStatus(403)
        ->push('example3.com')
        ->whenEmpty(Http::response());

    $responses = Http::pool(fn (Pool $pool) => [
        $pool->as('first')->retry(5)->get('https://example.com'), 
        $pool->as('second')->retry(5)->get('https://example2.com'), 
        $pool->as('third')->retry(5)->get('https://example3.com'), 
    ]);

    collect($responses)->map(fn (Response $response) => $response->body())->dd();
});

上面的请求池的响应在经过几次重试后,会返回预期的正确响应。结果如下:

php
[
  "first" => "example.com"
  "second" => "example2.com"
  "third" => "example3.com"
]

优雅的轮换加密密钥

由于 Laravel 加密所有 cookie,包括应用程序的会话 cookie,因此基本上对 Laravel 应用程序的每个请求都依赖于加密。但正因为如此,轮换应用程序的加密密钥将使所有用户退出您的应用程序。

此外,解密由先前加密密钥加密的数据变得不可能。

Laravel 11 允许通过环境变量 APP_PREVIOUS_KEYS 将应用程序以前的加密密钥定义为逗号分隔的列表。

Laravel 将始终使用“当前”加密密钥,该密钥位于 APP_KEY 环境变量中。解密值时,Laravel 将首先尝试当前密钥。如果使用当前密钥解密失败,Laravel 将尝试所有先前的密钥,直到其中一个密钥能够解密该值。

即使项目的加密密钥发生轮换,这种优雅解密的方法也允许用户不间断地继续使用您的应用程序。

有关 Laravel 中加密的更多信息,请查看官方文档

自动识别测试框架

虽然 Laravel 支持 PHPUnit 和 Pest 作为测试框架已经有一段时间了,但在 Laravel 11 之前通过命令生成 Pest 测试文件需要添加 --pest 标识,这使得 Pest 测试文件生成过程变得繁琐。

Laravel 11 允许通过命令行自动生成对应的测试文件,而无需添加标识。

php
php artisan make:test PestTest --pest # Laravel 10

php artisan make:test PestTest # Laravel 11

队列交互测试

在 Laravel 11 之前,尝试测试排队作业是否被释放 release()、删除 delete() 或失败 fail() 非常麻烦,并且需要伪造自定义队列。

在 Laravel 11 中,可以使用 withFakeQueueInteractions() 方法轻松测试这些队列交互。

php
use App\Jobs\ProcessPodcast;
 
$job = (new ProcessPodcast)->withFakeQueueInteractions();
 
$job->handle();
 
$job->assertReleased(delay: 30);

新的 artisan 命令

添加了新的 Artisan 命令以允许快速创建类、枚举、接口和 Trait:

bash
php artisan make:class      # 创建类
php artisan make:enum       # 创建枚举
php artisan make:interface  # 创建接口
php artisan make:trait      # 创建Trait

更多官方 artisan 命令可以查看 Github 仓库

新增数据库操作

Laravel 11 提供了额外的数据库模式操作和检查方法,包括本机修改、重命名和删除列。

此外,还提供了高级空间类型、非默认模式名称以及用于操作表、视图、列、索引和外键的本机模式方法:

php
use Illuminate\Support\Facades\Schema;

$tables = Schema::getTables(); // 获取数据库所有表信息
$views = Schema::getViews(); // 获取数据库所有视图信息
$columns = Schema::getColumns('users'); // 获取users表的所有字段信息
$indexes = Schema::getIndexes('users'); // 获取users表的所有索引信息
$foreignKeys = Schema::getForeignKeys('users'); // 获取users表的所有外键信息

相关链接