# 中大型项目架构

Laravel 的初学者分为两种：

* 一种是乖乖的将程序在写 MVC 架构内，这将导致 Controller 和 Model 非常臃肿，日后代码很难维护。
* 一种是不知道将程序写在哪一个Class内而犹豫不决。

本文整理出最适合 Laravel 的中大型项目架构，兼具易维护、易扩展、易复用和易测试的特点。

## Controller 过于肥大

受RoR的影响，初学者常认为 MVC 架构就是 Model，View，Controller：

* Model 就是数据库。
* Controller 负责与 HTTP 通信，调用 Model 与 View。
* View 就是 HTML。

假如按照此定义，以下需求应该写在哪里呢？

1.发送 Email，使用外部 API。

2.使用 PHP 写的逻辑。

3.依需求将显示格式作转换。

4.依需求是否显示某些资料。

5.依需求显示不同资料。

其中1, 2 属于商业逻辑，而3, 4, 5 属于显示逻辑，若依照一般人对 MVC 的定义，Model 是数据库，而 View 是HTML，以上这些需求都不能写在Model 与View，只能勉强写在Controller。

因此初学者开始将大量程序写在 Controller，造成 Controller 的肥大难以维护。

## Model 过于肥大

{% hint style="info" %}
既然逻辑写在 Controller 不方便维护，那我将逻辑都写在 Model 就好了？
{% endhint %}

当你将逻辑从 Controller 搬到 Model 后，虽然 Controller 变瘦了，但却肥了 Model，Model 从原本只处理数据库逻辑，现在变成还要负担商业逻辑与显示逻辑，结果更惨。

Model 代表数据库吗？把它想成是 Eloquent class就好，数据库逻辑应该写在 repository 里，这也是为什么 Laravel（Lumen） 已经没有 Models目录，Eloquent class 仅仅是放在 app 根目录下而已。

## 中大型项目架构

那我们该怎么写呢？别将我们的思维局限在 MVC 内 :

* Model : 仅当成 Eloquent class。
* Repository : 辅助 Model，处理数据库逻辑，然后注入到 service。
* Service : 辅助 Controller，处理商业逻辑，然后注入到 Controller。
* Controller : 接收 HTTP request，调用其他 service。
* Presenter : 处理显示逻辑，然后注入到 View。
* View : 使用 blade 将资料 binding 到 HTML。

其中蓝色为原本的 MVC，而紫色为本文要介绍的的重点 : Repository 模式，Service 模式与 Presenter 模式。

![](https://1762830433-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LbwRVdbqczwQuDbG6KX%2F-LwXhKCKGpXnVsQvPymK%2F-LwXhLtEaF8lhPPMiwWb%2Farch002.svg?generation=1576835314511694\&alt=media)

> 箭头表示物件依赖注入的方向。

我们可以发现 MVC 架构还在，由于 SOLID 的单一职责原则与依赖反转原则 :

* 我们将数据库逻辑从 Model 分离出来，由 repository 辅助 Model，将 Model 依赖注入进 repository。
* 我们将商业逻辑从 Controller 分离出来，由 service 辅助 Controller，将 service 依赖注入进 Controller。
* 我们将显示逻辑从 View 分离出来，由 presenter 辅助 View，将 presenter 依赖注入进 View。

建立目录在 src 的 Bundle 目录下建立 Repositories，Services 与 Presenters 目录。

别害怕在 Laravel 预设目录以外建立的其他目录，根据 SOLID 的单一职责原则，class 功能越多，责任也越多，因此越违反单一职责原则。

所以你应该将你的程序分割成更小的部分，每个部分都有它专属的功能，而不是一个 class 功能包山包海，也就是所谓的`万能类`。

所以整个项目不应该只有 MVC 三个部分，放手根据你的需求建立适当的目录，并将适当的 class 放到该目录下，只要我们的class 有namespace 帮我们分类即可。

## Repository

由于篇幅的关系，将 repository 独立成专文讨论，请参考[如何使用 Repository 模式](https://espier.gitbook.io/espier/kuang-jia/jia-gou-si-xiang/architecture-repository) ?

## Service

由于篇幅的关系，将 service 独立成专文讨论，请参考如何使用 Service 模式?

## 单元测试

由于现在Model、View、Controller 的依赖都已经拆开，也都使用依赖注入，因此每个部分都可以单独的做单元测试，如要测试service，就将repository 加以 mock，也可以将其他 service 加以 mock。

## 结论

本文谈到的架构只是开始，你可以依照实际需求增加更多的目录与 class，当你发现你的 MVC 违反 SOLID原则时，就大胆的将 class 从 MVC 拆开重构，然后依照以下手法 :

* 建立新的 class 或 interface。
* 将依赖类依赖注入到 class。
* 在 class 内处理他的职责。
* 将 class 或 interface 注入到 Controller 或 View。

最后搭配单元测试，测试重构后的架构是否与原来的需求结果相同。
