第 6-11 章速查(基础篇精华浓缩)
本文档定位:把基础篇 6 个章节的核心 API、模式、踩坑点压缩成可随手翻的速查表。
不是教学,是索引——遇到具体问题时翻这里找代码片段,需要深入理解时去看
docs/13-博客实战完整复盘.md。目标读者:写过 ThinkPHP,已经入门 Laravel,需要"老手快查"的人。
0. 全局速查(一图看懂 6-11 章)
6 章核心抽象的"职责分工"
HTTP 请求 ──▶ 路由 (web.php)
│
├─▶ 中间件链 ──▶ 鉴权 / CSRF / 限流
│ (第 10 章)
│
▼
Controller (第 9 章)
│
├─▶ Form Request ──▶ 验证 + 授权
│ (第 9 章)
│
├─▶ Policy ──▶ 资源级权限判断
│ (第 11 章)
│
├─▶ Eloquent Model ──▶ 数据库 (Migration 第 8 章)
│ (第 7 章)
│
▼
View (Blade) (第 10 章)
│
▼
HTTP 响应6 章对应的"核心文件位置"
| 章 | 抽象 | 文件位置 | 创建命令 |
|---|---|---|---|
| 6 | 路由 | routes/web.php / routes/api.php | (手写) |
| 7 | Eloquent Model | app/Models/{Name}.php | php artisan make:model {Name} |
| 8 | Migration | database/migrations/{ts}_*.php | php artisan make:migration create_xxx_table |
| 8 | Factory | database/factories/{Name}Factory.php | php artisan make:factory {Name}Factory |
| 8 | Seeder | database/seeders/{Name}Seeder.php | php artisan make:seeder {Name}Seeder |
| 9 | Controller | app/Http/Controllers/{Name}Controller.php | php artisan make:controller {Name}Controller --resource |
| 9 | Form Request | app/Http/Requests/{Name}Request.php | php artisan make:request Store{Name}Request |
| 10 | Blade 视图 | resources/views/**/*.blade.php | (手写) |
| 10 | Blade 组件 | resources/views/components/**/*.blade.php | php artisan make:component {Name} |
| 10 | 中间件 | app/Http/Middleware/{Name}.php | php artisan make:middleware {Name} |
| 11 | Policy | app/Policies/{Name}Policy.php | php artisan make:policy {Name}Policy --model={Name} |
"改一个功能要改哪些文件"对照表
| 想做的事 | 要改/新增的文件(按依赖顺序) |
|---|---|
| 加新模型 X | Migration → Model → Factory → Seeder → Policy → FormRequest(Store/Update)→ Controller → 路由 → 5 个 Blade 视图 → 测试 |
| 加新字段 | Migration(新增)→ Model $fillable → Form Request rules → 视图表单 → 测试 |
| 加新业务条件 | Model 加 scope → Controller 调用 scope → 视图(如有需要) |
| 加新权限规则 | Policy 加方法 → Controller authorizeResource() 已注册则自动生效,否则 $this->authorize() |
| 加新页面 | 路由 → Controller 方法 → Blade 视图 |
6. 路由系统速查
6.1 路由定义 7 种写法速查
| 写法 | 代码 |
|---|---|
| GET 路由 | Route::get('/posts', [PostController::class, 'index']); |
| POST 路由 | Route::post('/posts', [PostController::class, 'store']); |
| 全部 RESTful | Route::resource('posts', PostController::class); |
| 仅部分 RESTful | Route::resource('posts', PostController::class)->only(['index', 'show']); |
| 排除部分 RESTful | Route::resource('posts', PostController::class)->except(['destroy']); |
| API RESTful(无 create/edit) | Route::apiResource('posts', PostController::class); |
| 嵌套资源 | Route::resource('posts.comments', CommentController::class); |
6.2 Route::resource = 7 条路由
Route::resource('posts', PostController::class);等价于:
| HTTP 方法 | URL | 路由名 | Controller 方法 |
|---|---|---|---|
| GET | /posts | posts.index | index() |
| GET | /posts/create | posts.create | create() |
| POST | /posts | posts.store | store() |
| GET | /posts/{post} | posts.show | show($post) |
| GET | /posts/{post}/edit | posts.edit | edit($post) |
| PUT/PATCH | /posts/{post} | posts.update | update($post) |
| DELETE | /posts/{post} | posts.destroy | destroy($post) |
6.3 路由分组 4 种修饰符
Route::middleware('auth')->prefix('admin')->name('admin.')->group(function () {
Route::get('/dashboard', [AdminController::class, 'index'])->name('dashboard');
// 实际路由:GET /admin/dashboard,名字 admin.dashboard
});| 修饰符 | 作用 |
|---|---|
middleware('auth') | 整组套中间件 |
prefix('admin') | URL 加前缀 |
name('admin.') | 路由名加前缀(注意结尾的点) |
controller(PostController::class) | 整组共用一个 Controller,方法名直接写 |
6.4 命名路由 + 反查
// 定义
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
// 反查(视图 / Controller / 测试通用)
route('posts.show', $post) // /posts/my-first-post(用 slug)
route('posts.show', ['post' => $post]) // 同上,显式传参
route('posts.show', $post, false) // 相对路径不带域名6.5 路由模型绑定
默认(按 id)
Route::get('/posts/{post}', [PostController::class, 'show']);
// URL: /posts/1 → 自动 Post::findOrFail(1) 注入到 show(Post $post)按 slug
在 Model 里:
public function getRouteKeyName(): string
{
return 'slug';
}→ URL 自动变成 /posts/my-first-post,自动 Post::where('slug', 'my-first-post')->firstOrFail()。
显式绑定(多个键)
Route::get('/posts/{post:slug}', [PostController::class, 'show']);
// 直接在路由参数指定按 slug 解析6.6 路由顺序的坑 ⚠️
// ❌ 错误顺序
Route::resource('posts', PostController::class); // 先注册了 /posts/{post}
Route::get('/posts/featured', [PostController::class, 'featured']); // 永远到不了
// ✓ 正确顺序
Route::get('/posts/featured', [PostController::class, 'featured']); // 具体路由先
Route::resource('posts', PostController::class); // 通配路由后→ Laravel 按定义顺序匹配路由,第一条匹配就停。具体路由必须放在通配路由前。
6.7 自检命令
php artisan route:list # 列所有路由
php artisan route:list --path=posts # 按路径筛选
php artisan route:list --name=posts. # 按路由名筛选
php artisan route:list --method=POST # 按方法筛选
php artisan route:list --except-vendor # 排除框架自带路由→ AI 改完路由后让它跑这条命令自检,比 Boost 的任何工具都直接。
6.8 ThinkPHP vs Laravel 路由对照
| ThinkPHP 6 | Laravel 12 |
|---|---|
Route::rule('hello', 'Hello/index') | Route::get('/hello', [HelloController::class, 'index']) |
Route::rule('hello', 'Hello/index', 'POST') | Route::post('/hello', [HelloController::class, 'index']) |
Route::resource('blog', 'Blog') | Route::resource('blog', BlogController::class) |
Route::group('admin', function() {...}) | Route::prefix('admin')->group(function() {...}) |
Route::get('hello', ...)->middleware('auth') | 一致 |
配套实战:第 13 章 §3.4 路由设计 含完整的 11 条路由配置示例。
7. Eloquent ORM 速查
7.1 Model 标准模板
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Post extends Model
{
use HasFactory;
protected $fillable = ['title', 'slug', 'body', 'user_id', 'published_at'];
protected function casts(): array // ⚠️ Laravel 11+ 用方法签名
{
return ['published_at' => 'datetime'];
}
public function getRouteKeyName(): string // 自定义路由键
{
return 'slug';
}
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function scopePublished(Builder $query): Builder
{
return $query->whereNotNull('published_at')->where('published_at', '<=', now());
}
}7.2 CRUD 操作速查
查(Read)
Post::all(); // 全部
Post::find(1); // 按 id 找,找不到返 null
Post::findOrFail(1); // 找不到抛 404
Post::where('user_id', 1)->get(); // 条件查询
Post::where('user_id', 1)->first(); // 首条
Post::where('user_id', 1)->firstOrFail(); // 首条找不到抛 404
Post::where('user_id', 1)->count(); // 计数
Post::where('user_id', 1)->exists(); // 存在性
Post::where('user_id', 1)->paginate(10); // 分页
Post::latest()->take(5)->get(); // 最新 5 条
Post::orderBy('title')->get(); // 排序
Post::with('author')->get(); // eager load 防 N+1
Post::with(['author', 'comments'])->get(); // 多个关系
Post::withCount('comments')->get(); // 加 comments_count 字段
Post::published()->get(); // 调 scope增(Create)
Post::create(['title' => 'X', 'slug' => 'x', 'body' => '...', 'user_id' => 1]);
// 等价于:
$post = new Post();
$post->fill([...])->save();
// 关系链创建(自动设置外键)⭐ 安全做法
$user->posts()->create(['title' => 'X', ...]); // user_id 自动注入改(Update)
$post->update(['title' => 'New title']); // 单个
Post::where('user_id', 1)->update(['public' => false]); // 批量
// 不存在则建
Post::updateOrCreate(
['slug' => 'my-post'], // 查找条件
['title' => '...', ...] // 更新/创建数据
);删(Delete)
$post->delete(); // 单个
Post::destroy(1); // 按 id
Post::destroy([1, 2, 3]); // 多个
Post::where('user_id', 1)->delete(); // 条件批量7.3 关联关系 4 大类型
| 关系 | 描述 | Laravel 写法 |
|---|---|---|
| 一对一 | User 有 1 个 Profile | hasOne(Profile::class) |
| 一对一反向 | Profile 属于 1 个 User | belongsTo(User::class) |
| 一对多 | User 有多个 Post | hasMany(Post::class) |
| 一对多反向 | Post 属于 1 个 User | belongsTo(User::class) |
| 多对多 | Post 有多个 Tag | belongsToMany(Tag::class) |
| 远跨关系 | User → Post → Comment | hasManyThrough(Comment::class, Post::class) |
| 多态一对多 | Post / Video 都有 Comments | morphMany(Comment::class, 'commentable') |
命名陷阱 ⚠️
// 方法名 = 字段前缀 → 不需要显式传外键
public function user(): BelongsTo
{
return $this->belongsTo(User::class); // 默认找 user_id
}
// 方法名 ≠ 字段前缀 → 必须显式传第二个参数
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}7.4 Local Scope(业务条件复用)
定义
class Post extends Model
{
public function scopePublished(Builder $query): Builder
{
return $query->whereNotNull('published_at')->where('published_at', '<=', now());
}
public function scopeOfAuthor(Builder $query, int $userId): Builder
{
return $query->where('user_id', $userId);
}
}使用(省略 scope 前缀)
Post::published()->get();
Post::ofAuthor(1)->published()->get(); // 链式调用
$user->posts()->published()->get(); // 关系上调用7.5 N+1 查询防御
问题代码
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // 每次循环 1 次 SQL → N+1
}修复
$posts = Post::with('author')->get(); // 1 + 1 = 2 次 SQL检测 N+1(推荐开发期开启)
// app/Providers/AppServiceProvider.php
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
// 开发环境下 lazy load 直接抛异常,强制你 eager load
}7.6 ThinkPHP vs Laravel ORM 对照
| ThinkPHP 6 | Laravel 12 |
|---|---|
Db::name('posts')->select() | Post::all() |
Db::name('posts')->where('id', 1)->find() | Post::find(1) |
Db::name('posts')->where('id', 1)->update(...) | Post::where('id', 1)->update(...) 或 $post->update(...) |
Post::create([...]) | 一致 |
$post->user (关联) | 一致 |
软删除 trait SoftDelete | trait SoftDeletes + Migration softDeletes() |
Db::raw() | DB::raw() |
| 全局查询作用域 | Eloquent Global Scope(更统一) |
配套实战:第 13 章 §2 数据层 含完整 Post Model + 3 个 scope + 关系示例。
8. Migration + Artisan 速查
8.1 Migration 字段类型速查
Schema::create('posts', function (Blueprint $table) {
$table->id(); // bigint primary key
$table->string('title', 200); // VARCHAR(200)
$table->string('slug')->unique(); // VARCHAR + 唯一索引
$table->text('summary')->nullable(); // TEXT
$table->longText('body'); // LONGTEXT
$table->integer('views')->default(0); // INT
$table->decimal('price', 10, 2); // DECIMAL(10,2)
$table->boolean('is_featured')->default(false); // TINYINT
$table->json('meta'); // JSON
$table->date('publish_date'); // DATE
$table->dateTime('event_at'); // DATETIME
$table->timestamp('published_at')->nullable(); // TIMESTAMP
$table->softDeletes(); // deleted_at TIMESTAMP nullable
$table->timestamps(); // created_at + updated_at
});8.2 索引和外键
$table->index('user_id'); // 普通索引
$table->unique('email'); // 唯一索引
$table->index(['user_id', 'created_at']); // 复合索引
// 现代外键写法(Laravel 7+)
$table->foreignId('user_id')
->constrained() // 自动按命名找 users 表
->cascadeOnDelete(); // 删除用户时级联删除 posts
// 等价老写法
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');链式约束修饰符
| 修饰符 | 作用 |
|---|---|
nullable() | 允许 null |
default($value) | 默认值 |
unique() | 唯一约束 |
index() | 普通索引 |
unsigned() | 无符号(数值类型) |
comment('xxx') | 字段注释 |
after('column') | 加在某字段之后(仅 MySQL) |
change() | 修改字段(需 doctrine/dbal 包) |
8.3 Migration 生命周期命令
# 创建 migration
php artisan make:migration create_posts_table
php artisan make:migration add_views_to_posts_table --table=posts
php artisan make:migration drop_old_field --table=posts
# 跑 migration
php artisan migrate # 跑未执行的
php artisan migrate --pretend # 干跑(仅打印 SQL 不执行)
php artisan migrate:status # 查看哪些跑过
php artisan migrate:rollback # 回滚最后一批
php artisan migrate:rollback --step=2 # 回滚 2 批
php artisan migrate:reset # 回滚全部
php artisan migrate:refresh # reset + migrate
php artisan migrate:fresh # drop 所有表 + migrate
php artisan migrate:fresh --seed # drop + migrate + seed ⭐8.4 Factory 模板
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
public function definition(): array
{
return [
'title' => fake()->sentence(6),
'slug' => fake()->unique()->slug(),
'body' => fake()->paragraphs(5, true),
'user_id' => User::factory(), // 自动建 User
'published_at' => fake()->dateTimeBetween('-1 year', '-1 hour'),
];
}
public function draft(): static
{
return $this->state(fn () => ['published_at' => null]);
}
public function scheduled(): static
{
return $this->state(fn () => [
'published_at' => fake()->dateTimeBetween('+1 hour', '+1 month'),
]);
}
}8.5 Factory 调用方式
Post::factory()->create(); // 单个,写库
Post::factory()->make(); // 单个,不写库
Post::factory()->raw(); // 仅返回属性数组
Post::factory()->count(10)->create(); // 10 个
Post::factory()->count(10)->create(['user_id' => 1]); // 全部覆盖 user_id
Post::factory()->draft()->create(); // 用 state
Post::factory()->scheduled()->count(3)->create(); // state + count
// 关系链
Post::factory()->for($user, 'author')->create(); // belongsTo 端
User::factory()->has(Post::factory()->count(3))->create(); // hasMany 端8.6 Seeder 幂等模板(生产可用)
<?php
namespace Database\Seeders;
use App\Models\User;
use App\Models\Post;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$admin = User::firstOrCreate(
['email' => 'admin@example.com'],
User::factory()->raw(['name' => 'Admin', 'email' => 'admin@example.com'])
);
if ($admin->wasRecentlyCreated) { // 仅初次创建时 seed 关联数据
Post::factory()->count(5)->for($admin, 'author')->create();
}
}
}→ 跑多少次 php artisan db:seed 都不会因 UNIQUE 报错。
8.7 Artisan 命令速查(按使用频率)
高频(每天用)
php artisan serve # 启动开发服务器
php artisan tinker # REPL 交互
php artisan test # 跑测试
php artisan route:list # 列路由
php artisan migrate # 跑 migration中频
php artisan make:model Post -mfsr # 一次创建 Model + Migration + Factory + Seeder + Resource Controller
php artisan make:controller PostController --resource
php artisan make:migration create_posts_table
php artisan make:request StorePostRequest
php artisan make:policy PostPolicy --model=Post
php artisan make:component layout
php artisan make:test PostTest维护
php artisan optimize # 缓存路由 + 配置 + 视图
php artisan optimize:clear # 清所有缓存
php artisan config:cache # 缓存配置(生产)
php artisan config:clear
php artisan storage:link # 建立 storage/app/public 软链
php artisan db:seed
php artisan db:wipe # ⚠️ 删所有表8.8 Tinker 使用速查
php artisan tinker>>> User::count()
=> 6
>>> $user = User::find(1)
>>> $user->posts()->count()
>>> Post::factory()->create() // 临时造数据
>>> exit // 退出⚠️ Tinker 是长进程——seed 后查不到数据,重启 Tinker 拿新连接。
8.9 ThinkPHP vs Laravel Migration 对照
| ThinkPHP 6 | Laravel 12 |
|---|---|
用 php think migrate:create | php artisan make:migration |
php think migrate:run | php artisan migrate |
php think migrate:rollback | 一致 |
| 没有 Factory 概念 | php artisan make:factory + Faker |
| 没有 Seeder 标准化 | php artisan make:seeder + db:seed |
php think tinker(不存在) | php artisan tinker(必备) |
配套实战:第 13 章 §2 数据层 含完整 Migration / Factory / 幂等 Seeder 实例。
9. Controller + Form Request 速查
9.1 Resource Controller 标准模板(Laravel 12)
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Http\Requests\UpdatePostRequest;
use App\Models\Post;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
use Illuminate\View\View;
class PostController extends Controller implements HasMiddleware
{
use AuthorizesRequests;
public static function middleware(): array // ⚠️ Laravel 11+ 写法
{
return [
new Middleware('auth', except: ['index', 'show']),
];
}
public function __construct()
{
$this->authorizeResource(Post::class, 'post'); // ⭐ 一行注册 7 个方法的授权
}
public function index(): View
{
$posts = Post::published()->with('author')->latest('published_at')->paginate(10);
return view('posts.index', compact('posts'));
}
public function create(): View
{
return view('posts.create');
}
public function store(StorePostRequest $request): RedirectResponse
{
$post = $request->user()->posts()->create($request->validated()); // ⭐ 关系链创建
return redirect()->route('posts.show', $post)->with('status', 'Post created.');
}
public function show(Post $post): View
{
return view('posts.show', compact('post'));
}
public function edit(Post $post): View
{
return view('posts.edit', compact('post'));
}
public function update(UpdatePostRequest $request, Post $post): RedirectResponse
{
$post->update($request->validated());
return redirect()->route('posts.show', $post)->with('status', 'Post updated.');
}
public function destroy(Post $post): RedirectResponse
{
$post->delete();
return redirect()->route('posts.index')->with('status', 'Post deleted.');
}
}9.2 Form Request 标准模板
Store(创建)
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return true; // ⭐ 授权交给 Policy,这里默认放行
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:200'],
'slug' => ['required', 'string', 'max:255', 'unique:posts,slug'],
'body' => ['required', 'string'],
'published_at' => ['nullable', 'date'],
];
}
public function messages(): array // 可选:自定义错误消息
{
return [
'title.required' => '标题是必填的',
];
}
public function attributes(): array // 可选:自定义字段名
{
return ['title' => '文章标题'];
}
}Update(更新)
use Illuminate\Validation\Rule;
class UpdatePostRequest extends FormRequest
{
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:200'],
'slug' => [
'required', 'string', 'max:255',
Rule::unique('posts', 'slug')->ignore($this->route('post')),
],
'body' => ['required', 'string'],
'published_at' => ['nullable', 'date'],
];
}
}9.3 验证规则速查(最常用 30 个)
必填类
| 规则 | 含义 |
|---|---|
required | 必填 |
required_if:field,value | 当 field=value 时必填 |
required_unless:field,value | 当 field≠value 时必填 |
required_with:f1,f2 | f1 或 f2 任一存在时必填 |
nullable | 允许 null |
sometimes | 仅当字段存在时校验 |
类型类
| 规则 | 含义 |
|---|---|
string | 字符串 |
integer | 整数 |
numeric | 数字(含小数) |
boolean | true/false/1/0/'1'/'0' |
array | 数组 |
json | 合法 JSON 字符串 |
date | 合法日期 |
email | 邮箱 |
url | URL |
uuid | UUID |
ip | IP 地址 |
格式类
| 规则 | 含义 |
|---|---|
min:N | 字符串长度 / 数字 / 数组元素数 ≥ N |
max:N | ≤ N |
between:M,N | 在 M-N 之间 |
size:N | 严格等于 N |
regex:/^\d+$/ | 正则匹配 |
alpha | 仅字母 |
alpha_num | 字母+数字 |
alpha_dash | 字母+数字+下划线+连字符 |
数据库类
| 规则 | 含义 |
|---|---|
unique:posts,slug | posts 表 slug 字段唯一 |
unique:posts,slug,{id},id | 唯一但忽略 id 字段为 {id} 的记录 |
exists:users,id | users 表 id 字段必须存在该值 |
文件类
| 规则 | 含义 |
|---|---|
image | 是图片(jpg/jpeg/png/bmp/gif/svg/webp) |
mimes:jpg,png | 限定 MIME |
max:2048 | 最大 KB(注意单位) |
dimensions:min_width=100,min_height=200 | 图片尺寸 |
关联类
| 规则 | 含义 |
|---|---|
confirmed | 字段需要有 字段_confirmation 同值(密码确认场景) |
same:field | 与 field 相同 |
different:field | 与 field 不同 |
in:a,b,c | 必须是其中之一 |
not_in:a,b,c | 不能是其中之一 |
9.4 错误处理与回填
Controller 端无需手动处理
public function store(StorePostRequest $request)
{
// 验证失败时,Laravel 自动 redirect()->back()
// ->withErrors($validator)
// ->withInput()
// ↑ 不需要任何代码
}Blade 端读取错误和旧值
<input type="text" name="title" value="{{ old('title', $post?->title) }}">
@error('title')
<p class="text-red-600">{{ $message }}</p>
@enderror
{{-- 检查所有错误 --}}
@if ($errors->any())
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif9.5 Controller 的 4 种"接收数据"姿势
| 姿势 | 适用场景 |
|---|---|
$request->input('field') | 取单个字段,可设默认值 |
$request->only(['a', 'b']) | 取指定字段,返回数组 |
$request->except(['password']) | 排除字段 |
$request->validated() | ⭐ 用 Form Request 时取所有验证过的字段 |
→ 优先用 validated()——保证拿到的数据已经过验证,不会有意外字段。
9.6 ThinkPHP vs Laravel Controller 对照
| ThinkPHP 6 | Laravel 12 |
|---|---|
extends Controller | extends Controller |
Validate 类放 app/validate/ | FormRequest 放 app/Http/Requests/ |
validate(...) 在 controller 调用 | type-hint Form Request 自动验证 |
手写 if (!$result) abort(403) | $this->authorize('action', $model) |
| 没有"标准 RESTful 控制器"约定 | 7 方法约定(index/show/create/store/edit/update/destroy) |
配套实战:第 13 章 §3 HTTP 层 含 PostController + StorePostRequest + UpdatePostRequest 完整实例。
10. Blade + 中间件速查
10.1 Blade 语法速查(一表打尽)
| 语法 | 作用 |
|---|---|
{{ $var }} | 输出(自动 HTML 转义) |
{!! $var !!} | 输出原始 HTML(慎用,XSS 风险) |
{{ $var ?? 'default' }} | null coalescing |
{{-- 注释 --}} | Blade 注释(不输出到 HTML) |
<!-- 注释 --> | HTML 注释(输出到 HTML) |
@if / @elseif / @else / @endif | 条件 |
@unless ... @endunless | 反向条件 |
@isset($var) ... @endisset | 变量已定义 |
@empty($var) ... @endempty | 变量为空 |
@for / @foreach / @while / @forelse | 循环 |
@switch / @case / @default / @endswitch | 分支 |
@php ... @endphp | 内联 PHP(少用) |
@auth ... @else ... @endauth | 已登录/游客 |
@guest ... @endguest | 仅游客 |
@can('action', $model) | 权限判断(调 Policy) |
@cannot('action', $model) | 反向权限 |
@error('field') {{ $message }} @enderror | 验证错误显示 |
@csrf | CSRF token 隐藏字段 |
@method('DELETE') | HTTP method spoofing |
@include('partial') | 包含子视图 |
@yield('content') | 占位(旧布局方式) |
@section('content') ... @endsection | 填充 yield |
@extends('layout') | 继承(旧布局方式) |
@props([...]) | 组件参数定义(新组件方式) |
{{ $slot }} | 组件默认插槽 |
10.2 @forelse 处理空状态(强烈推荐)
@forelse ($posts as $post)
<article>{{ $post->title }}</article>
@empty
<p>没有文章</p>
@endforelse→ 比"先 @if (count($posts)) 再 @foreach"清晰得多。
10.3 现代组件(推荐 over 旧布局)
定义组件 resources/views/components/layout.blade.php
@props(['title' => null])
<!DOCTYPE html>
<html>
<head>
<title>{{ $title ?? 'My Site' }}</title>
</head>
<body>
{{ $slot }}
</body>
</html>使用
<x-layout title="Posts">
<h1>Posts</h1>
<p>Hello world</p>
</x-layout>→ Tailwind class 也可以传:<x-layout class="bg-gray-50"> 然后内部用 {{ $attributes->merge([...]) }}。
10.4 命名插槽
{{-- 定义 --}}
<header>{{ $header }}</header>
<main>{{ $slot }}</main>
<footer>{{ $footer }}</footer>
{{-- 使用 --}}
<x-card>
<x-slot:header>标题</x-slot:header>
主内容
<x-slot:footer>页脚</x-slot:footer>
</x-card>10.5 视图辅助函数
| 函数 | 作用 |
|---|---|
view('posts.index', ['posts' => $posts]) | 渲染视图 |
route('posts.show', $post) | 反查 URL |
url('/posts') | 生成绝对 URL |
asset('images/logo.png') | 资源 URL(自动加 /public) |
old('field', $default) | 旧表单值 |
csrf_field() | CSRF 隐藏字段(推荐用 @csrf) |
auth()->user() | 当前登录用户(未登录返 null) |
auth()->id() | 当前用户 id |
session('key') | 取 session |
request()->routeIs('posts.*') | 当前路由名是否匹配 |
Str::limit($body, 150) | 截字 + ... |
Str::slug($title) | 转 URL slug |
now() / today() | 当前 Carbon 时间 |
10.6 中间件三步走
Step 1:创建
php artisan make:middleware EnsureUserIsActiveStep 2:写逻辑
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class EnsureUserIsActive
{
public function handle(Request $request, Closure $next)
{
if (! $request->user()?->is_active) {
abort(403, 'Account is inactive');
}
return $next($request);
}
}Step 3:注册(Laravel 11+ 在 bootstrap/app.php)
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'active' => \App\Http\Middleware\EnsureUserIsActive::class,
]);
// 或者全局:
// $middleware->web(append: [\App\Http\Middleware\EnsureUserIsActive::class]);
})
->create();Step 4:使用
// 路由
Route::get('/dashboard', ...)->middleware('active');
Route::middleware(['auth', 'active'])->group(function () { ... });
// Controller(HasMiddleware 接口)
public static function middleware(): array
{
return [
'auth',
new Middleware('active', only: ['create', 'store']),
];
}10.7 内置中间件速查
| 中间件 | 作用 |
|---|---|
auth | 必须登录 |
auth:sanctum | Sanctum token 已验证 |
guest | 必须未登录 |
verified | 邮箱已验证 |
signed | 签名 URL 校验 |
throttle:60,1 | 限流(60 次/分钟) |
password.confirm | 必须最近确认过密码 |
cache.headers:public;max_age=2628000;etag | 响应缓存头 |
10.8 ThinkPHP vs Laravel 视图对照
| ThinkPHP 6 | Laravel 12 |
|---|---|
{$var} | {{ $var }} |
{:url('xxx')} | {{ route('xxx') }} |
{volist name="..." id="..."} | @foreach (... as ...) |
{include file="..."} | @include(...) 或 <x-component /> |
display('xxx') | view('xxx') |
{:date('Y-m-d', $time)} | {{ $time->format('Y-m-d') }}(Carbon) |
中间件叫 behavior | 中间件叫 middleware |
配套实战:第 13 章 §4 视图层 含完整 5 视图 + 共享组件示例。
11. 认证系统速查
11.1 认证场景速查表
| 场景 | 推荐方案 | 装哪个包 |
|---|---|---|
| 单体应用 + Web 表单登录 | 手写或 Breeze | laravel/breeze --dev |
| API + 移动端 + Token | Sanctum | laravel/sanctum |
| API + 第三方应用 + OAuth2 | Passport | laravel/passport |
| 后台管理 + 完整脚手架 | Filament 自带认证 | filament/filament |
| SPA(Vue/React 同域) | Sanctum SPA 模式 | laravel/sanctum |
| 复杂权限(角色/权限) | Spatie Permission | spatie/laravel-permission |
11.2 手写 Demo 登录(最小可用版)
路由
Route::get('/login', [AuthController::class, 'showLogin'])->name('login');
Route::post('/login', [AuthController::class, 'login']);
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');Controller
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class AuthController extends Controller
{
public function showLogin(): View
{
return view('auth.login');
}
public function login(Request $request): RedirectResponse
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials, $request->boolean('remember'))) {
$request->session()->regenerate(); // ⭐ 防 session fixation
return redirect()->intended(route('posts.index')); // ⭐ 回跳原始访问页
}
return back()
->withErrors(['email' => 'Invalid credentials.'])
->onlyInput('email'); // ⭐ 不回填 password
}
public function logout(Request $request): RedirectResponse
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken(); // ⭐ 三件套缺一不可
return redirect()->route('posts.index');
}
}视图(核心)
<form method="POST" action="{{ route('login') }}">
@csrf
<input type="email" name="email" value="{{ old('email') }}">
<input type="password" name="password">
<input type="checkbox" name="remember">
<button type="submit">Login</button>
</form>
{{-- Logout 必须用 POST,不能用 a 标签!--}}
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit">Logout</button>
</form>11.3 Auth Facade 常用方法
Auth::attempt(['email' => 'x', 'password' => 'y']); // 尝试登录
Auth::login($user); // 直接登录某个用户
Auth::loginUsingId(1); // 按 id 登录
Auth::logout(); // 退出
Auth::user(); // 当前用户(未登录返 null)
Auth::id(); // 当前用户 id
Auth::check(); // 是否已登录
Auth::guest(); // 是否游客
Auth::viaRemember(); // 是否通过 remember cookie 登录→ 也可用全局函数 auth()->user() / auth()->id()。
11.4 Policy 标准模板
php artisan make:policy PostPolicy --model=Postnamespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
public function viewAny(?User $user): bool // 列表页(允许游客)
{
return true;
}
public function view(?User $user, Post $post): bool // 详情页
{
if ($post->published_at?->isPast()) {
return true; // 已发布任何人可看
}
return $user !== null && $user->id === $post->user_id;
}
public function create(User $user): bool
{
return true; // 任何登录用户可创建
}
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id; // 只有作者可改
}
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
public function restore(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
public function forceDelete(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
}11.5 Policy 使用 4 种方式
Controller(推荐)
public function __construct()
{
$this->authorizeResource(Post::class, 'post'); // 一行注册全部 7 个 RESTful action
}
// 或单独调用
public function update(Post $post)
{
$this->authorize('update', $post);
// ...
}Form Request
public function authorize(): bool
{
return $this->user()->can('update', $this->route('post'));
}Blade
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Edit</a>
@endcan
@cannot('delete', $post)
<p>You can't delete this</p>
@endcannotUser 模型(直接 can())
if (auth()->user()->can('update', $post)) {
// ...
}11.6 ?User vs User 类型签名(关键)
| 签名 | 含义 |
|---|---|
view(?User $user, ...) | 允许游客($user 可能为 null) |
view(User $user, ...) | 必须登录(如果游客,框架直接 deny,不调这个方法) |
→ 这是 Laravel 11+ 的"用类型表达业务规则"——记住 ? 就是"游客可访问"。
11.7 Sanctum API Token 速查(第 14 章详细做)
装包
composer require laravel/sanctum
php artisan install:api # Laravel 11+ 一键装User 模型加 trait
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, ...;
}颁发 Token
$token = $user->createToken('mobile-app')->plainTextToken;
return ['token' => $token];路由保护
Route::middleware('auth:sanctum')->get('/api/me', function (Request $request) {
return $request->user();
});客户端调用
curl -H "Authorization: Bearer {token}" https://api.example.com/me11.8 ThinkPHP vs Laravel 认证对照
| ThinkPHP 6 | Laravel 12 |
|---|---|
| 自己写 / 用 ThinkAuth 包 | 内置 Auth Facade,开箱可用 |
| 自己写 RBAC 表 | 用 Spatie Permission 或 Policy |
session('user') 取用户 | auth()->user() |
| 自己生成 Token | Sanctum 一行 createToken() |
| 中间件检查登录 | 内置 auth 中间件 |
配套实战:第 13 章 §5 认证层 含手写 Demo 登录 + 4 个安全细节实战。
附:50 个 ThinkPHP → Laravel 一对一翻译表
路由
| ThinkPHP 6 | Laravel 12 |
|---|---|
Route::get('hello', 'Hello/index') | Route::get('/hello', [HelloController::class, 'index']) |
Route::resource('blog', 'Blog') | Route::resource('blog', BlogController::class) |
Route::group('admin', fn() => ...) | Route::prefix('admin')->group(fn() => ...) |
url('Hello/index') | route('hello.index') |
Controller
| ThinkPHP 6 | Laravel 12 |
|---|---|
class IndexController extends BaseController | class IndexController extends Controller |
Request::param('name') | $request->input('name') |
Request::post() | $request->all() |
redirect('/login') | redirect('/login') |
redirect()->route('xxx') | redirect()->route('xxx') |
json([...]) | response()->json([...]) |
view('index', $data) | view('index', $data) |
数据库
| ThinkPHP 6 | Laravel 12 |
|---|---|
Db::name('users')->select() | User::all() |
Db::name('users')->where('id', 1)->find() | User::find(1) |
Db::table('users')->insert([...]) | User::create([...]) 或 DB::table('users')->insert(...) |
Db::name('users')->where('id', 1)->update([...]) | User::where('id', 1)->update([...]) |
Db::name('users')->where('id', 1)->delete() | User::destroy(1) |
软删除 trait SoftDelete | trait SoftDeletes |
Db::raw('count(*)') | DB::raw('count(*)') |
Db::transaction(fn() => ...) | DB::transaction(fn() => ...) |
Model
| ThinkPHP 6 | Laravel 12 |
|---|---|
protected $table = 'posts' | protected $table = 'posts'(一致) |
protected $pk = 'id' | protected $primaryKey = 'id' |
protected $autoWriteTimestamp = true | 默认开启 timestamps |
关联 belongsTo('User') | belongsTo(User::class) |
关联 hasMany('Post') | hasMany(Post::class) |
| 没有 scope 概念 | scopeXxx(Builder $query) |
$model->save() | $model->save()(一致) |
$model->delete() | $model->delete()(一致) |
验证
| ThinkPHP 6 | Laravel 12 |
|---|---|
Validate 类放 app/validate/ | FormRequest 放 app/Http/Requests/ |
validate(Validate::class) | type-hint Form Request 自动注入 |
'name' => 'require|max:25' | 'name' => ['required', 'max:25'] |
错误 validate->getError() | $errors->first('name') |
视图
| ThinkPHP 6 | Laravel 12 |
|---|---|
{$var} | {{ $var }} |
{:url('xxx')} | {{ route('xxx') }} |
{volist name="..." id="vo"} | @foreach (... as $vo) |
{include file="..."} | @include(...) 或 <x-...> |
{:date('Y-m-d', $time)} | {{ $time->format('Y-m-d') }} |
<eq name="..."> | @if (...) ... @endif |
认证
| ThinkPHP 6 | Laravel 12 |
|---|---|
自己写 / topthink/think-auth | 内置 Auth::attempt() |
session('user_id') | auth()->id() |
Cache::remember('key', fn() => ..., 60) | Cache::remember('key', 60, fn() => ...) |
命令
| ThinkPHP 6 | Laravel 12 |
|---|---|
php think run | php artisan serve |
php think migrate:run | php artisan migrate |
php think make:controller Hello | php artisan make:controller HelloController |
php think make:model User | php artisan make:model User |
| 没有等价 | php artisan tinker ⭐ |
| 没有等价 | php artisan test ⭐ |
| 没有等价 | php artisan route:list ⭐ |
速查的速查(备查)
本节是"目录"——遇到问题时第一站。
| 我想做的事 | 看哪一节 |
|---|---|
| 加一条路由 | §6.1 路由定义 7 种写法 |
| 用一行注册 7 个 RESTful 路由 | §6.2 Route::resource |
| 路由按 slug 解析而不是 id | §6.5 路由模型绑定 |
| 写 Eloquent 查询 | §7.2 CRUD 操作速查 |
| 防 N+1 | §7.5 N+1 查询防御 |
| 业务条件复用 | §7.4 Local Scope |
| 加字段 / 改字段 | §8.1 Migration 字段类型速查 |
| 写测试用 Factory 造数据 | §8.4 Factory 模板 |
| 让 seed 不重复报错 | §8.6 Seeder 幂等模板 |
| 写一个 Controller | §9.1 Resource Controller 标准模板 |
| 验证表单输入 | §9.2 Form Request 标准模板 |
| 编辑时 unique 不报错 | §9.2 Update 部分 |
| 错误回填到表单 | §9.4 错误处理与回填 |
| 用 Blade | §10.1 Blade 语法速查 |
| 写一个组件 | §10.3 现代组件 |
| 让某些路由强制登录 | §10.6 中间件三步走 + auth 中间件 |
| 实现登录 | §11.2 手写 Demo 登录 |
| 加权限规则 | §11.4 Policy 标准模板 |
| 区分游客和登录用户的权限 | §11.6 ?User vs User 类型签名 |
| 做 API 认证 | §11.7 Sanctum API Token 速查 |
| 找 ThinkPHP 等价物 | §附录 50 个翻译表 |
本文档原则:能查到不要写、能写代码不要写文字、能列表不要写段落。
更新时机:每次实战遇到新模式 / 新踩坑,立即回填。
配套阅读:
docs/13-博客实战完整复盘.md(实战素材,看这里学"为什么")docs/17-Prompt工程反例集.md(Prompt 模板,看这里学"怎么让 AI 写")docs/12-Boost工具实战大全.md(Boost 工具用法)