首页 > 其他分享 >Laravel:MVC分层

Laravel:MVC分层

时间:2022-11-25 12:32:14浏览次数:39  
标签:Laravel 控制器 app 逻辑 QuickBill 分层 MVC php 目录


简介

这个类要放到哪儿?这可能是基于框架构建应用时非常常见的问题。很多开发者都会有这个疑问,因为他们被灌输了「模型」就是「数据库」这种概念。因此,在控制器里面处理 HTTP 请求,在模型类里面操作数据库增删改查,在视图里编写要显示的HTML,成了开发者们约定俗成的规定。但是,发送电子邮件的类要放到哪儿?验证数据的类呢?调用外部 API 的类呢?在这一章中,我们将介绍 Laravel 框架中良好的应用结构,并打破那些常见的阻止开发者做出好设计的心理障碍。

MVC是慢性谋杀

阻止开发者做出好设计的最大拦路虎就是一个简单的首字母缩写词:M-V-C。模型(Model)、视图(View)、控制器(Controller)已经统治 Web 框架的设计思想好多年了。这种思想的泛滥某种程度上因为 Ruby on Rails 的流行。然而,如果你问一个开发者「模型」的定义是什么,通常,你会听到他嘟哝着什么「数据库」之类的东西。这么说,模型就是数据库了。不管这意味着什么,模型里包含了关于数据库的一切。但是,你很快就会知道,你的应用程序需要的不仅仅是一个简单的数据库访问类。它需要更多的逻辑,如数据验证、调用外部服务、发送电子邮件等等。

「模型」这个单词的含义太模糊了,以至于显得没有任何意义。更具体来讲,模型是用来将我们的应用划分成更小、更清晰的类,使得各代码部分有着明确的权责。

所以,怎么解决这个问题呢?很多开发者开始将业务逻辑封装到控制器里面。当控制器庞大到一定规模,它们将会需要复用其他控制器中的业务逻辑。大部分开发人员并没有将这些业务逻辑提取到另外的类里面,而是错误的以为需要在控制器里面调用其他的控制器方法。这种模式通常被称为「HMVC」。遗憾的是,这种模式通常也意味着糟糕的程序设计,以及控制器太过复杂。

HMVC 意味着糟糕的设计:你觉得需要在控制器里面调用其他的控制器?这通常意味着糟糕的程序设计,以及你的控制器里面包含了过多的业务逻辑。好的做法是把控制器中的业务逻辑提取出来,放到一个新的第三方类里面,通常,我们将这种第三方类称之为服务类,这样你就可以在所有其他控制器里面注入服务类并使用它们了。

有一种更好的方式来构建应用程序结构。但首先我们要忘掉以往我们被灌输的关于「模型」的一切。干脆点,让我们直接删掉模型目录,重新开始吧!

 

再见,模型

删掉你的 ​​models​​​ 目录了吗?还没删就赶紧删了!我们将要在 ​​app​​​ 目录下创建一个新的目录,目录名就以我们这个应用的名字来命名,比如 ​​QuickBill​​。我们将继续使用在前面章节中编写的那些接口和类。

注意使用场景:记住,如果你在构建一个很小的 Laravel 应用,那在​​models​​ 目录下写几个 Eloquent 模型其实挺合适的。但在本章节,我们主要关注如何开发适用于分层架构的大型复杂项目。

所以,我们现在有了个 ​​app/QuickBill​​​ 目录,它和应用目录下的其他目录如 ​​Http​​​ 还有 ​​Console​​​ 都是平级的。在 ​​QuickBill​​​ 目录下我们还可以创建几个其他目录,例如 ​​Repositories​​​ 和 ​​Billing​​​ 目录。目录都创建好以后,别忘了在 ​​composer.json​​​ 文件里通过 PSR-4 自动载入机制将它们注册到 ​​QuickBill​​ 命名空间下:

"autoload": {
"classmap": [
"database/seeds",
"database/factories"
],
"psr-4": {
"App\\": "app/",
"QuickBill\\": "app/QuickBill"
}
},

现在,我们把所有 Eloquent 模型类都放到 ​​QuickBill​​​ 目录下面。这样我们就能很方便的以 ​​QuickBill\User​​​、​​QuickBill\Payment​​​ 这种方式来使用它们。​​Repositories​​​ 目录下包含 ​​PaymentRepository​​​ 和​​UserRepository​​​ 这种仓库类,仓库类里面包含了所有对数据的访问功能,比如 ​​getRecentPayments​​​ 和 ​​getRichestUser​​​。​​Billing​​ 目录包含了调用第三方支付服务(如 Stripe 和 Balanced)的类和接口。整个目录结构现在应该类似这样:

// app
// QuickBill
// Repositories
-> UserRepository.php
-> PaymentRepository.php
// Billing
-> BillerInterface.php
-> StripeBiller.php
// Notifications
-> BillingNotifierInterface.php
-> SmsBillingNotifier.php
User.php
Payment.php

数据验证放在哪?在哪儿进行数据验证常常困扰着开发人员。可以考虑将数据验证方法写进你的「实体」类里面(例如 ​​User.php​​​ 和 ​​Payment.php​​​)。方法名可以设置为 ​​validForCreation​​​ 或 ​​hasValidDomain​​​。或者你也可以专门创建一个验证器类 ​​UserValidator​​​,放到 ​​Validation​​ 命名空间下,然后将这个验证器类注入到你的 Repository 类里面。两种方式你都可以试试,看哪个你更喜欢!当然在 Laravel 5.* 中,你不需要自己创建验证器类了,通过 Laravel 自带的验证器类就可以满足你的所有需求。

摆脱了 ​​models​​ 目录的束缚后,你通常就能克服实现好的架构设计的心理障碍,也就能够构建一个更合适应用的目录结构。当然,你构建的每一个应用程序都会有一定的相似之处,因为不管多复杂的应用程序都需要一个数据访问层(Repository),以及一些外部服务层等等。

别害怕目录:不要惧怕创建更多目录来组织管理应用。将整个应用切割成多个细分的功能组件总是必要的,每一个功能组件都专注于某一项职责。跳出「模型」的框框来思考总是有帮助的。例如,我们之前就讨论过,你可以创建一个 ​​Repositories​​ 目录来存放所有的数据访问类。

核心思想就是分层

你可能已经注意到,优化应用目录结构的关键就是对不同组件的责任进行划分,或者说为不同的职责创建不同的层。控制器只负责接收和响应 HTTP 请求,然后调用合适的业务逻辑层的类。你的业务逻辑/领域逻辑层才是应用最核心的部分,其中包含了读取数据,验证数据,执行支付,发送电子邮件,还有程序里所有其他功能的代码。事实上,你的领域逻辑层不需要知道任何关于「Web」的事情!Web 层仅仅是一种访问应用程序的传输机制,关于 Web 和 HTTP 请求的一切不应该超出路由和控制器层的范围。做出好的架构设计的确很有挑战性,但好的架构设计也会带来可维护的、更加清晰的代码。

举个例子,与其在业务逻辑类里面直接获取 Web 请求,不如把 Web 请求通过控制器传递给业务逻辑类。这个简单的改动会将你的业务逻辑类和「Web」层解耦,并且不必担心怎么去模拟 Web 请求,就可以轻松测试业务逻辑类:

class BillingController extends BaseController
{

public function __construct(BillerInterface $biller)
{
$this->biller = $biller;
}

public function postCharge(Request $request)
{
$this->biller->chargeAccount(Auth::user(), $request->input('amount'));
return view('charge.success');
}

}

现在 ​​chargeAccount​​​ 方法更容易测试了,由于我们不再需要在 ​​BillerInterface​​​ 实现类中使用 ​​User​​​ 和 ​​Request​​类,只需将用户和金额传递到该方法即可。

编写可维护性应用程序的关键之一,就是职责分离。要时常检查一个类是否管得太宽,知道一些它不该知道的。你要常常问自己「这个类是否需要关心X?」如果答案是否定的,那么就要把这块逻辑提取出来放到另一个类里面,然后用依赖注入的方式将其注入进来。

如何判断一个类是否管得太宽?一个有用的方法就是检查你为什么要改这块代码。举个例子,当我们想调整通知逻辑的时候,是否需要修改 ​​Biller​​​ 的实现代码?当然不需要,​​Biller​​ 实现只关注支付,它与通知逻辑应当仅通过契约来进行交互。在处理代码时使用这种思路,可以帮助你快速找出应用中需要改进的地方。

东西都放哪儿?

当通过 Laravel 开发应用时,你可能会困惑于应该把各种「东西」放在哪里。例如,辅助函数要放在哪里?事件监听器要放在哪里?视图组件要放在哪里?答案可能出乎你的意料 —— 「随便,放哪儿都行!」Laravel 并没有很多关于这方面的文件系统上的约定。不过,这个答案的确不能让人满意,所以下面我们就这个问题展开讨论,一起探讨这些「东西」究竟可以放在哪里。

辅助函数

我们可以在 ​​app​​​ 目录下创建一个 ​​helpers.php​​​ 文件,然后将自定义的辅助函数都放到这个文件中。当然,由于这个文件里面包含的不是类,不适合通过命名空间引用其中的函数,需要在 ​​composer.json​​ 中全局注册它:

"autoload": {
...
"files": [
"app/helpers.php"
]
},

然后运行 ​​composer dump-auto​​​ 重新注册自动加载映射关系。然后就可以在应用中使用 ​​app/helpers.php​​ 中定义的辅助函数了。

事件监听器

事件监听器当然不该放到 ​​routes.php​​​ 文件里面,所以我们要找另外的地方来存放。事实上,在 Laravel 5.* 中,当我们通过 ​​php artisan make:listener​​​ 命令创建时间监听器时,系统会自动在 ​​app​​​ 目录下创建 ​​Listeners​​ 子目录,并将生成的监听器类放到该目录下。所以我们对于自定义的事件监听器类,放到该目录下即可。

错误处理器

在 Laravel 5.* 版本中,默认情况下所有的异常都是通过 ​​App\Exceptions\Handler​​​ 来处理的,你也可以通过自定义的异常类来处理,自定义异常类可以放到 ​​app/Exceptions​​ 目录下。

其他

通常,只要遵循 PSR-4 规范就可以在应用目录结构中保持类的整齐。结合你目前为止学习到的知识,对于什么代码要放在什么地方这个问题,应当可以给出一个有理有据的答案了。但永远不要拒绝试验。Laravel 的美妙之处就是你可以做出最适合你自己应用的约定。去探索和发现最适合你自己应用的结构吧,别忘了和他人分享你的见解!

例如,你可能受到我们之前例子的启发,在 ​​QuickBill​​​ 中创建一个 ​​Providers​​ 目录来存放所有你自定义的服务提供者,目录结构类似这样:

// app
// QuickBill
// Billing
// Extensions
//Pagination
-> Environment.php
// Providers
-> EventPusherServiceProvider.php
// Repositories
User.php
Payment.php

注意上面的例子,我们有 ​​Providers​​​ 和 ​​Extensions​​​ 两个命名空间。所有你自定义的服务提供者都可以放到 ​​Providers​​​ 命名空间下,而 ​​Extensions​​ 命名空间可以用来存放你对框架核心进行扩展的类。

标签:Laravel,控制器,app,逻辑,QuickBill,分层,MVC,php,目录
From: https://blog.51cto.com/u_13940603/5886353

相关文章

  • Laravel:服务提供者
    作为引导者Laravel服务提供者主要用来进行注册服务容器绑定(即注册接口及其实现类的绑定)。事实上,Laravel有好几十个服务提供者,用于管理框架核心组件的容器绑定。几乎框架里......
  • Laravel:接口即契约
    强类型与鸭子类型在之前的章节里,我们讨论了依赖注入的基础知识:什么是依赖注入;如何实现依赖注入;依赖注入有什么好处。之前的例子中也模拟了将接口注入到类里面的过程。在我们......
  • C#指定获取Json对象里的指定值 .net framework mvc示例
    C#中C#指定获取Json对象里的指定值https://www.cnblogs.com/sky6699/p/6889762.html 获取的json数据类型样式{"status":0,"msg":"","data":[......
  • laravel 模型追加字段并赋值
    //追加字段protected$appends=['state'];//赋值publicfunctiongetStateAttribute(){$status=$this->attributes['status'];if($status==1){......
  • GZip/Deflate Compression in ASP.NET MVC
    GZip/DeflateCompressioninASP.NETMVC CompressionCaveatsHttpcompressionisverycoolandprettyeasytoimplementinASP.NETbutyouhavetobecareful......
  • laravel dcat-admin upload multiple images
    $form->multipleImage('images')->sortable()->compress(['width'=>750,'quality'=>90,])->uniqueName()->saveAsString()->saving(function($value)use($form){......
  • Flink的API分层、架构与组件原理、并行度、任务执行计划、chain
    Flink的API分层注:越底层API越灵活,越上层的API越轻便StatefulStreamProcessing•位于最底层,是coreAPI的底层实现•processFunction•利用低阶,构建一些新的组......
  • 求超大文件上传方案( SpringMVC )
    ​ 对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线......
  • springmvc环境部署报错: NoClassDefFoundError: org/springframework/web/cors/CorsPro
    部署springmvc的时候报出一个很奇怪的错误:org.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname‘org.springframework.web.servlet.......
  • Spring —— SpringMVC简介
    SpringMVCSpringMVC技术与Servlet技术功能等同,均属于web层开发技术是一种基于java实现MVC模型的轻量级Web框架 SpringMVC入门案例           ......