首页 > 代码库 > PHP框架的三层架构设计真的合适么?

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下是系统的组件类,如果有与系统业务流程无关,但是在业务逻辑中需要的函数库,可以将它们作为组件放到这个目录下,如果配置文件内注册了该组件,在系统运行时会产生它们的实例以供调用。执行的流程大致如下图所示:


wKiom1QMYV7S5rcpAAEH_88CYno485.jpg

spacer.gif

    根据上图可以看出,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实现了物理部署独立。形成了我们的四层架构。

        

abc.zargo

yii.zip


本文出自 “架构师之路” 博客,转载请与作者联系!

PHP框架的三层架构设计真的合适么?