PHP整洁架构在Laravel中怎么用

wen PHP项目 2

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

目录导读

  1. 为什么Laravel项目需要整洁架构?
  2. 整洁架构核心四层模型解析
  3. Laravel中的目录结构重组方案
  4. 接口隔离与依赖注入实战
  5. 实体与用例的Laravel实现范例
  6. 常见误区与问答集锦
  7. 总结与最佳实践

PHP整洁架构在Laravel中怎么用

为什么Laravel项目需要整洁架构?

许多团队在Laravel中快速开发后,发现Controller越来越臃肿、Model逐渐成为“上帝类”,这正是传统MVC边界模糊的结果。整洁架构(Clean Architecture) 由Robert C. Martin提出,强调业务规则与框架解耦,在Laravel中应用整洁架构的核心价值在于:

  • 可测试性:业务逻辑不依赖Eloquent、HTTP请求等,可独立单元测试。
  • 框架无关性:未来迁移到其他框架只需替换适配层。
  • 领域聚焦:开发团队通过用例(Use Case)而非路由来理解业务。

问:整洁架构与Laravel的MVC冲突吗?
答:不冲突,Laravel作为“适配器”位于最外层,而MVC中的Model应拆分为实体(Entity)仓库(Repository)等小角色。


整洁架构核心四层模型解析

整洁架构通常分为四层(由内到外):

  1. 领域层(Domain):包含实体、值对象、领域服务。无任何框架依赖
  2. 应用层(Application):协调领域对象完成业务场景,定义接口(如仓库接口、事件接口)。
  3. 基础设施层(Infrastructure):实现数据库、队列、文件系统等具体技术。依赖Laravel
  4. 展示/交付层(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。


总结与最佳实践

  1. 坚持单向依赖:内层(Domain)绝不引用外层(Framework)的类名。
  2. 适配器模式:将Laravel的数据库、队列、文件系统等视为“外设”,通过接口解耦。
  3. 值对象不可变:如Email、Money应设计为不可变类,减少副作用。
  4. 持续重构:不必一开始就完美,从抽取Repository接口、隔离Entity开始,逐步迭代。

推荐工具:使用rector/rector自动迁移代码,deptrac检查依赖方向。

最后提醒:整洁架构不是银弹,对于简单CRUD项目,过度设计反而有害,但一旦业务逻辑变复杂,它将是保护业务核心不被框架绑架的最佳方案。

抱歉,评论功能暂时关闭!