PHP整洁架构在Laravel中的落地实践:从理论到工程化
目录导读

为什么Laravel项目需要整洁架构?
许多团队在Laravel中快速开发后,发现Controller越来越臃肿、Model逐渐成为“上帝类”,这正是传统MVC边界模糊的结果。整洁架构(Clean Architecture) 由Robert C. Martin提出,强调业务规则与框架解耦,在Laravel中应用整洁架构的核心价值在于:
- 可测试性:业务逻辑不依赖Eloquent、HTTP请求等,可独立单元测试。
- 框架无关性:未来迁移到其他框架只需替换适配层。
- 领域聚焦:开发团队通过用例(Use Case)而非路由来理解业务。
问:整洁架构与Laravel的MVC冲突吗?
答:不冲突,Laravel作为“适配器”位于最外层,而MVC中的Model应拆分为实体(Entity)与仓库(Repository)等小角色。
整洁架构核心四层模型解析
整洁架构通常分为四层(由内到外):
- 领域层(Domain):包含实体、值对象、领域服务。无任何框架依赖。
- 应用层(Application):协调领域对象完成业务场景,定义接口(如仓库接口、事件接口)。
- 基础设施层(Infrastructure):实现数据库、队列、文件系统等具体技术。依赖Laravel。
- 展示/交付层(Presentation):Controller、路由、视图,负责将请求转化为用例输入。
关键规则:依赖倒置——内层定义接口,外层实现;外层只依赖抽象,不直接依赖内层具体实现。
问:Laravel的Eloquent模型应该放在哪一层?
答:Eloquent模型属于基础设施层,因为它耦合了ORM,领域层只应使用纯PHP对象(POPO)或通过Repository接口获取数据。
Laravel中的目录结构重组方案
不建议直接在app/下打乱所有文件,一种推荐的目录设计如下:
app/
├── Domain/
│ ├── Entities/ # 纯PHP实体,无ORM
│ ├── ValueObjects/ # 值对象(如Email, Money)
│ └── Repositories/ # 接口定义(如UserRepositoryInterface)
├── Application/
│ └── UseCases/ # 用户注册用例、订单创建用例等
├── Infrastructure/
│ ├── Persistence/ # Eloquent模型、Repository实现
│ ├── Queue/ # 队列作业
│ └── Externals/ # 第三方API适配器
└── Presentation/
├── Http/
│ ├── Controllers/
│ └── Requests/ # 表单请求验证
└── Providers/ # 服务提供者
关键点:Domain目录绝对不使用use Illuminate\Database\Eloquent\Model;,Repository接口使用use App\Domain\Entities\User;。
问:Laravel默认的app/Models目录是否要删除?
答:可以保留,但将Eloquent模型视为基础设施实现,同时在Domain/Entities中创建对应的纯PHP实体用于业务逻辑。
接口隔离与依赖注入实战
步骤1:在Domain层定义Repository接口
// app/Domain/Repositories/UserRepositoryInterface.php
namespace App\Domain\Repositories;
use App\Domain\Entities\User;
interface UserRepositoryInterface
{
public function findById(int $id): ?User;
public function save(User $user): void;
}
步骤2:在Infrastructure层实现
// app/Infrastructure/Persistence/EloquentUserRepository.php
class EloquentUserRepository implements UserRepositoryInterface
{
public function findById(int $id): ?User
{
$eloquentUser = UserModel::find($id);
return $eloquentUser ? new User($eloquentUser->toArray()) : null;
}
// 注意:这里将Eloquent数据转换为纯实体
}
步骤3:在ServiceProvider中绑定
// app/Providers/AppServiceProvider.php
$this->app->bind(
UserRepositoryInterface::class,
EloquentUserRepository::class
);
问:为什么不在Controller中直接注入Eloquent模型?
答:整洁架构要求Controller只依赖用例(Use Case),而用例依赖接口,这样当未来切换数据库(如MongoDB)时,只需新增MongoUserRepository实现并修改绑定即可。
实体与用例的Laravel实现范例
实体示例(Domain层)
// app/Domain/Entities/User.php
class User
{
public function __construct(
private ?int $id,
private string $name,
private Email $email, // 值对象
private \DateTimeImmutable $createdAt
) {}
public function changeName(string $newName): void
{
if (strlen($newName) < 2) {
throw new \InvalidArgumentException('Name too short');
}
$this->name = $newName;
}
// 其他业务方法...
}
用例示例(Application层)
// app/Application/UseCases/RegisterUserUseCase.php
class RegisterUserUseCase
{
public function __construct(
private UserRepositoryInterface $userRepo,
private EventDispatcherInterface $eventDispatcher
) {}
public function execute(RegisterUserRequest $request): User
{
$user = new User(
null,
$request->name,
new Email($request->email),
new \DateTimeImmutable()
);
$this->userRepo->save($user);
$this->eventDispatcher->dispatch(new UserRegistered($user));
return $user;
}
}
Controller调用(Presentation层)
class UserController extends Controller
{
public function register(RegisterUserRequest $request, RegisterUserUseCase $useCase)
{
$user = $useCase->execute(
new RegisterUserRequestDTO($request->validated())
);
return response()->json(['id' => $user->getId()], 201);
}
}
问:是否所有的业务场景都必须经过UseCase?
答:是的,甚至简单的“获取用户列表”也应定义为ListUsersUseCase,以保持架构一致性。
常见误区与问答集锦
Q1:整洁架构会导致大量的样板代码吗?
A:初期确实会增加接口、转换器等代码,但长期看维护成本降低,可使用Laravel的Repository模式作为过渡,但需注意接口只能依赖领域实体。
Q2:Laravel的Eloquent ORM怎么在实体中使用?
A:不直接使用,实体是POPO,ORM只在基础设施层做数据持久化,如需保持聚合根缓存,可在实体类中注入Arrayable接口。
Q3:如何测试UseCase?
A:单元测试时Mock Repository接口,使用PHPUnit,由于不依赖Laravel的数据库,测试极快。
Q4:频繁的类型转换会不会影响性能?
A:在中小型项目中性能损耗可忽略,当数据量大时,可使用DTO直接映射或优化Repository的缓存策略。
Q5:Laravel的FormRequest验证应该放在哪一层?
A:FormRequest属于交付层的输入验证,用于确认数据合法性,业务规则验证(如“用户名必须唯一”)应放在UseCase中,因为需要访问Repository。
总结与最佳实践
- 坚持单向依赖:内层(Domain)绝不引用外层(Framework)的类名。
- 适配器模式:将Laravel的数据库、队列、文件系统等视为“外设”,通过接口解耦。
- 值对象不可变:如Email、Money应设计为不可变类,减少副作用。
- 持续重构:不必一开始就完美,从抽取Repository接口、隔离Entity开始,逐步迭代。
推荐工具:使用rector/rector自动迁移代码,deptrac检查依赖方向。
最后提醒:整洁架构不是银弹,对于简单CRUD项目,过度设计反而有害,但一旦业务逻辑变复杂,它将是保护业务核心不被框架绑架的最佳方案。