Zend Framework学习笔记(一):Zend_Controller

August 20th, 2008 | by 超群.com | 知识共享署名-非商业性使用-相同方式共享,转载请保留链接。

本博客所有原创文章采用知识共享署名-非商业性使用-相同方式共享,转载请保留链接http://chaoqun.17348.com/2008/08/learn_zend_framework_zend_controller/

前一段时间一直在看Zend Framework方面的东西,参照着设计模式阅读Zend Framework结构收获良多,我将以一系列的文章和大家一起探讨Zend Framework(文中将以ZF代替Zend Framework)。

ZF是Zend公司开发的一套PHP快速开发框架,类似的框架还有cakephpcodeigniter等,当然国内的也有一些这方面好的尝试,如FleaPHP&ThinkPHP,每个框架都有自己的特色,我这里只说一下ZF,ZF过于强大了以至很多人都觉得ZF是不是臃肿了?其实ZF在松散耦合方面做的非常的好,ZF的各个模块基本上都可以调出来单独使用,这就是很多人觉得ZF是PEAR2的原因。ZF的学习成本相对其他框架来说要高很多,当然也可以什么了解一点,然后写出只值“一点”的程序来,国内用ZF的大压力项目有6.cn,可惜Michael一直没有时间给我们分享更多,了解ZF可以关注一下phpeye,站长HaoHappy给我们贡献了ZF的中文手册,实在是一件功德无量的事情。

Zend Framework整个框架的核心的Zend_Controller,所以要学习ZF的同学最好把手册中Zend_Controller部分多看几遍,我们进入正题吧。

一、MVC的设计模式

一个良好的应用结构一般分为三层:Model层(一般是数据层)、View层(表现层)和Controller层(控制层或者说是逻辑层),底层数据和逻辑分离、逻辑与前台展现分离,这样的话程序的伸展性和可维护性都大大提高,下面的例子会将一个很糟糕的设计转化成一个PHP的MVC设计.

先看糟糕的程序吧:

<?php
$userID = 9527;

/**
 * 开始连接数据库,查询数据库等等的操作,类似:
 * $db = mysql_connect(‘host’, ‘user’, ‘pwd’);
 * mysql_select_db(‘dbname’);
 */

// 假设最后你获得了用户名称
$userName = ‘超群.com’
?>
<!– 开始显示用户名称 –>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd“>
<html xmlns=”http://www.w3.org/1999/xhtml“>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
<title><?php echo $userName;?>,你好</title>
</head>

<body>
Hell,<?php echo $userName;?>
</body>
</html>

我们会看到一个文件里面混合着PHP和HTML代码,如果美工修改了模板或者业务逻辑稍有变化都需要改大量的程序,而且还有一个致命的问题:代码复用,如果另外的地方也需要获得用户名称,你是不是要重新写一遍同样的代码呢?记住:重复代码是邪恶的。

然后再看一下MVC的设计,我们把业务划分为三块:获得用户名称(数据层)、控制用户名称显示(控制层)、用户名称显示模板(表现层):
数据层:

<?php
class UserModel
{
    // 用户ID
    var $_userId;
 
    // 构造函数
    function __construct($userId)
    {
        $thi-->_userId = $userId;
    }
 
    // 获取用户名称
    function getUserName()
    {
        // 一般情况是通过用户ID查数据库然后返回,这里简单处理
        return '超群.com';
    }
}
?>

控制层:

<?php
// 这里用smarty来做页面和逻辑分离
require_once ’smarty/Smarty.class.php’;
 
class UserController
{
    function sayHelloToUser($userObject)
    {
        $userName = $userObject->getUserName();
        // 调用Smarty
        $smarty = new Smarty();
        $smarty->assign(’userName’, $userName);
        $smarty->display(’tpl/user/sayhellotouser.tpl.php’);
    }
}
?>

表现层:

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd“>
<html xmlns=”http://www.w3.org/1999/xhtml“>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
<title>{$userName},你好</title>
</head>

<body>
Hell,{$userName}>
</body>
</html>

这个例子非常简单,但确能给各位一个关于MVC在PHP的呈现结构。

二、Zend_Controller

一般的应用手册上面已经写的很详细了,建议仔细阅读手册,我这里想讨论的是不用mod_rewrite然后还想用传统的目录结构怎么处理?我想很多人的困在这里,手册上推荐的目录结构是:

application/
    controllers/
        IndexController.php
    models/
    views/
        scripts/
            index/
                index.phtml
        helpers/
        filters/
html/
    .htaccess
    index.php

难道我所有的Controller都放一个目录吗?所有的Model都放一个目录吗?一个Controller对应一个View的目录吗?太不方便了,尤其不适合模块化开发,我们希望的是一个模块的东西放一个目录下,彼此之间不要形成“污染”。下面的目录结构我们可能更会喜欢点:

wwwroot/
    index.php
    default/
        controllers/
            IndexController.php
            FooController.php
        models/
        views/
    blog/
        controllers/
            IndexController.php
        models/
        views/      
    news/
        controllers/
            IndexController.php
            ListController.php
        models/
        views/
        views/

对于第一个问题(ZF不使用mod_rewrite照样工作),Akra(Zend Framework In Action的作者之一)给出了一种方案Zend Framework URLs without mod_rewrite,作者通过扩展Zend_Controller_Router_Route_Interface来实现,相当于撇开官方提供的Zend_Controller_Router_Rewrite,个人觉得这种方案有点不太合适,首先这是mod,是hack,并不是官方标准支持,这就意味着可能的bug以及将来的不兼容,当然,你也可以一直修改修改以保证健壮性以及兼容性,但其门槛也相对高很多。

我提供的方法有两种,一种是“友好”Url方式,比如通过http://127.0.0.1/index.php/mymodule/hello/hello,入口文件(index.php)如下:

<?php
// 引入Zend_Controller_Front
require_once ‘libs/Zend/Controller/Front.php’;
 
// 获得Zend_Controller_Front实例
$frontController = Zend_Controller_Front::getInstance();
 
// 打开异常开关
$frontController->throwExceptions(true);
 
// 设定模块目录
$frontController->addModuleDirectory(./modules’);
 
// 禁用模板解析,如果习惯用Smarty的话可以这么做,因为我们只是很简单的展示(直接打印),所以禁用
$frontController->setParam(’noViewRenderer’, true);
 
// 开始分发
$frontController->dispatch();
?>

然后在modules目录下创建文件mymodule/controllers/HelloController.php(注意大小写):

<?php
class Mymodule_HelloController extends Zend_Controller_Action
{
    public function helloAction()
    {
        echo ‘Hello,World!;
    }
}
?>

另外一种是常见的url形式:http://127.0.0.1/index.php?module=mymodule&contorller=hello&action=hello,只需要改一下入口文件就可以了。

<?php
// 引入Zend_Controller_Front
require_once ‘libs/Zend/Controller/Front.php’;
// 引入Zend_Controller_Request_Http
require_once ‘Zend/Controller/Request/Http.php’;
 
// 获得MVC参数
$module = $_GET['module'];
$controller = $_GET['controller'];
$action = $_GET['action'];
 
// 实例化一个Zend_Controller_Request_Http
$request = new Zend_Controller_Request_Http();
 
// 设定Zend_Controller_Request_Http路径信息
$request->setPathInfo($module ./. $controller ./. $action);
 
// 获得Zend_Controller_Front实例
$frontController = Zend_Controller_Front::getInstance();
 
// 打开异常开关
$frontController->throwExceptions(true);
 
// 设定模块目录
$frontController->addModuleDirectory(./modules’);
 
// 禁用模板解析,如果习惯用Smarty的话可以这么做,因为我们只是很简单的展示(直接打印),所以禁用
$frontController->setParam(’noViewRenderer’, true);
 
// 设定分发器请求
$frontController->setRequest($request);
 
// 开始分发
$frontController->dispatch();
?>

Zend_Controller是ZF中最复杂的部分,借用手册中的文字:

Zend_Controller工作流用若干组件来实现。虽然不需要完全理解所有组件的基础知识来使用它,但是拥有工作流程的知识很有帮助。

  • Zend_Controller_Front 控制了Zend_Controller系统的整个工作流。它是前端控制器(FrontController)模型的解释。Zend_Controller_Front处理所有由服务器接收的请求并负责把请求派发给动作控制器(ActionControllers)(Zend_Controller_Action)。
  • Zend_Controller_Request_Abstract (often referred to as the Request Object)描述请求环境和提供设置和读取控制器和动作名字以及任何请求参数的方法。另外它跟踪它所包含的动作是否被Zend_Controller_Dispatcher派遣。抽象请求对象的扩展可被用来封装整个请求环境,为了设置控制器和动作的名字,它允许路由器从请求环境中读出信息。缺省地,Zend_Controller_Request_Http被用来访问整个HTTP请求环境。
  • Zend_Controller_Router_Interface用来定义路由器。路由是个过程,在这个过程中它通过检查请求环境来决定哪个个控制器和哪个控制器中的动作应该接受请求。控制器、动作和可选的参数就通过Zend_Controller_Dispatcher_Standard处理来设置在请求对象中。路由只发生一次:在最初收到请求并在第一个控制器被派遣之前。缺省路由器,Zend_Controller_Router_Rewrite,从Zend_Controller_Request_Http取出URI的终点作为参数并基于在url中的路径信息分解成控制器、动作和参数。作为一个例子,URL http://localhost/foo/bar/key/value将被解析为foo控制器、bar 动作和带有一个值value的参数key。Zend_Controller_Router_Rewrite也可以用来匹配任意的路径;参见路由器文档 有更多的信息。
  • Zend_Controller_Dispatcher_Interface被用来定义派遣器。派遣是个过程,在这个过程中它从请求对象中取出控制器和动作并映射它们到控制器文件/类和在控制器中的动作方法。如果控制器和动作不存在,它派遣缺省的控制器和动作。实际的派遣过程包括初始化控制器类和在这个类中调用动作方法。不像路由,只发生一次,派遣是循环发生的。如果请求对象的派遣状态在任何点上重置,循环将被重复,调用不论哪个当前在请求对象中的动作。第一次循环随请求对象的派遣状态设置(布尔 true)完成,它将完成处理。缺省的派遣器是Zend_Controller_Dispatcher_Standard。它定义控制器为以Controller结尾的 MixedCasedClasses,并且动作为以Action结尾的camelCasedMethods:FooController::barAction()。在这个例子中,控制器是foo,动作是the action as bar。
  • Zend_Controller_Action是基本的动作控制器组件。每个控制器是一个从Zend_Controller_Action class扩展的单个的类并且应该包含一个或更多的动作方法。
  • Zend_Controller_Response_Abstract定义了一个基本的响应类,用来从动作控制器收集和返回响应。头和body的内容它都收集。缺省的响应类是Zend_Controller_Response_Http,它适合用于HTTP环境。

上面的文字,仔细的去琢磨吧,参照源代码,加入一下断点,可能会对理解会有帮助。

Tags: , ,

Post a Comment