Laravel- 应用架构

声明:本文并非博主原创,而是来自对《Laravel 4 From Apprentice to Artisan》阅读的翻译和理解,当然也不是原汁原味的翻译,能保证90%的原汁性,另外因为是理解翻译,肯定会有错误的地方,欢迎指正。

欢迎转载,转载请注明出处,谢谢!
转载自 https://segmentfault.com/a/1190000009438428#articleHeader0

应用架构

简介

这一章是哪出戏?对于使用框架创建应用这是非常普遍的。很多开发者会提出这样的问题,因为在他们脑仁里已经存在这样的观念,“模型”就是“数据库”。所以通常,控制器被用来和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命名空间的方式组织到同名目录下。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容