在开始之前,我有必要介绍一下 Zend_Application 。这是 Zend Framework 1.8 发布中的重大变更,可以说 Zend_Application 和其引入的 Bootstrap 及 Resource 概念,大大简化了应用程序的初始化组装过程,标志着 Zend Framework 真正迈向成熟。 Zend_Application 提出了两个关键概念 : 1. Bootstrap 对于 Bootstrap,我想接触过 Zend Framework 的人都不会陌生,意即把初始化程序的过程封装,以便管理及修改。在1.8版本出来之前,我想大部分人都是这样(或类似这样)写的 : // Bootstrap.php class Bootstrap { // ... public function initLoader(){...} public function initController(){...} public function initDb(){...} public function initView(){...} public function initLayout(){...} public function initSession(){...} public function initAuth(){...} public function initAcl(){...} // ... } 这样通常会导致 Bootstrap 非常巨大而臃肿,而若以 Zend_Application 的形式来做的话,则只需要创建 Zend_Application 实例,并编写相关的配置文件 (.ini) 就可以了,至于如何创建我将在下面的内容中详细介绍。 2. Resource Zend_Application_Resource 是 Zend Framework 针对 php 这种 web 开发语言的特性而加入的。它所阐述的思想是:按需加载 (Loaded On Demand) 。因为 php 每次解析都是资源循环的完整过程,这使得如何将每次php解析的代码量减至最低,就成了优化php应用程序的重要一环,也是众多框架在开发过程中的重点问题之一。 Resource 的概念实际上可以理解为 Zend Framework 组件,例如 Zend_Controller, Zend_Db, Zend_View 等等。同时它也允许用户自定资源以调用自己的组件。例如 Zend_Application_Resource_Db 的工作就是实例化 Zend_Db 对象,并设置默认 adapter : class Zend_Application_Resource_Db extends Zend_Application_Resource_ResourceAbstract { // ... // Defined by Zend_Application_Resource_Resource public function init() { // ... $this->_db = Zend_Db::factory($adapter, $this->getParams()); Zend_Db_Table::setDefaultAdapter($db); return $db; // ... } } 目前 Zend Framework 1.8 提供的默认资源总共10个: 1. Zend_Application_Resource_Db 2. Zend_Application_Resource_Frontcontroller 3. Zend_Application_Resource_Layout 4. Zend_Application_Resource_Locale 5. Zend_Application_Resource_Modules 6. Zend_Application_Resource_Navigation 7. Zend_Application_Resource_Router 8. Zend_Application_Resource_Session 9. Zend_Application_Resource_Translate 10. Zend_Application_Resource_View 相信在未来的版本中会有所增加。 下面是初始化一个 FrontController 资源的最简单代码 : // 定义应用程序路径 defined('APPLICATION_PATH') || define('APPLICATION_PATH', MY_PROJECT_ROOT . '/application'); // 定义应用程序环境 defined('APPLICATION_ENV') || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production')); // Zend_Application 和配置的 FrontController 信息 require_once 'Zend/Application.php'; $application = new Zend_Application( APPLICATION_ENV, array( 'resources' => array( 'FrontController' => array( 'controllerDirectory' => APPLICATION_PATH . '/controllers', ), ), ) ); // 加载资源并运行程序 $application->bootstrap(); $application->run(); 而通过 Zend_Application_Resource_ResourceAbstract 虚拟类,我们可以很方便的注册自己的资源,然后通过配置文件 (如 Application.ini) 安插进各自的 Bootstrap 过程中。而同时我们可以通过 Zend_Application_Bootstrap_Bootstrap::bootstrap($resource) 方法来动态载入所需资源 (resource) 。 例如我要注册一个自定义的视图 (View) 资源 : // 自定义 view 资源 class Kbs_Application_Resource_View extends Zend_Application_Resource_ResourceAbstract { protected $_view; // 初始化 view // @return Zend_View $view public function init() { if (null === $this->_view) { $options = $this->getOptions(); $view = new Zend_View($options); ...... $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper( 'ViewRenderer' ); $viewRenderer->setView($view); $this->_view = $view; } return $this->_view; } } 接着是配置 : $application = new Zend_Application(APPLICATION_ENV, array( // 自定义资源的路径及其前缀 'pluginPaths' => array( 'Kbs_Application_Resource' => 'Kbs/Application/Resource/', ), 'resources' => array( 'FrontController' => array(...), ...... // View 资源 'View' => array('title' => 'my application'), ), )); 当然也可以写在配置文件中,例如 : // Application.ini pluginPaths.Kbs_Application_Resource = APPLICATION_PATH "/../library/Kbs/Application/Resource/" resources.view.title = "my application" resources.view.encoding = "UTF-8" 然后给出配置文件路径 : $application = new Zend_Application( APPLICATION_ENV, PROJECT_ROOT . '/Config/Application.ini' )); -------------------------------------------------------------------------------- 经过以上的预习,我们大致了解了一下 Zend_Application 及 Zend_Application_Resource 的情况。接下来让我们正式进入主题 : 如何用 Zend_Application 实现应用程序的模块化及模板化设计。 Zend Framework 在基于 MVC 结构之上,提供了一套完整的模块设计 (Modular Design) 方案,可以说 Zend Framework 中的 MVC 是建立在模块之下的,每个模块都可以拥有自己的完整的 MVC 结构。 但是在 Zend Framework 中为不同模块设置不同模板,甚至为同一模块设置多个模板确不是那么容易的,需要一定的配置和技巧。 首先,我们创建应用程序目录结构如下 : 在 application 目录下我们设有 modules 及 templates 两个目录,其中各都有 admin 及 front 两个模块。modules 中的各个模块的 controllers 文件夹是存放所有控制器的地方,如 IndexController 等。templates 中的各个模块文件夹则存放所有的模板,如上图,模块 front 中存放了名为 default 和 oceanStyle 两种模板。 我们将拓展资源放在 library/Kbs/Application/Resource/ 目录下 : 再来是完整的配置文件 : ; Application.ini [production] ;=========== 库文件路径 ;includePaths.library = APPLICATION_PATH "/../library/" ;=========== 类自动加载的前缀 autoloadernamespaces.0 = "Zend_" autoloadernamespaces.1 = "ZendX_" autoloadernamespaces.2 = "Kbs_" ;=========== php ini 配置 phpsettings.date.timezone = "Asia/Shanghai" phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 phpsettings.error_reporting = 8191 ;=========== bootstrap 类的路径及类名 bootstrap.path = APPLICATION_PATH "/Bootstrap.php" bootstrap.class = "Bootstrap" ;=========== 自定义资源的路径及其前缀 pluginPaths.Kbs_Application_Resource = APPLICATION_PATH "/../library/Kbs/Application/Resource/" ;=========== front controller 配置 resources.frontController.moduleDirectory = APPLICATION_PATH "/modules/" resources.frontController.moduleControllerDirectoryName = "controllers" resources.frontController.defaultModule = "front" resources.frontController.plugins.common = "Kbs_Controller_Plugin_Common" resources.frontController.noErrorHandler = 0 resources.frontController.throwExceptions = 1 ;=========== layout 布局,实际上我们交由 view 来配置 resources.layout.layout = "we use resources.view.params.module.layout instead" resources.layout.layoutPath = "we use resources.view.params.module.layoutPath instead" ;=========== view 配置 resources.view.title = "" resources.view.encoding = "UTF-8" resources.view.helperPathPrefix = "Kbs_View_Helper_" resources.view.helperPath = "Kbs/View/Helper/" ;=========== front 和 admin 模块的 view 参数,包括 scripts 文件路径及前缀,layout 路径及名称 resources.view.params.front.basePath = APPLICATION_PATH "/templates/front/default/" resources.view.params.front.helperPathPrefix = "Kbs_View_Helper_Front_" resources.view.params.front.helperPath = "Kbs/View/Helper/Front/" resources.view.params.front.layout = "frontlayout" resources.view.params.front.layoutPath = APPLICATION_PATH "/templates/front/default/layout/" resources.view.params.admin.basePath = APPLICATION_PATH "/templates/admin/default/" resources.view.params.admin.helperPathPrefix = "Kbs_View_Helper_Admin_" resources.view.params.admin.helperPath = "Kbs/View/Helper/Admin/" resources.view.params.admin.layout = "adminlayout" resources.view.params.admin.layoutPath = APPLICATION_PATH "/templates/admin/default/layout/" ;=========== view 的其它参数 resources.view.params.pathCss = "/public/css/" resources.view.params.pathImg = "/public/img/" resources.view.params.pathJs = "/public/js/" resources.view.params.doctype = "HTML4_STRICT" resources.view.params.charset = "utf-8" ;=========== 数据库配置 resources.db.adapter = "pdo_mysql" resources.db.params.host = "localhost" resources.db.params.username = "xxx" resources.db.params.password = "xxx" resources.db.params.dbname = "xxx" resources.db.isDefaultTableAdapter = true resources.db.params.driver_options.1002 = "SET NAMES UTF8;" ;=========== 翻译配置 resources.translate.registry_key = "Zend_Translate" resources.translate.adapter = "array" resources.translate.options.scan = "directory" resources.translate.data.directory = APPLICATION_PATH "/languages/" resources.translate.data.fileExt = ".php" ;=========== locale resources.locale = true [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 phpsettings.error_reporting = 8191 resources.db.params.username = "xxx" resources.db.params.password = "xxx" resources.db.params.dbname = "xxx" [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 phpsettings.error_reporting = 8191 resources.db.params.username = "xxx" resources.db.params.password = "xxx" resources.db.params.dbname = "xxx" 在这里,testing 及 development 均继承自 production 环境。值得注意的是,phpsettings.* 是内置的 php 运行环境参数设定, 当然你也可以用 .htaccess 或者直接在 php.ini 中设定好。bootstrap.* 则是初始化应用程序所需的 bootstrap 类及其路径。而 resources.* 就是我们所说的资源。 以上仅为个人的配置,不一定适合每个应用,具体还要按自己需要修改。 接下来就是 index.php 入口 : // 项目根目录 defined('PROJECT_ROOT') || define('PROJECT_ROOT', realpath(dirname(dirname(__FILE__)))); // 定义到 application 的路径 defined('APPLICATION_PATH') || define('APPLICATION_PATH', PROJECT_ROOT . '/application'); // 定义开发环境 defined('APPLICATION_ENV') || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production')); // Include paths set_include_path(implode(PATH_SEPARATOR, array( PROJECT_ROOT . '/library' ))); // Zend_Application require_once 'Zend/Application.php'; // Create application $application = new Zend_Application( APPLICATION_ENV, PROJECT_ROOT . '/library/Kbs/Config/Application.ini' ); // 开发环境下打开自动加载警告 if ('production' !== APPLICATION_ENV) { $application->getAutoloader()->suppressNotFoundWarnings(false); } // 我们仅加载 frontController 资源 $application->getBootstrap()->bootstrap('FrontController'); $application->run(); 需要解释的是 APPLICATION_ENV 是预设的系统环境变量,它将用于 Application.ini 中区分应用程序运行环境,这里预设3个值:development, testing, production,分别表示开发环境,测试环境及实际运行环境。 注意我们在最开始仅仅载入了 frontController 这个资源,这是为了将初始化资源降到最低限度。我们将把其它资源部分地交由 Kbs_Controller_Plugin_Common 插件来按需分配,而余下部分则在需要时再通过 Zend_Application_Bootstrap_Bootstrap::bootstrap($resource) 按需加载。 我们使用 plugin resource 而不是使用在 bootstrap 中重载资源(e.g. Bootstrap::_initView)的方法来进行资源管理,这样的好处是我们的 Bootstrap 类将非常简洁 : // Kbs/Application/Bootstrap.php class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { // 我们不需要在这里重载任何资源 } 现在我们需要让程序 frontController 知道模板 (template) 的位置,为此我们不得不使用动作插件 (action plugin),这个我们已经在前面 Application.ini 中定义好了 : // 自定义拓展插件,名为 common resources.FrontController.plugins.common = "Kbs_Controller_Plugin_Common" // 自定义应用程序插件 Kbs_Controller_Plugin_Common 类 require_once('Zend/Controller/Plugin/Abstract.php'); class Kbs_Controller_Plugin_Common extends Zend_Controller_Plugin_Abstract { // route 结束时 public function routeShutdown(Zend_Controller_Request_Abstract $request) { // 获取模块名,如 admin,front 等 $module = $request->getModuleName(); // bootstrap 类 $bootstrap = Zend_Controller_Front::getInstance()->getParam('bootstrap'); // 加载 view $bootstrap->bootstrap('View'); $view = $bootstrap->getResource('View'); $moduleParams = $view->$module; // 配置 view $view->addBasePath($moduleParams['basePath']) ->addHelperPath($moduleParams['helperPath'], $moduleParams['helperPathPrefix']); // 加载 layout 并配置 $bootstrap->bootstrap('Layout'); $layout = $bootstrap->getResource('Layout'); $layout->setLayoutPath($moduleParams['layoutPath']) ->setLayout($moduleParams['layout']); } } Plugin 是我目前所能想到的最好的解决方案,因为我们将在 routeShutdown 路由结束时很方便的取得 module 模块名,从而根据预先在 Application.ini 里面设定的配置信息,来获取相应的模板信息。 资源 Kbs_Application_Resource_View 定义如下 : // 拓展资源 (plugin resource) view class Kbs_Application_Resource_View extends Zend_Application_Resource_ResourceAbstract { protected $_view; // 初始化 view public function init() { if (null === $this->_view) { // 获取从 Application.ini中 的配置 $options = $this->getOptions(); $view = new Zend_View($options); if (!empty($options['params'])) { foreach ($options['params'] as $key => $value) { $view->$key = $value; } } // viewRenderer 动作助手 $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper( 'ViewRenderer' ); // 保存配置好的视图对象 $viewRenderer->setView($view); $this->_view = $view; } return $this->_view; } } 到此,我们已经基本上完成了所有工作。接下来就是完成 layout 及 scripts 了,当然这不一定是程序员做的事了,大可交给专业设计人员。 上例中,只要把 resources.view.params.front.basePath = APPLICATION_PATH "/templates/front/default/" resources.view.params.front.layoutPath = APPLICATION_PATH "/templates/front/default/layout" 换成 resources.view.params.front.basePath = APPLICATION_PATH "/templates/front/oceanStyle/" resources.view.params.front.layoutPath = APPLICATION_PATH "/templates/front/oceanStyle/layout" 就能够把模板从 default 切换成 oceanStyle 。 以上便是如何用 Zend_Application 配置及组装应用程序,并完成多模块及多模板的基本过程。这里我忽略了 Zend/Application/Module 及其相关内容,因为据内部消息称,在版本1.9及2.0之前,这部分内容还会有较大修正和改善,所以在这里就不详细说明了