漫谈php框架之中间件

题图
题图

市面上常见的php框架有很多,最近因为有技术需求,所以对常见的php框架的中间件进行了一些了解。各个框架尽管在目标上对php框架的定义大同小异,但是在实现方式上却各有不同,且看下文:

定义

首先什么是php的中间件?

根据zend-framework中的定义:

所谓中间件是指提供在请求和响应之间的,能够截获请求,并在其基础上进行逻辑处理,与此同时能够完成请求的响应或传递到下一个中间件的代码。

这一介绍十分的简洁,但却略显抽象,接下来我们通过例子来一个个看。

处在原始时代的CI

首先来看CI框架,php star数 12830.
作为一款非常简洁的框架,CI被吐槽的不少,但是也有很多人喜欢。首先来看它官方给出的一张请求时序图:

CI框架请求时序
CI框架请求时序

根据上文中对中间件的定义,那么对于CI框架来说,唯一称得上是内置中间件的:Security模块

Security模块是在请求进入controller之前实现的逻辑:

  • 请求在完成路由之后,进入controller之前;
  • CI框架支持通过配置的方式,决定是否启用包括“URI安全、XSS过滤、CSRF保护”在内的功能模块;
  • 一旦框架初始化时探测到模块启用,那么优先进行模块逻辑;
  • 触发安全模块,请求即告终止。

乍看起来,CI框架的中间件十分的局限,但是其实它却提供了无限的可能性。。因为CI中还提供了一个叫做Hooks的功能。即钩子。

下面来看两个个hooks的例子:

定义一个在controller逻辑之前的钩子,并指定钩子的参数、类名或函数名信息:

$hook['pre_controller'] = array(
    'class'    => 'MyClass',
    'function' => 'Myfunction',
    'filename' => 'Myclass.php',
    'filepath' => 'hooks',
    'params'   => array('beer', 'wine', 'snacks')
);

定义一个在controller逻辑之后的钩子,并直接给出其实现:

$hook['post_controller'] = function()
{
    /* do something here */
};

为什么说CI没提供什么像样的中间件但是又很灵活呢,就是因为它可以在如下的多个阶段进行挂钩子的操作。细数过来有7种之多。

从后文中可以看出,很多其他的框架可能也就会涵盖两三种阶段,因此,从这个角度上来说,CI的钩子组合而成的中间件的确很灵活。

  • pre_system阶段: 在系统执行的早期调用,这个时候只有 基准测试类 和 钩子类 被加载了, 还没有执行到路由或其他的流程;
  • pre_controller阶段: 在你的控制器调用之前执行,所有的基础类都已加载,路由和安全检查也已经完成;
  • post_controller_constructor阶段: 在你的控制器实例化之后立即执行,控制器的任何方法都还尚未调用;
  • post_controller阶段: 在你的控制器完全运行结束时执行;
  • display_override阶段: 覆盖 _display() 方法,该方法用于在系统执行结束时向浏览器发送最终的页面结果; 这可以让你有自己的显示页面的方法。注意你可能需要使用 $this->CI =& get_instance()方法来获取 CI 超级对象,以及使用 $this->CI->output->get_output()方法来 获取最终的显示数据;
  • cache_override阶段: 使用你自己的方法来替代 输出类 中的 _display_cache() 方法,这让你有自己的缓存显示机制。
  • post_system 在最终的页面发送到浏览器之后、在系统的最后期被调用。

总结来看,CI中的中间件:

  • 有很大的自由度
  • 同时支持在多个阶段对请求进行嵌入(对比下来是最全面的)
  • 钩子函数的使用成本高;
  • 支持各种diy:
    • 请求来时http校验、权限校验、额外的安全策略
    • 请求去时上报数据

大红大紫的Laravel

github star 24997
作为最近两年大红大紫的Laravel,的确也是有必要对其中间件机制进行了解:

首先Laravel提供了一个很好的中间件自动生成工具:
php artisan make:middleware OldMiddleware
由Laravel的命令行完成,这种看似简单的命令行工具其实可以对框架的扩展起到非常重要的作用。

再来看一个Laravel中典型的请求过滤器:

<?php
namespace App\Http\Middleware;
use Closure;
class OldMiddleware
{
    /**
     * 运行请求过滤器。
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->input('age') <= 200) {
            return redirect('home');
        }
        return $next($request);
    }
}

过滤器,filter,是中间件中使用最广泛的一种,很多框架里甚至filter就等同于中间件。意如其名,即是对请求Request进行某种过滤,这个过滤可以是参数上的限制、安全策略的限制、http协议的限制,只要是请求中带来的属性,都可以据此进行过滤。

同时这里也可以看到,Laravel使用闭包的方式进行请求的传递,真正践行的优雅的中间件串联的方式,只需要调用next函数,请求即可被按照预先定义的规则传递到下一个中间件中。

Laravel支持全局的中间件和根据具体路由规定的中间件两种,同时优先级又以定义顺序为准。做到全局与具体情况的兼顾。同时它显示的支持前置、后置和Terminable三种中间件,覆盖了大部分的中间件场景,是一种相对不错的设计。

但美中不足或者说场景覆盖不够友好的地方在于它以路由的方式组织中间件,会与controller有些脱节,每次定义controller中action行为的时候,还需要转换为路由进行配置,略有些不方便。

总结来看

  • Laravel践行了让controller更纯粹的思想,中间件交给路由,controller只做它该做的事;
  • 中间件与路由组灵活结合,能够满足应用场景;
  • 前置、后置与Terminable支持了现有大部分的中间件需求;
  • 自动生成十分方便扩展中间件,开发友好;
  • 但对一个controller内多个action需要统一加入或统一不加入中间件的场景,支持不友好。

老生常谈yii 2.0

github star 4668

yii框架首先是中国人开发的,star数虽然不是很多,但是功能也算丰富。
yii框架从1.1到2.0,经过了一个比较大的升级,支持了很多新的特性,如果不支持,只怕是要落伍了。

在yii框架1.1中,中间件干脆就叫filters了,十分的直白,分为pre-filter和post-fiter两种,即前文中说的,在进入controller之前的过滤逻辑,和完成controller处理之后的过滤逻辑。

但是到了yii2.0之后,filters经过了一层升级,到了behaviors,明确了一点:重心放在了每一个controller的行为上,而不是像Laravel一样controller很傻很单纯。

yii框架的behaviors可以在controller或application中配置。

这里是一个访问控制的filter,具体进行什么样的访问控制由className定义,同时对controller中的action支持“only”关键字,还有“”关键字,能够支持排除法的功能,这个在一些场景下还是很有用的。同时“roles”也能够支持你预先定义好的角色的概念,比如学生无法访问教师后台,而教师无法访问学生论坛等。

use yii\filters\AccessControl;
public function behaviors()
{
    return [
        'access' => [
            'class' => AccessControl::className(),
            'only' => ['create', 'update'],
            'rules' => [
                // allow authenticated users
                [
                    'allow' => true,
                    'roles' => ['@'],
                ],
                // everything else is denied by default
            ],
        ],
    ];
}

当然,在Yii中你也可以自定义filter:

namespace app\components;

use Yii;
use yii\base\ActionFilter;

class ActionTimeFilter extends ActionFilter
{
    private $_startTime;

    public function beforeAction($action)
    {
        $this->_startTime = microtime(true);
        return parent::beforeAction($action);
    }

    public function afterAction($action, $result)
    {
        $time = microtime(true) - $this->_startTime;
        Yii::trace("Action '{$action->uniqueId}' spent $time second.");
        return parent::afterAction($action, $result);
    }
}

这里明显可以看出,这个filter针对action,分别在“beforeAction”和“afterAction”两个阶段进行了逻辑处理,完成了请求的计时工作。

所以总的来看,Yii框架中的中间件:

  • 支持前置和后置两个阶段的自定义;
  • 提供了基本的访问控制中间件;
  • 配置侵入到controller中,完成对controller行为的深度控制;
  • 无法自动生成中间件,自定义成本略高。

大家伙 ZendFramework

ZendFramework是由zend公司推出的php框架,其目标就是建立一套大而全的php框架。以满足企业应用开发的目标。
ZendFramework由很多不同的模块构成,使用者可以通过相互组合的方式来实现自己想要的功能,同时也能够不一次加载大而全的框架,十分的灵活。
比如有负责授权的"zend-authentication",或者是负责验证码的"zend-captcha"等等。

其中"zend-stratigility" 负责提供中间件以及中间件执行流的功能。

use Zend\Stratigility\MiddlewarePipe;
use Zend\Diactoros\Server;

require __DIR__ . '/../vendor/autoload.php';

$app    = new MiddlewarePipe();
$server = Server::createServer($app, $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES);

// Landing page
$app->pipe('/', function ($req, $res, $next) {
    if (! in_array($req->getUri()->getPath(), ['/', ''], true)) {
        return $next($req, $res);
    }
    return $res->end('Hello world!');
});

// Another page
$app->pipe('/foo', function ($req, $res, $next) {
    return $res->end('FOO!');
});

$server->listen();

这里的代码给出了两个中间件的例子。第一个是落地页,监听了root路径,如果命中了这一路由规则,那么请求会被提前结束,返回给用户“Hello world!”。
而第二个中间件去匹配foo这一路径,模糊匹配的方式,如果命中了,会返回FOO并结束请求。

与Laravel类似,这里同样支持使用next(可调用的变量)的方式将请求继续向下传递。而这里中间件配置的方式也跟Laravel比较像,是统一在一个地方根据路由进行配置的,这样完全可以按照如下的方式根据不同的路由定义不同的中间件处理逻辑:

$app->pipe('/api', $apiMiddleware);
$app->pipe('/docs', $apiDocMiddleware);
$app->pipe('/files', $filesMiddleware);

总结来看,ZendFramework的中间件:

  • 主要侧重在请求前置阶段,淡化了请求后置或其他阶段
  • 通过路由的方式统一配置中间件,支持串行
  • 并未预先定义中间件

我心目中的中间件设计

首先按照不同的类别列举一下常见的中间件:

  • 前置中间件:
    • cookie验证:验证用户的cookie
    • 用户角色验证:定义不同的用户角色并验证
    • 用户权限验证:配置不同的用户权限,并验证
    • 安全相关,如CSRF校验:CSRF校验中间件
    • http方法过滤:过滤特定的GET POST请求
    • http或者page cache:对指定路径的页面进行缓存
    • 跨域中间件:不用在nginx配置,而是通过框架的方式,针对某些域名或某些请求,提供跨域的服务。
  • 后置中间件:
    • 共同数据输出:针对统一业务的公共数据,在后置中统一输出
  • 请求返回浏览器之后的中间件:
    • 打印日志
    • 更新session(Laravel)

所以一个php框架最好能够:

  • 定义核心可用中间件;
  • 提供在不同阶段扩展中间件的能力,不能太多,支持前置和后置即可覆盖大部分场景;
  • 统一配置中间件,方便管理所有的中间件,让controller单纯一些;
  • 提供中间件自动生成与方便扩展功能。

以上

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • 原文链接 必备品 文档:Documentation API:API Reference 视频:Laracasts ...
    layjoy阅读 8,607评论 0 121
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,006评论 25 707
  • 小山上的风 作者:【英】米尔恩 朗读:尚焕军 配乐:桃花开 春风来 没有一个人知道, 没有一个人能告诉我: 风从什...
    一粒玉米阅读 164评论 0 0
  • 在“know love”群里有小伙伴提到朱子先生对未来伴侣的期望:她啊,家在哪儿不重要,什么职业也不重要,最...
    羽然说阅读 1,038评论 4 2