PHP框架的三层架构设计真的合适么?
对于基本的Web开发,我们已经习惯了MVC架构。模型层(M)提供持久化数据对象与数据访问,控制层(C)完成业务逻辑处理,视图层(V)提供模板表现。其中控制层与模型层和视图层交互形成整个系统。
这种分层方式在逻辑上实现了解耦与分离,很多语言如Java和Python的框架都有各自的实现方式,如Struts采用Bean+JSP+Hibernate的方式实现。Django采用中间件的方式实现。无论是JAVA框架的实现方式还是Python框架的实现方式,都比较好的解决了MVC之间的交互问题,使得每个层次成为单独的构件,每一层的构件可复用在其它地方。如Java Bean除了可供JSP使用,还可包装成Soap服务。这样既保证了层之间的逻辑分离,也保证了层之间的物理分离。如Java Bean可以单独被部署到分布式应用服务器上。EJB就是一个很好的例子。而对于目前广泛应用的一些PHP框架,在实现上感觉缺少对物理独立性的考虑。
以Yii框架为例子来说,Yii框架的组织结构大致如下所示:
Site
|____resources
|____protected
|____config
|____controllers
|____models
|____views
|____components
|____framework
|____index.php
resources代表站点的一些资源性资料,如图片、样式表等。freamework为框架核心档。index.php为入口文件,所有访问路径均以index.php后跟参数为标准。而protected内就是业务逻辑需要的MVC三层及系统需要的一些配置信息了。protected内的componets下是系统的组件类,如果有与系统业务流程无关,但是在业务逻辑中需要的函数库,可以将它们作为组件放到这个目录下,如果配置文件内注册了该组件,在系统运行时会产生它们的实例以供调用。执行的流程大致如下图所示:
根据上图可以看出,Action需要同时与Model层与View层进行交互,在Action内,不仅有业务逻辑,还需调用Model层的方法和View层的方法。这样,业务逻辑与Model层、View层就存在了紧耦合的关系。这种层次结构应用在基本的网站上还是可以满足要求,但是不具备扩展性和可修改性。如果现在想把业务逻辑包装称REST风格或SOAP服务,那么只能重新写一次包含业务逻辑的Action,因为现在的Action里已经包含了页面的输出方法,而不是单纯的数据结果。对于需要使用同一动态数据展示不同表现形式的需求,也只能通过判断(修改)的方式而不能通过增加类或简单Action(扩展)的方式实现。这违反了面向对象的开放封闭原则。由于系统需求往往是频繁的变更的,如果我们常常的修改现有的代码,不仅会造成现有系统代码结构的混乱,而且极易造成隐藏的,难以发现的错误。所以,只有通过扩展来实现需求的变更,对系统来说才是最安全的。我们的工作才会更加的轻松和高效。
既然Yii提供的源码不能完美的解决扩展性和可修改性的问题,那么我们何不对它进行一次小小的改造,使它能胜任更加高的要求呢。俗话说的好,在软件的世界里,加一层能解决所有的问题。所以,我们也决定使用“加一层”的办法,以提高我们系统的可扩展性与可修改性。由于Yii是通过URI路由来查找Action并进行相应操作,最后也是通过Action方法产生响应结果的。所以,为了不破坏Yii的框架结构,我们只能在Action的下方增加一层,作为专门的业务逻辑层。为了偷懒,我们借鉴Java的叫法,称它为bean层。此时,Action就最为一个专门的包装层,包装bean的逻辑,然后与View层交互产生页面,或直接输出数据。这样如果我想把业务流程包装成SOAP服务,只需增加一个Action即可,无需重写业务逻辑。如果有多种视图显示要求,也无须重写业务逻辑,只需扩展Action即可。下面用代码来说明“这一层”是如何加上去的。
首先我们在protected目录下新建一个beans目录,然后在components内新建LoadBean.php文件,用于实例化bean类及获取bean对象。beans下的文件以XXXBean.php命名,如SiteBean.php。在beans下新建一个configure.php文件。此文件是bean加载的配置文件,以实现IOC之用。新结构如下所示:
Site
|____resources
|____protected
|____config
|____controllers
|____models
|____views
|____beans
|____configure.php
|____SiteBean.php
|____components
|____LoadBean.php
|____framework
|____index.php
beans下是专门负责业务逻辑的地方,Action内只需调用beans下的业务逻辑并提供输出即可,无需再写业务逻辑代码。为了达到以上要求,我们需要对Yii的代码做少许修改。首先找到框架核心内的CWebApplication.php文件,它的位置是framework/web/CWebApplication.php。 在该文件内添加一个getBeanPath方法。代码如下:
public function getBeanPath(){ return $this->_controllerPath=$this->getBasePath().DIRECTORY_SEPARATOR.‘beans‘; }
然后找到该文件内的createController方法,找到$classFile=$basePath.DIRECTORY_SEPARATOR.$className.‘.php‘;这行代码,在它下面添加以下代码:
$beanPath = $owner->getBeanPath(); $beanName = ucfirst($id).‘Bean‘; $classBean = $beanPath.DIRECTORY_SEPARATOR.$beanName.‘.php‘; if(is_file($classBean)){ if(!class_exists($beanName,false)) require($classBean); }
第三步,打开刚才新建的LoadBean.php文件,添加以下代码:
class LoadBean { private $objs; public function init(){ $beanconfig = Yii::app()->basePath.‘\beans\configure.php‘; $beans = require $beanconfig; foreach($beans as $bean){ if (!$this->objs[$bean] instanceof $bean.‘Bean‘){ $class = $bean.‘Bean‘; if(class_exists($class)){ $this->objs[$bean] = new $class($bean); } } } } public function obj($name){ try{ if (array_key_exists($name,$this->objs)){ return $this->objs[$name]; }else{ throw new Exception(‘bean name error‘); } }catch(Exception $e){ echo $e->getMessage(); } } }
第四步,找到protected/config/main.php文件,添加以下代码:
‘beans‘=>array( ‘class‘=>‘LoadBean‘, ),
让系统在初始化时加载第三步添加的LoadBean类。
第五步,找到protected/beans/SiteBean.php,添加以下代码:
class SiteBean extends Controller{ public function abc(){ return123; } }
此abc方法即是我们的业务逻辑代码。
第六步,找到protected/beans/configure.php,添加以下代码:
return array( ‘Site‘, );
此‘Site‘即为SiteBean类的‘Site‘名。
第七步,实现Action方法,找到protected/controllers/SiteController.php文件(如没有,可直接创建),代码如下:
class SiteController extends Controller{ public function actionIndex(){ print_r(Yii::app()->beans->obj(‘Site‘)->abc()); } }
Action类不直接与Bean类交互,而是通过组件类做代理进行通讯,使Action与Bean也实现了分离。
此时,利用浏览器访问http://yourdomain/index.php?r=Site/index,即可显示123。在此,我们实现了业务逻辑与Action的分离,增加了系统的扩展性与可修改性,bean实现了物理部署独立。形成了我们的四层架构。
本文出自 “架构师之路” 博客,转载请与作者联系!