声明:本文并非博主原创,而是来自对《Laravel 4 From Apprentice to Artisan》阅读的翻译和理解,当然也不是原汁原味的翻译,能保证90%的原汁性,另外因为是理解翻译,肯定会有错误的地方,欢迎指正。
欢迎转载,转载请注明出处,谢谢!
应用架构
简介
这一章是哪出戏?对于使用框架创建应用这是非常普遍的。很多开发者会提出这样的问题,因为在他们脑仁里已经存在这样的观念,“模型”就是“数据库”。所以通常,控制器被用来和HTTP交互,模型就是和数据库_打交道_,视图就是还有HTML代码的那部分。但是,对于那些比如发送邮件的类、验证数据类、访问接口的类该怎么区分呢?本章我们就使用Laravel构建好的架构进行探讨,打破那些固话在你心中的概念,让开发回归本质。
MVC会弄死你的
阻碍我们的一种设计即:M-V-C。模型,视图,控制器,这种框架思维已经控制开发人员很多年了。这种思维来源于Ruby On Rails。如果,让一个程序员去解释什么是“模型”,通常都会听到将其和“数据库”关联的答案。据说,模型_就是_数据库。模型包含了数据库的一切。但是,很快你就会发现,在简单的数据库访问类之上还有很多额外的逻辑。他需要我们进行数据验证,调取额外的服务,发送邮件,等等。
什么是模型?
模型现在已经变的模棱两可,很难具体指代什么。根据开发中遇到的那么多词汇,我们可以理解认为,他就是为了将应用切分成小而清晰,具有特定职责的类。
那么,这种困境中的解决方案是什么?很多开发人员会在控制器之上添加更多的逻辑。当控制器变的很大的时候,需要复用其他控制器中的一些逻辑层。很多人会错误的认为_需要_在当前控制器调用其他控制器,而不是讲逻辑抽象成单独的类。这种模式通常称为“HMVC”。不幸的是,这也是糟糕的设计,通常控制器会很复杂。
HMVC(通常)预示着糟糕的设计
当觉得须要在控制器中调用其他控制器?这意味着当前设计是糟糕的,控制器里面的业务逻辑太负责。我们可以讲逻辑抽象成通用类,以便在其他控制器中进行调用。
总会有更好的程序设计。我们需要忘记以前在脑海中残留的那种“模型”的设计理念,干脆让我们删除模型目录,并重新开始。
再见,模型
是否已经把你的models
目录删除?如果没有,没关系,别再去理他。我们来在app
下建立一个新文件夹,简单的起个应用名字即可QuickBill
,在后续的讨论中,我们之前举例的那些例子都会出现。
注意使用场景
记住,如果你创建的是小型的Laravel应用,在
models
下创建几个Eloquent模型还是很合适的。而在本章中,我们关注的是拥有更多“层次”架构的复杂应用。
我们已经有了app/QuickBill
目录,他和controllers
和views
目录同级。我们可以在QuickBill
下创建一些其他目录,比如Repositories
和Billing
目录。建好目录之后,记得在composer.json
注册PSR-0自动加载。
"autoload": {
"psr-0": {
"QuickBill": "app/"
}
}
现在,我们把Eloquent类放到QuickBill
根目录下,我们就能轻松的访问到QuickBillUser
,以及QuickBillPayment
等。在Respositories
目录中创建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
。或者也可以创建一个命名空间为Validation
的UserValidator
类,并注入到repository类中。两种方法看个人喜好。
摆脱models
目录,我们就能冲破枷锁,实现好的设计。当然,我们创建的很多项目都会有相似之处,无论多么复杂的项目也都会有数据接入(存储)层,以及其他服务层等等。
不要害怕文件夹
不要因为多建文件夹而害怕,他是组织应用程序的很好方式。通常我们希望以此讲应用分割成很小的组件,每个组件都有自己特定的职责。别被“模型”束缚了思维。就像上面举的例子,我们可以创建一个
Repository
来存放所有数据接入的相关类。
处处皆分层
你应该已经注意到,好的应用设计应拥有明确的职责划分,有明确的逻辑分成。控制只是用来接收HTTP请求并请求逻辑处理类。业务处理层_才是_整个应用的中心。它包含了像数据获取,数据验证,支付处理,邮件发送类库,以及应用中的各种函数等。事实上,业务逻辑无需感知“网络”,网络仅仅接入应用的传输机制,他不应超出应用中的路由和控制器的范畴。好的架构是经得起考研的,是由清晰代码组成的可持续发展的架构。
例如,我们使用向控制器中传入网络请求输入来替代在类中直接访问网络请求实例的方法。简单的改动即将类从“网络”中解耦出来,这种方式也不用担心在测试时对请求的再次模拟了:
class BillingController extends BaseController{
public function __construct(BillerInterface $biller)
{
$this->biller = $biller;
}
public function postCharge()
{
$this->biller->chargeAccount(Auth::user(), Input::get('amount'));
return View::make('charge.success');
}
}
chargeAccount
方法可以很容易进行测试,只需传入测试数据用到的整型数据,而不是使用一个含有Request
和Input
类的BillerInterface
接口的实现类库来作为参数传入该方法。
职责分离是编写健壮应用的关键。这种关键就是一个类是否管的太多。你应该时常问自己:“是不是这个类还要关心X?”,如果答案是“不”,就将逻辑抽象出来,并用依赖注入的方式处理。
改变的原因很简单
决定类库是否足够职责分离的一个非常有用的方法就是检验自己为什么要更改这些代码。比如,在调整通知逻辑的时候,是否
Biller
接口的实现也要修改?当然不,Biller
的实现只和支付有关,只需按照约定和通知逻辑交互。保持这样的思维观念,就能帮你快速改进应用中的各个部分,使之变得健壮起来。
“瓶瓶罐罐”都放哪
当使用Laravel开发应用的时候,会经常有这样的疑问,很多“东西”不知道放哪。比如,“helper”函数放哪?事件坚挺程序放哪?视图组件又该在哪?答案可能会让你凌乱:“哪都可以”!Laravel没有文件该归属哪里的概念。然而这个答案并不是让人满意的,在继续深入之前,让我们先就上面的问题探讨下。
辅助函数
Laravel的辅助函数放在support/helpers.php
文件中。或者你也想创建这样一个自己的辅助函数文件,“start”目录就是个不错的地方。在请求应用时,start/global.php
都会被引用到,我们可以在这添加上加载自己的helpers.php
文件:
// Within app/start/global.php
require_once __DIR__.'/../helpers.php';
事件监听器
事件监听器当然不能属于routes.php
文件,放在start
文件也不合适,我们要另择地方安放他。服务提供器的目录就不错,之前我们知道,服务提供不仅是容器注册绑定的服务,还可以做很多其他事情。它可以讲很多监听器组织起来,这种方式是代码清理整洁,也不影响应用逻辑。视图组件也可以放到这个位置,他和监听器其实是类似的,都能收纳在服务提供器中。
比如,用服务提供器组织监听器:
<?php namespace QuickBillProviders;
use IlluminateSupportServiceProvider;
class BillingEventsProvider extends ServiceProvider{
public function boot()
{
Event::listen('billing.failed', function($bill)
{
// Handle failed billing event...
});
}
}
创建完提供器后,只需要简单的在app/config/app.php
配置中providers
数组添加上它就好。
注意boot方法
记住,上例中我们使用
boot
的原因,register
方法仅仅是用来将服务注册到容器的方法。
错误处理
如果应用中我们自定义了错误处理,别接管在“start”文件中,同样,像事件监听器一样,最好还放在服务提供器中进行组织。提供器可以像这样命名QuickBillErrorProvider
,并在boot
方法中讲所有自定义的错误处理注册进来,重申一下:我们要将这些代码和我们的逻辑分离开来。最终,自定义的错误处理程序如下:
<?php namespace QuickBillProviders;
use App, IlluminateSupportServiceProvider;
class QuickBillErrorProvider extends ServiceProvider {
public function register()
{
//
}
public function boot()
{
App::error(function(BillingFailedException $e)
{
// Handle failed billing exceptions ...
});
}
}
简洁的方案
当然在只有一两个错误处理方式的情况下,把他放到“start”文件也是一种简洁的方式。
其他
通常,类库应该以PSR-0规范组织在我们的应用中。命令式代码如事件监听、错误处理、以及其他“注册”类型的服务最好组织在服务提供器中。基于如上原则,我们就能决策出代码的组织规律。有一点不要太犹豫,Laravel是为了让工作方便于我们的业务,这也是Laravel的宗旨。寻找适合自己应用的结构,并分享给其他人。
如上,我们可以为所有自定义的服务提供器添加一个命名空间Providers
并创建目录组织起来:
// app
// QuickBill
// Billing
// Extensions
//Pagination
-> Environment.php
// Providers
-> EventPusherServiceProvider.php
// Repositories
User.php
Payment.php
上例中,有两个命名空间Extensions
和Providers
,自定义的服务放到Providers
目录下,对框架扩展的组件以Extensions
命名空间的方式组织到同名目录下。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。