第9章 9.2 快速上手 Laravel:让 PHP 写起来像 Python 一样优雅
上章回顾:上一章我们速览了 PHP 8 的新特性——构造函数属性提升、联合类型、match 表达式……你有没有发现,PHP 8 已经越来越像 Python 了?这一章我们要把「像 Python 一样优雅」这件事做到极致——用 Laravel 框架,写出跟 Python Flask 一样简洁好看的代码。
本章目标:30 分钟入门 Laravel 能跑起来,60 分钟写出你的第一个完整小项目。
🎯 开场 3 分钟:为什么需要框架?
场景切入:写一个「显示用户列表」的页面
不用框架,你会这样写 PHP:
// 原生 PHP:5 个文件起步
<?php
header('Content-Type: text/html; charset=utf-8');
$conn = new mysqli('localhost', 'root', 'password', 'test');
$result = $conn->quer\n\n\n\n\n\ny("SELECT * FROM users");
echo "<table><tr><th>ID</th><th>姓名</th></tr>";
while ($row = $result->fetch_assoc()) {
echo "<tr><td>{$row['id']}</td><td>{$row['name']}</td></tr>";
}
echo "</table>";
?>
痛点来了:
- 想改 URL 路由?改 Apache/Nginx 配置
- 想加个登录验证?从头写 Session 处理
- 想返回 JSON?手写 json_encode + 头信息
- 多人协作?每人写一套,代码像意大利面
Laravel 就是来解决这些问题的——给你一套约定好的结构,让代码自动变得有序。
🧱 基础 25 分钟:核心概念
概念 1:路由——网站的「导航地图」
是什么:路由就是「URL 地址 → 处理函数」的映射表,像餐厅的座位号。
类比:你打电话给外卖平台,「我要点餐」这个请求被路由到「点餐组」,「我要投诉」被路由到「客服组」。
为什么用:不用改服务器配置,直接在代码里定义 URL 行为。
怎么用:
// routes/web.php - Laravel 路由文件
use Illuminate\Support\Facades\Route;
// 访问 /hello 就执行这个函数
Route::get('/hello', function () {
return '你好,Laravel!';
});
// 带参数的路由
Route::get('/user/{id}', function (string $id) {
return "用户ID是:{$id}";
});
对比 Python Flask:
# Python Flask 等价写法
from flask import Flask
app = Flask(__name__)
@app.route('/hello')
def hello():
return '你好,Laravel!'
@app.route('/user/<id>')
def user(id):
return f"用户ID是:{id}"
看,Python Flask 简洁,但 Laravel 更适合大型项目。
概念 2:MVC 架构——分工明确的「流水线」
是什么:MVC = Model(模型) + View(视图) + Controller(控制器),一种代码组织模式。
类比:去医院看病:
- Model = 病历本(存数据)
- View = 医生的诊断书(展示结果)
- Controller = 医生(协调:看完病历、检查身体、写诊断书)
为什么用:不用 MVC 的话,所有代码混在一起,改需求时像在一锅粥里找芝麻。
怎么用:
// Model:app/Models/User.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $table = 'users'; // 对应哪张表
protected $fillable = ['name', 'email']; // 允许批量写入的字段
}
// Controller:app/Http/Controllers/UserController.php
<?php
namespace App\Http\Controllers;
use App\Models\User;
class UserController extends Controller
{
// 展示所有用户
public function index()
{
$users = User::all(); // 等价于 SELECT * FROM users
return view('users.index', ['users' => $users]);
}
// 创建新用户(表单提交后)
public function store(Request $request)
{
User::create($request->only(['name', 'email']));
return redirect('/users');
}
}
// View:resources/views/users/index.blade.php
<h1>用户列表</h1>
<ul>
@foreach ($users as $user)
<li>{{ $user->name }} - {{ $user->email }}</li>
@endforeach
</ul>
Laravel 的 View 用 Blade 模板引擎,语法像写 HTML 但带「超能力」——{{ }} 会自动转义防 XSS,@foreach @if 让模板自己会「思考」。
概念 3:Artisan 命令——你的命令行瑞士军刀
是什么:Laravel 自带一系列命令行工具,叫 Artisan( artisan /ɑːrˈtɪsən/ ,手工艺人)。
为什么用:生成代码骨架、数据库迁移、启动开发服务器……不用手动创建文件。
怎么用:
# 创建控制器
php artisan make:controller UserController
# 创建模型 + 迁移文件(一起搞定)
php artisan make:model User -m
# 启动开发服务器(相当于 python manage.py runserver)
php artisan serve
# 查看所有可用命令
php artisan list
# 实际运行效果
$ php artisan serve
NFO Server running on http://127.0.0.1:8000.
$ php artisan make:controller ArticleController
NFO Controller created successfully.
概念 4:Eloquent ORM——用「对象」操作数据库
是什么:Eloquent 是 Laravel 的 ORM(对象关系映射),让你用 PHP 对象操作数据库,不用写 SQL。
类比:ORM 就像翻译官——你说中文「给我所有用户」,翻译官帮你转成英文 SQL。
为什么用:不用记 SQL 语法,代码更安全(防 SQL 注入),还能跨数据库(MySQL/PostgreSQL/SQLite 换着用)。
怎么用:
// 查数据 - 不用写 SELECT
$users = User::all(); // 查所有
$user = User::find(1); // 按主键查
$users = User::where('age', '>', 18)->get(); // 条件查询
// 增数据 - 不用写 INSERT
User::create(['name' => '张三', 'email' => 'zhangsan@example.com']);
// 改数据 - 不用写 UPDATE
$user = User::find(1);
$user->name = '李四';
$user->save();
// 删数据 - 不用写 DELETE
$user->delete();
🔥 实战 35 分钟:3 个递进小项目
项目 1(5 分钟):Hello Laravel
目标:访问 /hello 返回「Hello World」,访问 /now 返回当前时间。
步骤:
- 创建新项目(如果还没创建):
composer create-project laravel/laravel my-first-laravel
cd my-first-laravel
- 打开
routes/web.php,添加路由:
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/hello', function () {
return 'Hello World';
});
Route::get('/now', function () {
return now()->toDateTimeString(); // Laravel 自带的时间对象
});
- 启动服务器:
php artisan serve
- 打开浏览器访问:
-http://127.0.0.1:8000/hello→ 显示「Hello World」
-http://127.0.0.1:8000/now→ 显示「2024-01-15 14:30:00」
预期输出:
# 浏览器访问 /now 显示:
2024-01-15 14:30:00
一句话解释:Route::get 定义了 URL 到函数的映射,now() 是 Laravel 封装的 Carbon 时间对象。
项目 2(15 分钟):用户管理 CRUD
目标:做一个完整的用户增删改查页面,数据存 SQLite(不用装 MySQL)。
步骤:
- 生成脚手架(一条命令搞定):
php artisan make:model User -mcr
# -m = 创建迁移文件(数据库表结构)
# -c = 创建控制器
# -r = 生成 CRUD 完整方法
- 配置数据库(用 SQLite,文件数据库):
// .env 文件,找到 DB_CONNECTION,改成:
DB_CONNECTION=sqlite
DB_DATABASE=/absolute/path/to/database/database.sqlite
# 创建 SQLite 数据库文件
touch database/database.sqlite
- 写数据库迁移(
database/migrations/xxxx_create_users_table.php):
<?php
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->nullable();
});
}
# 执行迁移,创建数据表
php artisan migrate
- 写 Controller(
app/Http/Controllers/UserController.php):
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index()
{
$users = User::orderBy('created_at', 'desc')->paginate(10);
return view('users.index', compact('users'));
}
public function create()
{
return view('users.create');
}
public function store(Request $request)
{
User::create($request->validate([
'name' => 'required|min:2',
'email' => 'required|email|unique:users',
]));
return redirect()->route('users.index');
}
public function edit(User $user)
{
return view('users.edit', compact('user'));
}
public function update(Request $request, User $user)
{
$user->update($request->validate([
'name' => 'required|min:2',
'email' => 'required|email|unique:users,email,' . $user->id,
]));
return redirect()->route('users.index');
}
public function destroy(User $user)
{
$user->delete();
return redirect()->route('users.index');
}
}
- 添加路由(
routes/web.php):
Route::resource('users', UserController::class);
- 创建视图文件(
resources/views/users/index.blade.php):
<h1>用户管理</h1>
<a href="{{ route('users.create') }}">➕ 添加用户</a>
<table border="1" cellpadding="5">
<tr><th>ID</th><th>姓名</th><th>邮箱</th><th>操作</th></tr>
@foreach ($users as $user)
<tr>
<td>{{ $user->id }}</td>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
<td>
<a href="{{ route('users.edit', $user) }}">✏️ 编辑</a>
<form action="{{ route('users.destroy', $user) }}" method="POST" style="display:inline">
@csrf
@method('DELETE')
<button type="submit">🗑️ 删除</button>
</form>
</td>
</tr>
@endforeach
</table>
{{ $users->links() }}
预期输出:访问 http://127.0.0.1:8000/users 显示用户列表,有分页、编辑、删除按钮。
一句话解释:Route::resource 一行代码定义了 7 条 RESTful 路由,Eloquent 自动处理 SQL,Laravel 自动做 CSRF 防护。
项目 3(15 分钟):CSV 批量导入用户
目标:上传 CSV 文件,批量导入用户到数据库。
步骤:
- 安装处理 CSV 的包:
composer require league/csv
- 在
UserController添加导入方法:
use League\Csv\Reader;
public function import(Request $request)
{
$request->validate([
'csv_file' => 'required|mimes:csv,txt|max:10240',
]);
$csv = Reader::createFromPath($request->file('csv_file')->getRealPath());
$csv->setHeaderOffset(0);
$count = 0;
foreach ($csv->getRecords() as $record) {
User::firstOrCreate(
['email' => $record['email']],
['name' => $record['name']]
);
$count++;
}
return redirect()->route('users.index')->with('success', "成功导入 {$count} 条数据");
}
- 在
resources/views/users/index.blade.php添加上传表单:
<hr>
<h3>📤 CSV 批量导入</h3>
<form action="{{ route('users.import') }}" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="csv_file" accept=".csv,.txt">
<button type="submit">导入</button>
</form>
<p><strong>CSV 格式要求:</strong>第一行是表头(name,email),后续每行一条用户数据</p>
- 创建测试 CSV 文件
test_users.csv:
name,email
王五,wangwu@example.com
赵六,zhaoliu@example.com
预期输出:
# 上传 CSV 后页面显示:
成功导入 2 条数据
一句话解释:firstOrCreate 像个「智能秘书」——如果邮箱已存在就跳过,不存在才创建,避免重复数据。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:路由顺序搞反——404 了!
// ❌ 错误:动态路由放在静态路由前面
Route::get('/user/{id}', ...); // 这会匹配所有 /user/xxx,包括 /user/create
Route::get('/user/create', ...); // 永远执行不到!
// ✅ 正确:静态路由放前面
Route::get('/user/create', ...);
Route::get('/user/{id}', ...);
坑 2:没加 @csrf——表单提交被拦截!
<!-- ❌ 错误:Laravel 默认开启 CSRF 防护,不加 token 会 419 错误 -->
<form action="/users" method="POST">
<input name="name">
</form>
<!-- ✅ 正确:加 @csrf 指令 -->
<form action="/users" method="POST">
@csrf
<input name="name">
</form>
坑 3:Eloquent 查不到就崩——用 findOrFail
// ❌ 错误:ID 不存在时返回 null,后面调用 $user->name 报错
$user = User::find($id);
// ✅ 正确:找不到就抛 404 页面
$user = User::findOrFail($id);
坑 4:数据库迁移——字段改了就忘了 --step
# ❌ 错误:修改迁移文件后直接 migrate,数据库不生效
php artisan migrate
# ✅ 正确:重置后重新迁移(开发环境)
php artisan migrate:fresh
坑 5:dd() 调试留在了生产环境
// ❌ 错误:dd() 会中断程序,上线后用户看到一堆调试信息
$users = User::all();
dd($users);
// ✅ 正确:用日志或只开发环境用的调试
\Log::info('Users count: ' . $users->count());
// 或用 PHP 内置的 debug_backtrace
性能小贴士:N+1 查询问题
// ❌ 错误:在循环里查关联数据,100 个用户 = 101 次查询
@foreach ($users as $user)
{{ $user->posts->count() }} <!-- 每次都查一次 posts 表 -->
@endforeach
// ✅ 正确:预加载,一次查完
$users = User::with('posts')->get();
// 等价于 SELECT * FROM users 和 SELECT * FROM posts WHERE user_id IN (1,2,3...)
调试技巧:用 Laravel Tinker 实时交互
# 打开 Tinker,进入交互式调试环境
php artisan tinker
# 里面可以实时执行 PHP 代码
>>> User::count()
=> 15
>>> User::first()->name
=> "张三"
>>> exit
✏️ 练习题
练习 1(2 分钟):改路由参数
- 输入:访问
/user/666 - 预期输出:页面显示「用户ID是:666」
- 提示:找到
routes/web.php,添加一条Route::get('/user/{id}', ...)
练习 2(2 分钟):加个判断
- 输入:在练习 1 基础上,如果 ID 是
1,显示「这是管理员」 - 预期输出:访问
/user/1显示管理员提示,否则显示用户 ID - 提示:在路由的闭包函数里加
if ($id === '1')
练习 3(3 分钟):换个数据源
- 输入:把「用户列表」改成「文章列表」(articles 表,有 title 和 content 字段)
- 预期输出:访问
/articles显示文章列表 - 提示:复制
User.php改名为Article.php,改$fillable和$table,然后php artisan make:controller ArticleController -r
练习 4(5 分钟):串起项目 2 和 3
- 输入:在项目 3 的 CSV 导入基础上,加一个「导出 CSV」功能
- 预期输出:点击按钮下载
users.csv - 提示:用
League\Csv\Writer,把$users转成 CSV 格式返回
练习 5(3 分钟):读懂报错
- 输入:用户提交表单后页面显示「419 Page Expired」
- 预期输出:知道原因是 CSRF token 过期或缺失
- 提示:检查表单里有没有
@csrf指令
作业:做一个「通讯录管理工具」
需求描述:公司内部通讯录,支持:
- 查看所有同事
- 添加新同事(姓名、手机号、部门)
- 编辑同事信息
- 删除同事
- 搜索同事(按姓名或部门)
功能点:
1. 用 Eloquent ORM 操作 SQLite 数据库
2. 用 Blade 模板展示数据
3. 支持搜索过滤
加分项:
1. 分页显示(每页 5 条)
2. 手机号格式验证
验收标准:
- 能跑起来(php artisan serve)
- CRUD 操作正常
- 搜索能过滤结果
- 代码有注释
提交方式:评论区贴关键代码或 GitHub 链接
📚 总结 + 资源
本分钟学到的 3 个核心点:
1. 路由是 URL 到函数的映射,用 Route::get() 定义,用 Route::resource 一行定义 CRUD 路由
2. MVC 架构让代码分工明确——Model 管数据、View 管展示、Controller 管逻辑
3. Eloquent ORM 让你用对象操作数据库,User::all() = SELECT,User::create() = INSERT
延伸学习资源:
- Laravel 官方文档 —— 最权威的教材
- 《Laravel 之道》—— 国内写的实战派教程
- Laracasts —— 视频教程,老外做的,很细
互动钩子:你在项目里用过 Laravel 的哪个功能?遇到过什么坑?评论区聊聊,老粉优先回复!
下章预告:Laravel 很强大,但它还是「同步」的——一个请求进来,必须等上一个处理完才能处理下一个。下一章我们要认识一个能「同时处理 10 万连接」的大杀器——Swoole 协程与异步 PHP,让 PHP 跑得像 Node.js 一样快。

评论(0)