本节摘要:Laravel 的目录就是工地的分区规划:app 是业务车间,routes 是进件登记簿,resources 是装修材料库,database 是地基图纸柜。本节逐区讲清各目录职责与 MVC 三层(模型、视图、控制器)的分工逻辑,并用一个"查询工单详情"的最小示例,把路由、控制器、模型、视图四件套串成一条可运行的线。
上一章把环境跑通后,你可能打开过项目根目录,被十几个文件夹糊了一脸。这一节的目标是让你看完就能"报得出位置":改页面去哪、改接口去哪、建表去哪。框架的目录约定不是强迫症,而是把"东西放哪"这个团队里最常见的沟通成本降到零——任何人接手,位置都一样。
| 分区 | 里面是什么 | 你最常干的事 |
|---|---|---|
| app/ | 业务代码核心车间:Http 控制器、Models 模型、Providers 提供者 | 绝大多数业务逻辑写在这里 |
| routes/ | 路由登记簿:web.php 页面路由、console.php 命令注册 | 注册每个入口 |
| resources/views | Blade 装修材料库 | 写页面模板 |
| database/ | migrations 地基图纸、seeders 填充、factories 假数据厂 | 建表改表、造测试数据 |
| config/ | 全部配置文件,一行一项 | 改队列、数据库、缓存等配置 |
| bootstrap/ | 应用装配入口(Laravel 11 的中间件、异常、路由配置都在这) | 配全局中间件与异常 |
| public/ | 唯一对外公开的目录,index.php 是所有请求的大门 | Web 服务器指向这里 |
| storage/ | 运行时杂物间:日志、编译后的模板、缓存、上传文件 | 排查日志、清缓存 |
| tests/ | 验收检测台:Feature 与 Unit 测试 | 第九章的主场 |
MVC 的分工逻辑放工地上说最直白:控制器是工头,接路由派来的单,指挥但不亲自砌墙;模型是施工队,管数据这栋楼的进出与规矩;视图是装修队,把数据装成用户看得见的页面。工头不碰水泥,装修队不画图纸——层与层之间只通过明确接口传递材料,这就是 MVC 的全部精神。

光看地图记不住,我们给第一章提到的小项目加一个"工单详情页",一次用到四件套。先登记路由:
<?php // routes/web.php use App\Http\Controllers\WorkOrderController; use Illuminate\Support\Facades\Route; Route::get('/orders/{id}', [WorkOrderController::class, 'show']) ->name('orders.show');
控制器(工头)接单,调用施工队并整理交付物:
<?php // app/Http/Controllers/WorkOrderController.php namespace App\Http\Controllers; use App\Models\WorkOrder; class WorkOrderController extends Controller { public function show(int $id) { // 工头不亲自查库,喊施工队来 $order = WorkOrder::findOrFail($id); // 把材料递给装修队渲染 return view('orders.show', ['order' => $order]); } }
模型(施工队)声明自己和哪张表、哪些字段打交道:
<?php // app/Models/WorkOrder.php namespace App\Models; use Illuminate\Database\Eloquent\Model; class WorkOrder extends Model { // 约定优先:类名 WorkOrder 默认对应 work_orders 表 protected $fillable = ['title', 'status', 'budget']; }
视图(装修队)把数据装成页面:
{{-- resources/views/orders/show.blade.php --}} <!DOCTYPE html> <html lang="zh"> <head><meta charset="utf-8"><title>工单详情</title></head> <body> <h1>工单 {{ $order->id }}:{{ $order->title }}</h1> <p>状态:{{ $order->status }} 预算:{{ $order->budget }} 元</p> </body> </html>
访问 orders 路径加一个存在的编号,页面就出来了。注意这条链路里没有人写"去哪找类":WorkOrderController 由路由文件 use 进来,WorkOrder 由 PSR-4 自动加载按命名空间定位——这就是第一章讲过的约定在起作用。
💡 关键直觉:查无此单时 findOrFail 会直接抛出 404 异常而不是返回 null。让异常替你做兜底,比在每个控制器里手写 if 判断干净得多。这个特性在第四章验证一节还会反复用到。
下一节我们打开这台工地的物资调度中心——服务容器。你会明白上面例子里 Request、模型这些依赖到底是谁送进来的。