Yii框架深度剖析
上QQ阅读APP看书,第一时间看更新

1.4 自定义框架

在学习Yii框架前,我们先自定义一个框架,这样能更好地掌握框架技术的工作机制,为以后学习Yii框架做好准备。同时,设计一个自己的PHP框架并一步步地实现,可以及时融入最新的思想和理念。更重要的是,属于自己的框架可以根据项目的需要为其量身定制。

那么,就先从实现MVC框架模式开始。

1.4.1 MVC框架模式的实现

Yii使用了Web开发中广泛采用的MVC框架模式,因此,使用者在使用Yii建立应用系统时,必须对MVC的原理有一些了解。MVC一直以来是Yii框架初学者很难跨过的一个障碍,本节仿照Yii框架代码实现MVC的软件架构,希望能够通过深入浅出的方式,让读者对MVC有清楚的认识。

1.MVC框架模式的工作原理

传统的基于PHP语言的Web应用程序把PHP代码和HTML、CSS、JavaScript代码混合在一起,这样不利于代码的后期维护,同时也不利于程序功能的扩展。基于MVC的应用程序,把应用程序中的各个功能独立出来,可以很好地实现程序功能的分工合作,对于代码的维护和扩展也十分方便。

MVC是一种目前广泛流行的框架模式。近年来,随着PHP的成熟,它正在成为在LAMP平台上推荐的一种框架设计模式,也是广大PHP开发者非常感兴趣的框架设计模式,并有不断成长的趋势。随着网络应用的快速增加,MVC模式对于Web应用的开发无疑是一种非常先进的设计思想。无论用户选择哪种语言,无论应用多么复杂,都能为用户理解分析应用模型提供最基本的分析方法,为用户构造产品提供清晰的设计框架,为用户的软件工程提供规范的依据。MVC的设计思想是把一个应用的输入、处理和输出流程按照模型(Model)、视图(View)和控制器(Controller)的方式进行分离,这样的一个应用分成3个层——模型层、视图层和控制层,下面分别进行介绍。

(1)视图

视图是用户看到的并与之交互的界面。视图可以向用户显示相关的数据,并能接收用户的输入数据,但它并不进行任何实际的业务处理。视图可以向模型查询业务状态,但不能改变模型。视图还能接收模型发出的数据更新事件,从而对用户界面进行同步更新。作为视图,它只是作为一种输出数据并允许用户操作的方式。

(2)模型

在MVC的3个部件中,模型是主体部分,包含业务数据和业务逻辑,同时负责访问和更新持久化数据。一个模型能为多个视图提供数据,每个视图都从不同角度来表达模型。由于应用于模型的代码只需写一次就可以被多个视图重用,因此降低了代码的重复性。

(3)控制器

控制器负责协调整个应用程序的运转,作用就是接收浏览器端的请求。它接收用户的输入并调用模型和视图去完成用户的需求,当用户单击Web页面中的超链接或发送HTML表单时,控制器本身不输出,只是接收请求并决定调用哪个模型去处理浏览器端发出的请求,然后确定用哪个视图来显示模型处理返回的数据。

MVC处理过程如图1-3所示,首先控制器接收用户的请求,并决定应该调用哪个模型来处理;然后模型根据用户请求进行相应的业务逻辑处理,并返回数据;最后控制器调用相应的视图来格式化模型返回的数据,并通过视图呈现给用户。

图1-3 MVC设计模式

2.MVC模式的优点

使用PHP开发出来的Web应用,初始的开发模板就是混合的数据编程。例如,直接向数据库发送请求并用HTML显示,开发速度往往比较快,但由于数据页面的分离不是很直接,因此很难体现出业务模型的样子或者模型的重用性。产品设计弹性力度很小,很难满足用户的多样化的需求。MVC要求对应用分层,虽然要进行额外的工作,但产品的结构清晰,产品的应用通过模型可以得到更好的体现。

首先,最重要的是应该有多个视图对应一个模型的能力。在目前用户需求快速变化的情况下,可能有多种方式访问应用的要求。例如,订单模型可能有本系统的订单,也有网上订单,或者其他系统的订单,但对于订单的处理都是一样,也就是说,订单的处理是一致的。按照MVC设计模式,一个订单模型以及多个视图即可解决问题。这样减少了代码的复制,即减少了代码的维护量,一旦模型发生改变,也易于维护。其次,由于模型返回的数据不带任何显示格式,因而这些模型也可直接应用于接口的使用。再次,由于一个应用被分离为3层,因此有时改变其中的一层就能满足应用的改变。面对一个应用的业务流程或者业务规则的改变,只需改动MVC的模型层。

控制器还有一个好处,就是可以用它来连接不同的模型和视图去完成用户的需求,这样它可以为构造应用程序提供强有力的手段。给定一些可重用的模型和视图,控制器可以根据用户的需求选择模型进行处理,然后通过视图将处理结果显示给用户。

最后,MVC还有利于软件工程化管理。由于不同的层各司其职,每一层不同的应用具有某些相同的特征,因此有利于通过工程化、工具化特性产生管理程序代码。

综上所述,MVC是构筑软件非常好的框架模式,即将业务处理与显示分离,强制地将应用分为模型、视图及控制层。总之,MVC模式会使得应用更加强壮,更加有弹性,更加个性化。

3.MVC框架模式的实现

在实现MVC框架模式之前,我们先来介绍一下不使用MVC的开发流程。这里有一个网站的3个页面,分别是首页(index.html)、列表页(arc_list.html)和内容页(article.html)。这3个页面都是静态页面。接下来实现由静态页面改写成PHP动态页面,以便能及时从数据库中读取最新内容。这里只实现首页中的“行业百科”模块,效果如图1-4所示。

图1-4 首页中“行业百科”模块效果图

静态页面index.html中“行业百科”模块代码如下。

    <div class="title2 indextt4">
          <span><a title=行业百科href="#">行业百科</a></span>
          <em><a href="#">更多 &gt; &gt; </a></em>
    </div>
    <div class="rightList2 marginbtm15">
          <ul class=ulRightList1s>
              <li><a title=洗碗机分类href="#" target=_blank>洗碗机分类</a></li>
              <li><a title=家用全自动洗碗机分类href="#" target=_blank>家用全自动
洗碗机分类</a></li>
              <li><a title=洗碗机在中国的三起三落href="#" target=_blank>洗碗机在
中国的三起三落</a></li>
              <li><a title=家用全自动洗碗机发展历史href="#" target=_blank>家用全
自动洗碗机发展历史</a></li>
              <li><a title=什么是洗碗机? href="#" target=_blank>什么是洗碗机?
</a></li>
          </ul>
    </div>

使用PHP语言从数据库中读取“行业百科”栏目下的文章标题,重新编写成index.php文件,代码如下所示。

    <div class="title2 indextt4">
          <span><a title=行业百科href="#">行业百科</a></span>
          <em><a href="#">更多 &gt; &gt; </a></em>
    </div>
    <div class="rightList2 marginbtm15">
          <ul class=ulRightList1s>
    <? php
          $dbh = new PDO('mysql:dbname=dscms; host=127.0.0.1', 'root', 'aa09090909');
          $dbh->exec("set names 'utf8'");
          $query = "SELECT title FROM ds_article WHERE cid='14'";
          try {
          //执行SELECT查询,并返回PDOstatement对象
                $pdostatement = $dbh->query($query);
                $result=$pdostatement->fetchAll();
                foreach ($result as $row)
                {
    ?>
                <li><A title=<? php echo $row["title"]; ? > href="#"
                target=_blank><? php echo $row["title"]; ? ></A></li>
    <? php
                }
          } catch (PDOException $e) {
                echo $e->getMessage();
          }
    ?>
          </ul>
    </div>

实现了首页中“行业百科”从数据库查询功能后,首页中其他功能,还有列表页、内容页,实现过程和“行业百科”类似,这里就不列举了。

如此编写代码是很多初学者经历的一个阶段,就是将PHP代码和HTML、CSS、JavaScript代码混合在一起使用。如果有人之前这样去做的话,能否体会出代码混合在一起编写所带来的麻烦?

首先就是不利于代码的重复使用,如上文中的“行业百科”模块。如果在列表页和内容页中也有一样的模块,则需要重复编写,或者把这部分的代码放到一个文件中,频繁使用include语句去调用。或者代码连重复利用都不行,如数据库操作等。其次就是不利于较大项目的团队合作,如后端开发不需要使用HTML、CSS和JavaScript等技术;前端开发不需要使用数据库、PHP开发。最后,不利于代码的后期扩展,如在后期的项目维护过程中,代码混杂,层次不清,将导致重复修改。

如何解决这些不利于软件开发的问题呢?毫无疑问,MVC框架模式就能解决,具体处理流程如下。

步骤1:创建models/Article.php,并在文件中定义文章表模型类Article,其中的find()方法返回查询数据的结果。

    <? php
    class Article
    {
          public function find()
          {
              $dbh = new PDO('mysql:dbname=dscms; host=127.0.0.1', 'root', '');
              $dbh->exec("set names 'utf8'");
              $query = "SELECT title FROM ds_article WHERE cid='14'";
              try {
              //执行SELECT查询,并返回PDOstatement对象
                    $pdostatement = $dbh->query($query);
                    return $result=$pdostatement->fetchAll();
              } catch (PDOException $e) {
                    echo $e->getMessage();
              }
          }
    }
    ?>

步骤2:在framework/framework.php文件中创建控制器的基类CController,并实现控制器渲染视图方法render(),这个方法的功能是加载指定目录下的视图文件,并将控制器中的数据传递到视图文件中。

    <? php
    class CController{
          /**
          加载指定目录下的模板文件,并将控制器中的数据传递到视图文件中
          @param     string     $fileName     提供模板文件的文件名
          @param array                     变量名=>变量值
          */
          public function render($viewName, $data){
              extract($data, EXTR_PREFIX_SAME, 'data'); //将数组$data变成变量的形式
              require($viewName); //包含视图文件
          }
    }
    ?>

步骤3:创建Controllers/DefaultController.php文件,创建控制器DefaultController继承父类CController,创建首页管理方法actionIndex(),在其中创建模型Article对象,并调用find()方法获取数据,渲染视图,并把数据输出到视图页面。

    <? php
    require '../framework/CController.php'; //导入框架文件
    require '../models/Article.php'; //导入文章表模型类文件
    class DefaultController extends CController
    {
          //首页管理
          public function actionIndex()
          {
                //创建模型对象
                $article=new Article();
                //获得数据
                $result=$article->find();
                //渲染视图,并把数据输出到视图页面
                $this->render("../views/index.php", array("result"=>$result));
          }
          //列表页管理
          public function actionList(){}
          //内容页管理
          public function actionArticle(){}
    }
    $default_con = new DefaultController();
    $default_con->actionIndex();
    ?>

步骤4:创建views/index.php,在视图文件中,对查询结果变量$result进行循环处理,生成完整的HTML页面。

    <DIV class="rightList2 marginbtm15">
    <UL class=ulRightList1s>
    <? php
              foreach ($result as $row)
              {
    ?>
                <li><A title=<? php echo $row["title"]; ? > href="#"
                target=_blank><? php echo $row["title"]; ? ></A></li>
    <? php
              }
    ?>
    </UL>
    </DIV>

实现的MVC框架执行流程如图1-5所示。

图1-5 MVC框架执行流程

1.用户直接调用控制器实例对象。控制器调用类中的action方法(动作)。

2.控制器调用模型实例对象从数据库中读取数据。

3.渲染视图。

4.视图读取并显示模型的属性。

5.动作完成视图渲染并将其返回给用户。

本节按照MVC框架模式的工作思想,完成了控制器、模型、视图3个部分的代码分离。我们访问程序,需要去访问controllers目录下的控制器文件,这样做存在明显的设计缺陷。如果控制器文件较多,则会导致系统结构访问混乱,并存在后期维护困难、安全性差等一系列问题,而且不便于系统的统一管理。

下一节将新增入口文件,通过解析用户请求的URL,提取出控制器名和动作方法名,创建相应控制器实例对象,并执行动作方法。

1.4.2 入口文件

本节首先介绍系统多个请求入口设计带来的不便,然后介绍单一请求入口设计模式实现原理。本节的学习目标是明确单一入口文件设计模式的优点,避免在以后的开发项目中出现多入口。

1.入口文件设计

系统中凡是能够被访问的PHP文件称为入口文件。如果用户的不同请求直接对应到Web服务器中的不同PHP文件,即系统是多入口设计。在刚开始学习PHP的时候,通常一个项目都会这样做:

· index.php——网站首页

· list.php? page=5——内容列表页

· info.php? id=12——内容详细页

· login.php——用户登录页

又或者在1.4.1节实现MVC框架模式后,访问不同的控制器类文件,如DefaultController. php或SiteController.php。

对于这些项目来说,都有多个入口文件,随着项目规模的不断扩大,多入口的设计缺陷会越来越明显,如系统目录结构混乱,后期维护困难,容易暴露程序漏洞,不便于系统的统一管理等。为了避免多入口设计带来的诸多问题,可以使用单一入口设计模式。单一入口设计模式就是一个文件处理所有的HTTP请求,也就是说,访问任何控制器文件,无论是DefaultController.php、SiteController. php,还是其他控制器类文件。每一次请求都是指向服务器的同一个文件,如入口文件index.php,该文件负责URL解析,最终转向所要访问的页面,如图1-6所示。

图1-6 单一入口文件模式

PHP单一入口模式可谓是现在一种比较流行的大型Web应用开发模式。当前比较流行的一些PHP开发框架,如Zend、ThinkPHP和Yii等都是采用单一入口模式。

使用单一入口文件模式的优点如下。

· 更加安全。单一入口模式给用户提供了单一的请求入口,在入口文件可以对请求进行过滤,加入安全处理代码,而传统的多请求入口模式需要为每个文件都加入安全处理程序块。

· 模块化程度高。开发人员只需关注自己所开发的模块,开发人员之间不需考虑程序是否正常运行,因为这一切全部交给入口文件来协调。

· 便于统一管理,定制性强。系统的所有模块都由入口文件进行统一管理,任何一个模块可以不经模块本身启用或禁用。

2.入口文件中实现URL的解析

在上文中提到入口文件的URL解析,即入口文件会将原始请求转发给相应的处理控制器,完成具体的业务处理。例如,有以下URL地址:

    http://<hostname>/
    http://<hostname>/index.php
    http://<hostname>/index.php? r=site
    http://<hostname>/index.php? r=site/index

提示:自定义框架模仿Yii框架采用路径(PATH)URL模式访问规则。路径URL模式采用目录分层的思想,路径格式简洁,URL解析效率高,此URL格式为:http://<hostname>/appname/index.php?r=controllerID/actionID

我们希望上面所有URL被解析后都会访问SiteController控制器的actionIndex()方法。URL解析执行流程如图1-7所示,首先访问入口文件,在其中分析请求URL的参数,在没有“r”参数的情况下默认访问SiteController的actionIndex()方法,否则依据“r”参数访问SiteController的actionIndex()方法,即所有的访问由URL的参数来统一解析和调度。

图1-7 URL解析执行流程图

入口文件index.php中代码实现如下。

    <? php
        //默认控制器是SiteController
          $defaultController="site";
        //默认动作actionIndex
        $defaultAction="index";
          //如URL为http://hostname/index.php? r=controllerid/actionid
          //得到controllerid/actionid
          if(! empty($_GET['r']))
          {
              $route=$_GET['r'];
              //得到controllerid赋值给成员变量
              $pos=strpos($route, '/');
              $defaultController=substr($route,0, $pos);
              $defaultController=strtolower($defaultController);
              //得到actionid赋值给成员变量
              $defaultAction=(string)substr($route, $pos+1);
          }
          //得到控制器类名
          $className=ucfirst($defaultController).'Controller';
          //获得控制器文件路径
          $classFile="./controllers/".$className.'.php';
          //最后一步操作:该类文件存在则导入,该类存在则创建对象并调用acion方法
          if(is_file($classFile))
          {
              if(! class_exists($className, false))
              {
                    require($classFile);
                    $class= new $className();
                    $functionName="action".ucfirst($defaultAction);
                    $class->$functionName();
              }
          }
    ?>

由上面的程序可知,默认的控制器是SiteController,默认的执行方法是actionIndex()方法。控制器的类名首字母大写,以“Controller”结尾,而且控制器类文件必须保存在controllers文件夹中;动作方法名必须以“action”为前缀,acitonID首字母大写。从这段程序中也可以了解到代码规范的重要性,因为文件名或类名等都会在程序中使用。同样的道理,在将要学习的Yii框架开发过程中,也要遵守一定的编码规范。例如,命名类时,使用驼峰风格,即每个单词的首字母大写并连在一起,中间无空格;变量名和方法名应该使它们的第一个单词全部小写,其余单词首字母大写,以使其区别于类名,如$basePath、runController();对私有类成员变量来说,推荐以下画线作为其名字前缀,如$_actionList。

提示:为了使PHP语言开发的框架能够遵循共同的编码风格,在2009年由几个框架的开发者组成了PHP-FIG(PHP Framework Interoperability Group)小组,一直扩展到现在已经拥有20多位成员。

实现入口文件后,框架执行流程如图1-8所示。

图1-8 框架执行流程

1.用户发出了访问URL的请求,Web服务器通过执行入口文件index.php处理此请求。

2.入口文件负责完成URL的解析,根据URL请求创建控制器并调用动作处理用户请求。

3.控制器调用模型实例对象从数据库中读取数据。

4.渲染视图。

5.视图读取并显示模型的数据。

6.动作完成视图渲染并将其返回给用户。

3.单一入口模式服务器环境配置

实现单一入口模式之后,需要确保应用根目录下,除入口文件外的PHP文件(所有安全敏感的PHP文件)都不允许访问。通过实践证明,使用Apache服务器的目录级配置文件.htaccess文件保护目录比使用其他方式更为有效和安全。更重要的是,使用.htaccess的方式进行设置,不需要编写程序就可以实现,具体操作比较容易。

(1)目录级配置文件.htaccess

.htaccess是一个纯文本文件,其中存放着Apache服务器配置相关的一些指令,它类似于Apache的站点配置文件,如httpd.conf文件。.htaccess与httpd.conf配置文件不同的是,它只作用于此目录及其所有子目录。另外,httpd.conf是在Apache服务启动的时候就加载的,而.htaccess只有在用户访问目录时加载,其中,修改.htaccess文件不需要重启Apache服务器。.htaccess的功能包括设置网页密码、设置发生错误时出现的文件、禁止读取文件、重新定向文件等。

在需要针对目录改变服务器的配置,而对服务器系统没有root权限时,应该使用.htaccess文件。如果服务器管理员不愿意频繁修改配置,则可以允许用户通过.htaccess文件自己修改配置,尤其是在一台机器上提供多个用户站点,而又期望用户可以自己改变配置的情况下,一般会开放部分.htaccess的功能给使用者自行设置。

注意:.htaccess是一个完整的文件名,不是***.htaccess或其他格式。

如何允许用户使用.htaccess文件呢?在Apache服务器的配置文件httpd.conf中,查找服务器的根目录的配置信息:

    <Directory "e:/wamp/www/">
        ……
        AllowOverride None
          ……
    </Directory>

在此块配置项中,把“AllowOverride None”修改成“AllowOverride All”,即允许Apache服务器调用.htaccess文件,在需要时针对目录改变服务器的配置。

提示:httpd. conf配置文件中的AllowOverride会根据设定的值决定是否读取目录中的.htaccess文件,来改变原来所设置的权限。为避免用户自行建立.htaccess文件修改访问权限,httpd.conf文件中默认设置每个目录为:AllowOverride None。

All:读取.htaccess文件的内容,修改原来的访问权限。

None:不读取.htaccess文件。

(2)实现禁止访问除入口文件之外的PHP文件

在Apache服务器的目录级配置文件.htaccess文件中添加“deny from all”(表示全部IP地址都不许可,相对地,“allow from all”表示全部都允许),即可实现包含该.htaccess的文件夹不允许被外部访问。接下来创建protected目录,并把需要保护的文件移到该目录下。

改进后的目录结构如下:

    |   index.php
    ├—css
    ├—framework
    |      .htaccess
    |      CController.php
    ├—images
    ├—js
    └—protected
        |  .htaccess
        ├—controllers
        |     DefaultController.php
        |     SiteController.php
        ├—models
        |     Article.php
        └—views
              index.php

1.4.3 应用(前端控制器)

1.4.2节中对原有的MVC模式进行了改进,在入口文件中实现了URL的解析。用户的每一次请求都指向服务器的唯一可访问文件。经过解析URL,最终转向所要访问的控制器。但是当系统日趋复杂和多样时,如URL参数和POST数据需要进行必要的检查和特殊字符过滤、记录日志、访问统计等,如果各种可以集中处理的任务都放在入口文件执行,那么将会出现代码重复、业务逻辑混乱且分散的情况。因此,为了降低系统代码逻辑的复杂度,进一步集中控制系统,并提高系统的安全控制能力,以及可维护性、可重用性和可伸缩性,本节中对原有的MVC模式进行了改进,提出了应用(前端控制器)的概念,实现MVC在复杂系统中的前端控制器开发模式优化策略。

1.在应用中实现URL解析

采用前端控制器模式,提供一个处理不同请求的中心,处理工作包括安全事务、视图选择、异常处理和响应内容的生成,通过将这些处理工作集中在一点进行,大大降低了PHP代码量,同时也减少了视图层的程序逻辑,保证了在不同请求之间可以大量地重用逻辑代码。

应用(前端控制器)的URL解析功能在文件framework/Cweb-Application.php文件中实现,流程图如图1-9所示。解析URL代码如下。

图1-9 应用中解析URL流程图

    <? php
    class CWebApplication {
          public $name;
          //默认控制器是SiteController
          public $defaultController="site";
          //默认动作是actionIndex
          public $defaultAction="index";
          //执行应用
          public function run()
          {
              //如URL为http://hostname/index.php? r=controllerid/actionid
              //得到controllerid/actionid
              if(! empty($_GET['r']))
              {
                    $route=$_GET['r'];
                    //得到controllerid赋值给成员变量
                    $pos=strpos($route, '/');
                    $this->defaultController=substr($route,0, $pos);
                    $this->defaultController=strtolower($this->defaultController);
                    //得到actionid赋值给成员变量
                    $this->defaultAction=(string)substr($route, $pos+1);
              }
              //得到控制器类名
              $className=ucfirst($this->defaultController).'Controller';
              //获得控制器文件路径
              $classFile="./protected/controllers/".$className.'.php';
              //最后一步操作:该类文件存在及该类存在,则导入并调用acion方法
              if(is_file($classFile))
              {
                    if(! class_exists($className, false))
                    {
                        require($classFile);
                        $class= new $className();
                        $functionName="action".ucfirst($this->defaultAction);
                        $class->$functionName();
                    }
              }
          }
    }

2.单例模式创建应用(前端控制器设计模式)

对于系统中的某些类来说,只有一个实例很重要。例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。例如,在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此,确保系统中某个对象的唯一性(即一个类只能有一个实例)是非常重要的。

我们希望系统中的应用(前端控制器)只有一个实例对象而且该实例对象易于外界访问,从而方便应用实例对象个数的控制并节约系统资源,单例模式是最好的解决方案之一。

单例模式是一种常用的软件设计模式。其要点有3个:一是类只能有一个实例,二是它必须自行创建这个实例,三是它必须自行向整个系统提供这个实例。

从具体实现角度来说,就是以下3点:一是单例模式的类只提供私有的构造方法,二是类定义中含有一个该类的静态私有对象,三是该类提供静态的公有方法用于创建或获取它本身的静态私有对象。在framework/CWebApplication.php文件中添加下面所示的部分代码。

    <? php
    class CWebApplication {
          ……
          //定义类的静态私有对象
          private static $_app;
          //构造方法在实例对象被创建时自动执行
          private function __construct($config=null)
          {
              //获取配置文件中的数组
          }
          //静态的公有方法用于创建它本身的静态私有对象
          public static function createApplication($config=null)
          {
              if(self::$_app===null)
                    self::$_app = new CApplication($config);
              return self::$_app;
          }
          //静态的公有方法用于获取它本身的静态私有对象
          public static function app()
          {
              return self::$_app;
          }
          //执行应用
          public function run(){……}
    }

3.应用的配置文件

默认情况下,应用是一个CWebApplication的实例。要自定义它,通常需要提供一个配置文件以在创建应用实例时初始化其属性值。这就好比去组装计算机,客户拿来具体的配置单,按照要求就可以组装符合要求的计算机。而CWebApplication就是组装工人,配置单就是下面要说明的配置文件。

配置信息在配置文件中以数组元素的方式存放,一个元素就是两个字符串组成的键值对,一个字符串是键(key),另一个字符串是这个键的对应的值(value)。大多数的系统都有一些配置常量,将这些常量放在配置文件中,系统通过访问这个配置文件取得配置常量,就可以通过修改配置文件而无须修改程序达到更改系统配置的目的。系统也可以在配置文件中存储一些工作环境信息,这样在系统每次访问时,这些信息可以运行在每一个应用的生命周期中。

通常在一个单独的PHP脚本(protected/config/main.php)中保存这些配置。在脚本中,通过以下方式返回此配置数组。

    <? php
    return array(
          //默认控制器
          "defaultController"=>"default",
          //通过应用全局访问方法Yii::app()->name;直接访问。
          "name"=>"my application",
    );
    ?>

在应用的构造方法中添加对配置文件操作的代码:

    <? php
    class CWebApplication {
          ……
        //构造方法在实例对象被创建时自动执行
          private function __construct($config=null)
          {
              //获取配置文件中的数组
              if(is_string($config))
                    $config=require($config);
              /*
                    把配置文件中数组定义的元素赋值给CWebApplication类中相同成员属性
                    array(
                        "name"=>"my application",
                        "defaultController"=>"default",
                    );
              */
              if(is_array($config))
              {
                    /*
                        第一次循环:$this->name=“my application”;
                        第二次循环:$this->defaultController=“default”;
                    */
                    foreach($config as $key=>$value)
                        $this->$key=$value;
              }
          }
    ……
    }

要应用此配置,将配置文件的名字作为参数传递给应用的构造器,或像下面这样传递到CApplication::createApplication(),这通常在入口脚本中完成。

    <? php
    //加载framework文件夹下的所有文件,为了清楚演示,这里没有用__autoload()
    require "./protected/framework/CController.php";
    require "./protected/framework/CApplication.php";
    //定义配置文件路径
    $config="./protected/config/main.php";
    //创建应用(前端控制器)对象
    $app=CApplication::createApplication($config);
    $app->run();
    ?>

实现前端控制器模式后,框架执行流程如图1-10所示。

图1-10 框架执行流程

1.用户发送访问URL的请求,Web服务器通过执行入口脚本index.php处理此请求。

2.入口脚本创建一个应用实例并执行。

3.创建一个所请求控制器的实例以进一步处理用户请求。控制器决定动作指向控制器类中的action方法。

4.控制器调用模型实例对象从数据库中读取数据。

5.渲染视图。

6.视图读取并显示模型的数据。

7.动作完成视图渲染并将其呈现给用户。

1.4.4 从自定义框架到Yii框架

在前几节中,介绍了框架的概念及使用框架技术的优势,并结合PHP的发展历史总结了现阶段PHP及其框架技术的应用领域。

为了让读者更好地理解Yii框架,并认识到框架技术并不是多么复杂,本节自定义了一个MVC框架,实现控制器、模型、视图的分离,创建单一入口模式的目录结构,实现应用的预处理、初始化和执行。显然,自定义的框架功能还很少,不能满足框架作为“半成品”的需要。因此,接下来要进入到Yii框架的学习,因为Yii提供了目前Web 2.0应用开发所需要的几乎一切功能。下面是这些特性的简短说明。

· 模型-视图-控制器(MVC)设计模式:Yii在Web编程中采用这一成熟的技术从而可以更好地将逻辑层和表现层分开。

· 数据库访问对象(DAO)和Active Record:Yii允许开发者模型数据库中的数据对象,从而节省他们在编写很长和重复的SQL语句上所使用的精力。

· 与jQuery整合:作为目前最流行的JavaScript框架之一,jQuery可以编写高效而灵活的JavaScript接口。

· 表单输入和验证:Yii使得收集表单输入变得非常容易和安全。Yii拥有一套确保数据的有效性的验证器,它也有辅助方法和部件,显示验证失败时的错误。

· Web 2.0部件:由于jQuery的支持,Yii配备了一套Web 2.0的部件,如自动完成输入字段、TreeView等。

· 身份验证和授权:Yii具有内置的身份验证支持。它也支持通过分层的基于角色的访问控制(RBAC)的授权。

· 主题:它能够瞬间改变一个Yii应用的视图。

· Web服务:Yii支持自动生成复杂的WSDL服务规范和管理Web服务请求处理。

· 国际化(I18N)和本地化(L10N):Yii支持消息转换、日期和时间格式、数字格式和界面本地化。

· 分层缓存方案:Yii支持数据缓存、页面缓存、片段缓存和动态内容。缓存的存储介质,可以轻松地更改而不触及应用程序代码。

· 错误处理和日志记录:错误的处理可以很好地呈现出来,日志信息可以分类、过滤并分配到不同的位置。

· 安全:Yii拥有许多安全的措施,包括跨站点脚本(XSS)预防、跨站点请求伪造(CSRF)预防和Cookie篡改预防等。

· 符合XHTML:Yii的组件和命令行工具生成的代码符合XHTML标准。

· 自动代码生成:Yii提供了可以自动生成代码的工具,根据用户的需要可生成一个程序“骨架”、CRUD应用等。

· 完全面向对象:Yii框架坚持严格的面向对象编程范式。它没有定义任何全局方法或变量。而且,它定义的类层次结构允许最大程度的可重用性和定制。

· 友好地使用第三方代码:通过Yii精心设计,让第三方代码可以非常好地工作。例如,用户可以在自己的Yii应用程序中使用PEAR或Zend Framework的代码。

· 详细的文档:每一个单一的方法或属性都拥有非常清楚的记录。同时,还提供了一个全面的教程和一些新手教程。

· 扩展库:Yii提供了一个为用户提供组件的扩展库,这使得上述功能列表是不断扩展的。