欢迎访问我的新主页:http://huoding.com/
作者:老王
PHP社区有很多框架可供选择,比如
CodeIgniter,
Yii,
Solar,
Zend,
Symfony,
CakePHP,
Lithium等。一直以来,为了争做头牌,各个框架的粉丝团喋喋不休,好事者还试图通过运行
Hello World来以性能决胜负,但难以服众,其实问题的本质在于大家对框架的理解不同所致,属于审美观的差异。
从整体结构上来看,对框架而言,微内核很重要。所谓微内核说白了就是代码要迷你。代码越多Bug越多,而且伴随着复杂度的升高,一方面会造成运行效率的下降,另一方面在使用时的易用度也好不到哪去。反之,避开高,大,全,的模式套路,只保留最核心的功能,便能有足够的精力保证研发工作的可持续性发展,至于其他非核心功能,则留给第三方去实现,如此一来,才能形成一个欣欣向荣的生态社区,这点是非常重要的,对一个项目而言,即便它在技术上是非常优秀的,但如果没有形成自己的生态社区,也必将没落,生态社区是任何项目存在的基石,只要你营造了一个和谐的生态社区,它就会自然而然的保证新陈代谢向前发展,这方面的例子很多,比如Firefox,JQuery等等,都有大把大把的第三方优秀插件,它们都是生态社区里优胜劣汰的产物。
细化到局部的设计,对一个Web框架而言,遵循Web的本质去设计框架很重要,一个半开玩笑的判断框架好坏的方法是看它是否实现了request和response封装,对一次HTTP请求而言,一个request进来,一个response出去,是很正常的流程,如果没有封装这些基本元素,自然不是一个完善的Web框架,使用起来就有可能出现问题,比如说下面一段取自Zend框架的演示代码:
01 class FooController extends Zend_Controller_Action
02 {
03 public function createAction()
04 {
05 if ($this->some->save()) {
06 $this->_redirect(...);
07 }
08
09 // ...
10 }
11 }
需要注意的是,这里标红的redirect方法实际上含有一个exit操作,也就是说,脚本执行到这里,就被硬编码中断了,如果控制器需要存在一个操作前后的before,after触发器(在Zend框架里称作preDispatch,postDispatch),那这这种情形下就只能仰天长叹了,因为执行过程由于前面提到的硬编码中断已经退出了,after(postDispatch)触发器永远没机会执行。
那么看看更好的设计应该是什么样的,意淫一段伪代码:
01 class FooAction extends Action
02 {
03 public function execute($request)
04 {
05 if ($this->some->save())
06 {
07 return $this->redirector(...);
08 }
09
10 // ...
11 }
12 }
非常自然的一个操作流程,request进来(作为参数),response出去(作为返回值),这里没有使用硬编码的中断,而是返回的redirector,这里的redirector实际就是一个response类型,由于是return而不是exit,所以通过前端控制器的协调,操作前后的before,after触发器都可以被执行到。
一直在强调简单,自然。下面就可以看到简单,自然带来的好处了。假如一个操作涉及缓存管理,会话管理等逻辑,由于他们,可能存在复杂的关系,在传统的处理方式中,代码免不了涉及很多if,else之类的嵌套逻辑,这无疑是坏味道,下面看看如果在简单自然的架构上面扩展这样的功能:
01 class FooAction extends Action
02 {
03 protected $wrappers = array('Cache', 'Session');
04
05 public function execute($request)
06 {
07 // ...
08 }
09 }
可以看到这里全然没有if,else的困扰,不过要想明确操作过程,还得看看里面的wrapper是如何设计的:
01 class CacheWrapper extends Wrapper
02 {
03 public function execute($request)
04 {
05 // ...
06 }
07 }
01 class SessionWrapper extends Wrapper
02 {
03 public function execute($request)
04 {
05 // ...
06 }
07 }
执行方法看似复杂,实际就是一个装饰模式的简单应用而已,如下:
01 $object = new CacheWrapper(
02 new SessionWrapper(
03 new FooAction()
04 )
05 );
06
07 $response = $object->execute($request);
其实这样的方式在Python框架里很常见,但PHP框架里鲜有耳闻。如果还不清楚,欢迎仔细围观下图:
程序其实就是一个洋葱头,一层一层的,简单却又不失扩展的灵活性,这就是本文的Web框架审美观。
补充:有人会诧异为什么上面把控制器设计成单Action风格,而不是现在流行的多Action风格?这是因为只有使用单Action风格,接口才是稳定的(只有一个execute方法),这样才能使用装饰模式,当然如果是多Action的话,也可以使用__call来装饰,但那样显得不太优雅,所以我不喜欢。
后记:如果对文中提及的装饰模式实现方式感兴趣,可以参考我很久以前写的文章:
半透明的装饰模式
思考后的补充:
通过和很多网友的交流,我又反思了一下,感觉对PHP而言,封装request和response对象虽然很优雅,但是显得有点“重”了,对request而言,或许直接操作GPC更直接,对response而言,可以参照Python WSGI的风格,预定义一个数组结构,比如array('headers' => ..., 'body' => ..., ...),或许更好用,Action也不用再以类的形式存在,返璞归真为最简单的脚本(脚本也可以有返回值,return我们上面说的预定义好的数组结构,通过include操作接收返回值),或者function形式,不过这样的话,简单的request,response需求无所谓,一旦涉及复杂的request,response操作,由于都是数组形式,没有对象可以封装相关逻辑,代码会显得杂乱,而且装饰器的使用也要重新考虑了,我还不能确认是否值得,存疑。
我新写了一篇《
返璞归真:面向过程的装饰模式实现》,欢迎参阅。