注: zf 命令行工具
Make a note of the absolute path to this script.
Wherever you see references to the command zf, please substitute the absolute path to the script. On Unix-like systems, you may want to use your shell's alias functionality: aliaszf.sh=path/to/ZendFramework/bin/zf.sh
.
If you have problems setting up the zf command-line tool, please refer to
the manual
.
在你的Zend Framework安装目录中有个bin/子目录,里面含有分别为Unix用户和Window用户准备的zf.sh和zf.bat脚本。记下这个脚本的绝对路径。
无论在哪里看到zf命令的参考(references),请替代为脚本的绝对路径。在类Unix系统中,你可能想使用shell的别名函数:alish
zf.sh=path/to/ZendFramework/bin/zf.sh.
如果你再安装zf命令行工具是有任何问题,请参考手册。
打开终端(windows:开始->运行,然后使用cmd命令)。到你准备开始新项目的目录。然后使用相应的脚本路径并且执行下面当中的一个:
- % zf create project quickstart
运行这个命令将创建你的基本网站结构,包括你的初始控制器和视图。目录树看起来像下面这样:
- quickstart
- |-- application
- | |-- Bootstrap.php
- | |-- configs
- | | `-- application.ini
- | |-- controllers
- | | |-- ErrorController.php
- | | `-- IndexController.php
- | |-- models
- | `-- views
- | |-- helpers
- | `-- scripts
- | |-- error
- | | `-- error.phtml
- | `-- index
- | `-- index.phtml
- |-- library
- |-- public
- | |-- .htaccess
- | `-- index.php
- `-- tests
- |-- application
- | `-- bootstrap.php
- |-- library
- | `-- bootstrap.php
- `-- phpunit.xml
这个时候,如果你还没有将Zend Framework加入到你的include_path,我们推荐或者拷贝或者链接它到你的library/目录,无论哪种情况,你将想或者递归的拷贝或者链接Zend Framework 安装目录的library/Zend/子目录到你的项目的library/目录。在类unix系统中,它将象下面这样:
- # Symlink:
- % cd library; ln -s path/to/ZendFramework/library/Zend .
-
- # Copy:
- % cd library; cp -r path/to/ZendFramework/library/Zend .
在windows系统中,在资源管理其中可以很容易的做这个事情。
既然项目已经创建了,开始要弄懂的主要的工作就是引导程序(bootstrap),配置(configuration),动作控制器(action controllers)和视图(views)。
引导程序(Bootstrap)
引导类定义哪些资源和组件要去初始化。默认情况下,Zend Framework 的前端控制器是被初始化的,并且使用application/controllers/ 作为寻找动作控制器的默认的目录(稍后更多)。这个类看起来像这样:
- // application/Bootstrap.php
-
- class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
- {
- }
就想你看到的,要开始的内容没有多少
配置
Zend Framework
自身是没有配置的,你经常需要配置你的应用程序。默认的配置位于application/configs/application.ini,它包含一些设置你的PHP环境的基本指令(比如,打开或关闭错误报告),指示你的引导类(bootstrap class)路径(以及它的类名),还有你的动作控制器路径,它看起来像下面这样:
- ; application/configs/application.ini
-
- [production]
- phpSettings.display_startup_errors = 0
- phpSettings.display_errors = 0
- includePaths.library = APPLICATION_PATH "/../library"
- bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
- bootstrap.class = "Bootstrap"
- appnamespace = "Application"
- resources.frontController.controllerDirectory = APPLICATION_PATH"/controllers"
- resources.frontController.params.displayExceptions = 0
-
- [staging : production]
-
- [testing : production]
- phpSettings.display_startup_errors = 1
- phpSettings.display_errors = 1
-
- [development : production]
- phpSettings.display_startup_errors = 1
- phpSettings.display_errors = 1
这个文件的许多东西应该注意,首先,当使用INI类型的配置,你应该直接提到常量并且扩展它们;APPLICATION_PATH 实际上是一个常量。另外注意有许多定义的部分:production,staging,testing和development。后面三个继承“production”环境的设置。这是一个有用的组织配置方法去确信适当的配置在应用程序开发的每个阶段都是可用的。
动作控制器
应用程序的动作控制器包含应用程序工作流,并且做映射请求到适当模型和视图的工作。
一个动作控制器应该有一个或多个以“Action”结尾的方法;这些方法可能后面通过web被请求。默认的,Zend Framework URLs按照“/controller/action” 模式,这里“controller”映射动作控制器名(减去“Controller”后缀),“action”映射一个动作方法(减去“Action”后缀 )。
典型的,你总是需要一个IndexController,这是一个后备控制器(fallback controller),而且它也是站点的主页。还需要一个ErrorController,它为了指示诸如HTTP 404错误(没有发现控制器或者动作)和HTTP 500错误的发生(应用程序错误)。
默认的 IndexController 像下面这样:
- // application/controllers/IndexController.php
-
- class IndexController extends Zend_Controller_Action
- {
-
- public function init()
- {
- /* Initialize action controller here */
- }
-
- public function indexAction()
- {
- // action body
- }
- }
默认的 ErrorController像下面这样:
- // application/controllers/ErrorController.php
-
- class ErrorController extends Zend_Controller_Action
- {
-
- public function errorAction()
- {
- $errors = $this->_getParam('error_handler');
-
- switch ($errors->type) {
- caseZend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
- caseZend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
- caseZend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
-
- // 404 error -- controller or action not found
- $this->getResponse()->setHttpResponseCode(404);
- $this->view->message = 'Page not found';
- break;
- default:
- // application error
- $this->getResponse()->setHttpResponseCode(500);
- $this->view->message = 'Application error';
- break;
- }
-
- $this->view->exception = $errors->exception;
- $this->view->request = $errors->request;
- }
- }
你会注意到:(1)IndexController没有包含真是代码;(2)ErrorController提到视图属性。这导致我们很好的进入到下一个主题
视图
Zend Framework
中的视图是用普通的PHP代码写的。视图脚本放置在application/views/scripts/中,这里脚本被使用控制器名称进一步的分类。在我们的示例中,我们已经有了一个IndexController和一个ErrorController,因此在视图脚本目录有相应的index/ 和 error/ 子目录。在这些子目录中,随后你将发现和创建对应每个暴露的(exposed)控制器动作的视图脚本。在默认情况下,我们会有视图脚本index/index.phtml 和error/error.phtml.
视图脚本可以包含任何你想要的标记,并且使用<?php 开始标记和?>结束标记区插入PHP指令。
下面的代码是默认安装下index/index.phtml视图脚本:
- <!-- application/views/scripts/index/index.phtml -->
- <style>
-
- a:link,
- a:visited
- {
- color: #0398CA;
- }
-
- span#zf-name
- {
- color: #91BE3F;
- }
-
- div#welcome
- {
- color: #FFFFFF;
- background-image: url(http://framework.zend.com/images/bkg_header.jpg);
- width: 600px;
- height: 400px;
- border: 2px solid #444444;
- overflow: hidden;
- text-align: center;
- }
-
- div#more-information
- {
- background-image: url(http://framework.zend.com/images/bkg_body-bottom.gif);
- height: 100%;
- }
-
- </style>
- <div id="welcome">
- <h1>Welcome to the <span id="zf-name">Zend Framework!</span><h1 />
- <h3>This is your project's main page<h3 />
- <div id="more-information">
- <p>
- <img src="http://framework.zend.com/images/PoweredBy_ZF_4LightBG.png" />
- </p>
-
- <p>
- Helpful Links: <br />
- <a href="http://framework.zend.com/">Zend Framework Website</a> |
- <a href="http://framework.zend.com/manual/en/">Zend Framework
- Manual</a>
- </p>
- </div>
- </div>
-
Error/error.phtml
视图脚本有点更加有趣,因为它使用一些PHP的条件语句。
- <!-- application/views/scripts/error/error.phtml -->
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN";
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd>
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>Zend Framework Default Application</title>
- </head>
- <body>
- <h1>An error occurred</h1>
- <h2><?php echo $this->message ?></h2>
-
- <?php if ('development' == $this->env): ?>
-
- <h3>Exception information:</h3>
- <p>
- <b>Message:</b> <?php echo $this->exception->getMessage() ?>
- </p>
-
- <h3>Stack trace:</h3>
- <pre><?php echo $this->exception->getTraceAsString() ?>
- </pre>
-
- <h3>Request Parameters:</h3>
- <pre><?php echo var_export($this->request->getParams(), 1) ?>
- </pre>
- <?php endif ?>
-
- </body>
- </html>
创建一个虚拟主机
为了这个快速开始的目的,我们将假设你在使用Apache web服务器。Zend Framework完美的工作在其他的web服务器——包括Microsoft IIS,lighttppd,ngix等等——但是多数的开发者最低程度的应该熟悉Apache。并且它提供一个容易的Zend Framework 的目录结构和重写能力的简单介绍。
为了创建你的vhost,你应该知道httpd.conf文件的位置,还有一些需要知道的其他的配置文件的位置(and potentially where other configuration files are located)。一些常见的地点:
- /etc/httpd/httpd.conf (Fedora, RHEL, and others)
- /etc/apache2/httpd.conf (Debian, Ubuntu, and others)
- /usr/local/zend/etc/httpd.conf (Zend Server on *nix machines)
- C:/Program Files/Zend/Apache2/conf (Zend Server on Windows machines)
在你的httpd.conf中(或者在一些系统的 httpd-vhosts.conf中),你需要去做两件事情。首先,确信NameVirtualHost被定义,典型的,你将把它设为“*:80”。其次,定义一个虚拟主机:
- <VirtualHost *:80>
- ServerName quickstart.local
- DocumentRoot /path/to/quickstart/public
-
- SetEnv APPLICATION_ENV "development"
-
- <Directory /path/to/quickstart/public>
- DirectoryIndex index.php
- AllowOverride All
- Order allow,deny
- Allow from all
- </Directory>
- </VirtualHost>
有几件事情要注意。首先,注意我们的项目中的DocumentRoot 设置要明确指定public子目录;这意味着只有在那个目录中的文件在任何时候能够被服务器直接服务。其次,注意“AllowOverride”的Order和Allow指令;我们在项目中可以使用htacess文件,在开发过程中,这是一个好的办法,因为它可以防止当你改变你的站点指令的时候不断重新启动web服务器。实际上,在产品中,你应该可能将你的htaccess文件的内容放到你的服务器设置中并且禁用htaccess。第三,注意SetEnv命令,它要做的是为你的虚拟主机设置环境变量,这个变量将在index.php中为我们的Zend Framework应用程序设置APPLICATION_ENV常量。在产品中,你能够省略这个指令(在这种情况下,将使用默认值“产品”)或者明确的设定它为“产品”。
最后,你需要在你的hosts文件中增加一个条目,对应于在ServerName指令中设定的值,在类Unix系统中,这个文件通常位于/etc/hosts;在windows中,你通常会在c:/windows/system32/drivers/etc中找到它。无论什么系统,这个条目看起来会是这样:
- 127.0.0.1 quickstart.local
开始你的webserver(或者重新启动它),你就可以准备开始了。
检查
到目前为止,你应该能够启动你的初步Zend Framework应用。在浏览器中键入在前面的章节中配置的服务器名,你应该可以看到一个欢迎页面
创建布局
你可能已经注意到在前面章节中的视图脚本是HTML片段而非完整的页面代码。这是被设计成这样的。我们想让返回内容的动作只与动作本身相关,而不是与作为一个整体的应用程序相关。
现在我们必须组合产生的内容到完整的HTML页面。我们还希望应用程序有一个一致的外观和感觉。我们将使用一个全局的站点布局去完成这两个任务。
Zend Framework有两种设计模式去实施布局:
» 两步视图法 和
» 组合视图法.两步视图法经常与
»变换视图 模式联合使用。基本思想是你的应用程序视图创建内容(representation),然后把它注入到主视图以便最后的转换。组合视图模式处理一个或多个原子视图、应用程序视图。
在Zend Framework中,Zend Layout 结合这些模式的思想。不是每个动作视图脚本需要包括全站内容(artifacts),替代的是他们只要关注他们自己的责任。
偶然的,你可能在你的全站视图脚本中需要特定应用信息。幸运的是,Zend Framework提供了许多视图占位符以便在你的动作视图脚本中提供那些信息
要开始使用Zend_Layout,首先我们需要通知引导程序(bootstrap)去使用布局资源。这个可以使用“zf enable layout”命令:
- % zf enable layout
- Layouts have been enabled, and a default layout created at
- application/layouts/scripts/layout.phtml
- A layout entry has been added to the application config file.
正如命令所指出的那样,application/configs/application.ini 被更新了,现在在“production”节中包含如下内容
- ; application/configs/application.ini
-
- ; Add to [production] section:
- resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
最终的INI文件看起来像下面这样:
- ; application/configs/application.ini
-
- [production]
- ; PHP settings we want to initialize
- phpSettings.display_startup_errors = 0
- phpSettings.display_errors = 0
- includePaths.library = APPLICATION_PATH "/../library"
- bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
- bootstrap.class = "Bootstrap"
- appnamespace = "Application"
- resources.frontController.controllerDirectory = APPLICATION_PATH"/controllers"
- resources.frontController.params.displayExceptions = 0
- resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
-
- [staging : production]
-
- [testing : production]
- phpSettings.display_startup_errors = 1
- phpSettings.display_errors = 1
-
- [development : production]
- phpSettings.display_startup_errors = 1
- phpSettings.display_errors = 1
这个指令告诉你的应用程序在application/layouts/scripts去寻找布局视图脚本。如果你检查你的目录树,你会看到这个目录已经为你创建了layout.phtml文件。
我们也想确信我们的应用程序有一个XHTML文档类型(doctype)声明。为了确信这点,我们需要增加一个资源到我们的引导程序(bootstrap)中。
在引导程序(bootstrap)中增加资源的最简单方法是仅仅创建一个以_init短语开始的protected方法。在这种情况下,我们想去初始化文档类型(doctype),所以我们再初始(bootstrap)类中将创建一个_initDoctype()方法:
- // application/Bootstrap.php
-
- class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
- {
- protected function _initDoctype()
- {
- }
- }
在这个方法里,我们需要提示视图使用恰当的文档类型(doctype)。但是视图对象从哪里来呢?简单的解决办法是去初始化视图资源。一旦我们有了,我们能够从引导程序(bootstrap)中取出(pull)视图对象并使用它。 为了初始化视图资源,在你的application/configs/application.ini 文件中“production”部分增加下面的行:
- ; application/configs/application.ini
-
- ; Add to [production] section:
- resources.view[] =
这个告诉我们去初始化没有选项的视图(‘[]’说明视图键是一个数组,并且我们没有传递任何数据给它)。
既然我们有一个视图,让我们充实我们的_initDoctype()方法。在这个方法里面,我们将首先确信视图资源已经运行,提取视图对象,然后配置它
- // application/Bootstrap.php
-
- class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
- {
- protected function _initDoctype()
- {
- $this->bootstrap('view');
- $view = $this->getResource('view');
- $view->doctype('XHTML1_STRICT');
- }
- }
现在我们已经初始化Zend_Layout并且已经设置了文档类型(Doctype),让我们来创建我们全站的布局
- <!-- application/layouts/scripts/layout.phtml -->
- <?php echo $this->doctype() ?>
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>Zend Framework Quickstart Application</title>
- <?php echo $this->headLink()->appendStylesheet('/css/global.css') ?>
- </head>
- <body>
- <div id="header" style="background-color: #EEEEEE; height: 30px;">
- <div id="header-logo" style="float: left">
- <b>ZF Quickstart Application</b>
- </div>
- <div id="header-navigation" style="float: right">
- <a href="<?php echo $this->url(
- array('controller'=>'guestbook'),
- 'default',
- true) ?>">Guestbook</a>
- </div>
- </div>
-
- <?php echo $this->layout()->content ?>
-
- </body>
- </html>
我们使用layout()视图助手获得我们的应用程序内容,并访问“content”键。如果你想,你可以渲染其他的响应片段,但是大多是情况下,必须做的就是这么多。
还要注意使用headLink()占位符。这是产生HTML<link>元素的简单方法,以及在你整个应用程序中跟踪它们。如果你需要增加另外的css表以支持一个动作,你可以这么做,并确保它将出现在最后被渲染的页面
注:检查点
创建模型和数据表
在我们开始之前,让我们来思考几点:这些类将放在什么地方,我们如何找到它们?我们创建的默认的项目实例化一个自动加载(autoloader)。我们可以给它附加其他的自动加载(autoloader),所以它知道在哪里可以找到不同的类。典型的,我们想让我们各种各样的MVC类归入同一个目录树——在这个例子中是application/ ——并且大多数经常使用共同的前缀(common prefix)。
Zend_Controller_Front
有一个“模块(modules)”概念,它是一个单独的小应用程序。模块模拟在application/目录下用zf工具建立的目录结构,且里面所有的类假定有相同的前缀——模块名。“Application/”本身就是一个模块——“default”或者“application”模块。因此,我们将去设置在此目录下资源的自动导入。
Zend_Application_Module_Autoloader
提供将模块下各种资源映射到相应目录的功能,并且也提供一个标准的命名机制。在引导对象(bootstrap object)初始化时,这个类的实例被默认创建。你的应用程序引导程序(bootstrap)将默认的使用“Application”作为模块前缀。这样,我们的模型、表单和数据表类将以“Applicatin_”为前缀。
Now, let's consider what makes up a guestbook. Typically, they are simply a list of entries with a comment, timestamp, and, often, email address. Assuming we store them in a database, we may also want a unique identifier for each entry. We'll likely want to be able to save an entry, fetch individual entries, and retrieve all entries. As such, a simple guestbook model API might look something like this:
现在,我们来看看是什么组成了留言簿,典型的,他们简单的由评论(comment),戳(timestamp),经常还包括邮件地址的条目列表。假若将他们存储在数据库中,还要为每一个条目建立一个唯一的标识符。我们想要保存条目、查询单个条目,检索所有条目。因此,一个简单的留言薄模型API可能看起来像这样:
- // application/models/Guestbook.php
-
- class Application_Model_Guestbook
- {
- protected $_comment;
- protected $_created;
- protected $_email;
- protected $_id;
-
- public function __set($name, $value);
- public function __get($name);
-
- public function setComment($text);
- public function getComment();
-
- public function setEmail($email);
- public function getEmail();
-
- public function setCreated($ts);
- public function getCreated();
-
- public function setId($id);
- public function getId();
- }
-
- class Application_Model_GuestbookMapper
- {
- public function save(Application_Model_Guestbook $guestbook);
- public function find($id);
- public function fetchAll();
- }
__get()
和__set()将提供一个提供了一个便捷的机制去访问单个条目属性,代理其他的getter和setter方法(proxy to the other getters and setters),他们也帮助确保对象中只有白名单属性才是可用的
Find()
和fetchAll()提供获取单个条目和能力,而save()负责将条目存储到数据库。
从现在开始,我们能够开始建立我们的数据库
首先我们需要初始化我们的数据源。就像布局和视图资源那样,可以为数据源提供配置。我们可以使用“zf configure db-adapter”命令:
- % zf configure db-adapter /
- > 'adapter=PDO_SQLITE&dbname=APPLICATION_PATH "/../data/db/guestbook.db"' /
- > production
- A db configuration for the production has been written to the application config file.
-
- % zf configure db-adapter /
- > 'adapter=PDO_SQLITE&dbname=APPLICATION_PATH "/../data/db/guestbook-testing.db"' /
- > testing
- A db configuration for the production has been written to the application config file.
-
- % zf configure db-adapter /
- > 'adapter=PDO_SQLITE&dbname=APPLICATION_PATH "/../data/db/guestbook-dev.db"' /
- > development
- A db configuration for the production has been written to the application config file.
现在编辑你的application/configs/application.ini file文件,在相应的部分你将会看到下面的行被加进去。
- ; application/configs/application.ini
-
- [production]
- ; ...
- resources.db.adapter = "PDO_SQLITE"
- resources.db.params.dbname = APPLICATION_PATH"/../data/db/guestbook.db"
-
- [testing : production]
- ; ...
- resources.db.adapter = "PDO_SQLITE"
- resources.db.params.dbname = APPLICATION_PATH"/../data/db/guestbook-testing.db"
-
- [development : production]
- ; ...
- resources.db.adapter = "PDO_SQLITE"
- resources.db.params.dbname = APPLICATION_PATH"/../data/db/guestbook-dev.db"
最后的配置文件看起来像这样:
- ; application/configs/application.ini
-
- [production]
- phpSettings.display_startup_errors = 0
- phpSettings.display_errors = 0
- bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
- bootstrap.class = "Bootstrap"
- appnamespace = "Application"
- resources.frontController.controllerDirectory = APPLICATION_PATH"/controllers"
- resources.frontController.params.displayExceptions = 0
- resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
- resources.view[] =
- resources.db.adapter = "PDO_SQLITE"
- resources.db.params.dbname = APPLICATION_PATH"/../data/db/guestbook.db"
-
- [staging : production]
-
- [testing : production]
- phpSettings.display_startup_errors = 1
- phpSettings.display_errors = 1
- resources.db.adapter = "PDO_SQLITE"
- resources.db.params.dbname = APPLICATION_PATH"/../data/db/guestbook-testing.db"
-
- [development : production]
- phpSettings.display_startup_errors = 1
- phpSettings.display_errors = 1
- resources.db.adapter = "PDO_SQLITE"
- resources.db.params.dbname = APPLICATION_PATH"/../data/db/guestbook-dev.db"
注意数据库将被存储在“data/db/”目录下。创建这些目录并且使它们是可写的。在类unix系统中,你可以像下面这么做:
- % mkdir -p data/db; chmod -R a+rwX data
在wendows系统中,你需要在资源管理器中创建目录并设置对任何人的写权限。
这里我们有一个到数据库的连接,它连接到位于application/data/目录下的sqlite数据库。我们来设计一个简单的数据表用于存储我们的留言板条目。
- -- scripts/schema.sqlite.sql
- --
- -- You will need load your database schema with this SQL.
-
- CREATE TABLE guestbook (
- id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- email VARCHAR(32) NOT NULL DEFAULT '[email protected]',
- comment TEXT NULL,
- created DATETIME NOT NULL
- );
-
- CREATE INDEX "id" ON "guestbook" ("id");
为使我们的工作有创意的(so that we can have some working data out of the box),我们来创建一些数据行使我们的应用程序更加有趣
- -- scripts/data.sqlite.sql
- --
- -- You can begin populating the database with the following SQL statements.
-
- INSERT INTO guestbook (email, comment, created) VALUES
- ('[email protected]',
- 'Hello! Hope you enjoy this sample zf application!',
- DATETIME('NOW'));
- INSERT INTO guestbook (email, comment, created) VALUES
- ('[email protected]',
- 'Baz baz baz, baz baz Baz baz baz - baz baz baz.',
- DATETIME('NOW'));
现在我们已经定义了数据表和数据。让我们一起来得到一个脚本,去建立这个数据库。当然,这个在产品中是不需要的,但是这个脚本将帮助开发者建立局部的数据库需求,以便有一个完整的应用程序。创建带有以下内容的“scripts/load.sqlite.php”脚本
- // scripts/load.sqlite.php
-
- /**
- * Script for creating and loading database
- */
-
- // Initialize the application path and autoloading
- defined('APPLICATION_PATH')
- || define('APPLICATION_PATH', realpath(dirname(__FILE__) .'/../application'));
- set_include_path(implode(PATH_SEPARATOR, array(
- APPLICATION_PATH . '/../library',
- get_include_path(),
- )));
- require_once 'Zend/Loader/Autoloader.php';
- Zend_Loader_Autoloader::getInstance();
-
- // Define some CLI options
- $getopt = new Zend_Console_Getopt(array(
- 'withdata|w' => 'Load database with sample data',
- 'env|e-s' => 'Application environment for which to create database (defaults to development)',
- 'help|h' => 'Help -- usage message',
- ));
- try {
- $getopt->parse();
- } catch (Zend_Console_Getopt_Exception $e) {
- // Bad options passed: report usage
- echo $e->getUsageMessage();
- return false;
- }
-
- // If help requested, report usage message
- if ($getopt->getOption('h')) {
- echo $getopt->getUsageMessage();
- return true;
- }
-
- // Initialize values based on presence or absence of CLI options
- $withData = $getopt->getOption('w');
- $env = $getopt->getOption('e');
- defined('APPLICATION_ENV')
- || define('APPLICATION_ENV', (null === $env) ? 'development': $env);
-
- // Initialize Zend_Application
- $application = new Zend_Application(
- APPLICATION_ENV,
- APPLICATION_PATH . '/configs/application.ini'
- );
-
- // Initialize and retrieve DB resource
- $bootstrap = $application->getBootstrap();
- $bootstrap->bootstrap('db');
- $dbAdapter = $bootstrap->getResource('db');
-
- // let the user know whats going on (we are actually creating a
- // database here)
- if ('testing' != APPLICATION_ENV) {
- echo 'Writing Database Guestbook in (control-c to cancel): '. PHP_EOL;
- for ($x = 5; $x > 0; $x--) {
- echo $x . "/r"; sleep(1);
- }
- }
-
- // Check to see if we have a database file already
- $options = $bootstrap->getOption('resources');
- $dbFile = $options['db']['params']['dbname'];
- if (file_exists($dbFile)) {
- unlink($dbFile);
- }
-
- // this block executes the actual statements that were loaded from
- // the schema file.
- try {
- $schemaSql = file_get_contents(dirname(__FILE__) .'/schema.sqlite.sql');
- // use the connection directly to load sql in batches
- $dbAdapter->getConnection()->exec($schemaSql);
- chmod($dbFile, 0666);
-
- if ('testing' != APPLICATION_ENV) {
- echo PHP_EOL;
- echo 'Database Created';
- echo PHP_EOL;
- }
-
- if ($withData) {
- $dataSql = file_get_contents(dirname(__FILE__) .'/data.sqlite.sql');
- // use the connection directly to load sql in batches
- $dbAdapter->getConnection()->exec($dataSql);
- if ('testing' != APPLICATION_ENV) {
- echo 'Data Loaded.';
- echo PHP_EOL;
- }
- }
-
- } catch (Exception $e) {
- echo 'AN ERROR HAS OCCURED:' . PHP_EOL;
- echo $e->getMessage() . PHP_EOL;
- return false;
- }
-
- // generally speaking, this script will be run from the command line
- return true;
现在,我们来执行这个脚本,从一个终端或者DOS命令行做如下工作:
- % php scripts/load.sqlite.php --withdata
你应该会看到像下面一样的输出:
- path/to/ZendFrameworkQuickstart/scripts$ php load.sqlite.php --withdata
- Writing Database Guestbook in (control-c to cancel):
- 1
- Database Created
- Data Loaded.
现在,我们的留言板有了一个完整的工作数据库和表。我们下面几步准备去建立我们的应用程序代码。这包括建立一个数据源(我们的例子里,我们将使用Zend_Db_Table)和一个数据映射(data mapper)去连接数据源和数据模型(model)。最后,我们将建立与这个模型相互作用控制器去显示已经存在的和要发布的条目。
我们将使用
» 表数据网关(Table Data Gateway)去连接我们的数据源;Zend_Db_Table提供这个功能。为了开始,我们创建一个基于Zend_Db_Table的表类,就像我们已经为布局和数据适配器做的那样,我们能够使用zf工具的“create db-table”命令,它需要至少两个参数,一个是你想要引用类的名称,另一个是要映射的数据表
- % zf create db-table Guestbook guestbook
- Creating a DbTable at application/models/DbTable/Guestbook.php
- Updating project profile 'zfproject.xml'
看一下你的目录树,你会看到一个新的目录:application/models/DbTable/被创建,里面还有一个名为Guestbook.php的文件,若你打开这个文件,你会看到下面的内容:
- // application/models/DbTable/Guestbook.php
-
- /**
- * This is the DbTable class for the guestbook table.
- */
- class Application_Model_DbTable_Guestbook extendsZend_Db_Table_Abstract
- {
- /** Table name */
- protected $_name = 'guestbook';
- }
注意这个类的前缀:Application_Model_DbTable。我们模块名的类前缀“Application”是第一个部分,第二部分是“Model_DbTable”。后者是映射模块的目录“models/DbTable”。
扩展的Zend_Db_Table提供一个表名和可选的主键(如果它不是“id”),这都是有必要的。
现在我们来创建
» 数据映射(Data Mapper)。Data Mapper映射域对象到数据库。在我们这个例子中,它将映射我们的模型(model)Application_Model_Guestbook到数据源Application_Model_DbTable_Guestbook. Data mapper典型的API像下面这样:
- // application/models/GuestbookMapper.php
-
- class Application_Model_GuestbookMapper
- {
- public function save($model);
- public function find($id, $model);
- public function fetchAll();
- }
除了这些方法之外,我们将增加设置和检索表数据网关(Table Data Gateway)的方法。使用“zf CLI”工具来创建初始的类:
- % zf create model GuestbookMapper
- Creating a model at application/models/GuestbookMapper.php
- Updating project profile '.zfproject.xml'
现在,像下面这样编辑application/models/GuestbookMapper.php 中的Application_Model_GuestbookMapper类:
- // application/models/GuestbookMapper.php
-
- class Application_Model_GuestbookMapper
- {
- protected $_dbTable;
-
- public function setDbTable($dbTable)
- {
- if (is_string($dbTable)) {
- $dbTable = new $dbTable();
- }
- if (!$dbTable instanceof Zend_Db_Table_Abstract) {
- throw new Exception('Invalid table data gateway provided');
- }
- $this->_dbTable = $dbTable;
- return $this;
- }
-
- public function getDbTable()
- {
- if (null === $this->_dbTable) {
- $this->setDbTable('Application_Model_DbTable_Guestbook');
- }
- return $this->_dbTable;
- }
-
- public function save(Application_Model_Guestbook $guestbook)
- {
- $data = array(
- 'email' => $guestbook->getEmail(),
- 'comment' => $guestbook->getComment(),
- 'created' => date('Y-m-d H:i:s'),
- );
-
- if (null === ($id = $guestbook->getId())) {
- unset($data['id']);
- $this->getDbTable()->insert($data);
- } else {
- $this->getDbTable()->update($data, array('id = ?' =>$id));
- }
- }
-
- public function find($id, Application_Model_Guestbook$guestbook)
- {
- $result = $this->getDbTable()->find($id);
- if (0 == count($result)) {
- return;
- }
- $row = $result->current();
- $guestbook->setId($row->id)
- ->setEmail($row->email)
- ->setComment($row->comment)
- ->setCreated($row->created);
- }
-
- public function fetchAll()
- {
- $resultSet = $this->getDbTable()->fetchAll();
- $entries = array();
- foreach ($resultSet as $row) {
- $entry = new Application_Model_Guestbook();
- $entry->setId($row->id)
- ->setEmail($row->email)
- ->setComment($row->comment)
- ->setCreated($row->created);
- $entries[] = $entry;
- }
- return $entries;
- }
- }
现在是时候创建我们模型类的时候了。我们将又一次使用“zf create model”命令来做这个事情:
- % zf create model Guestbook
- Creating a model at application/models/Guestbook.php
- Updating project profile '.zfproject.xml'
我们将修改这个空的PHP类,通过传递一个数据数组到构造函数(constructor)或者setOptions()方法使它容易去填充模型:
- // application/models/Guestbook.php
-
- class Application_Model_Guestbook
- {
- protected $_comment;
- protected $_created;
- protected $_email;
- protected $_id;
-
- public function __construct(array $options = null)
- {
- if (is_array($options)) {
- $this->setOptions($options);
- }
- }
-
- public function __set($name, $value)
- {
- $method = 'set' . $name;
- if (('mapper' == $name) || !method_exists($this,$method)) {
- throw new Exception('Invalid guestbook property');
- }
- $this->$method($value);
- }
-
- public function __get($name)
- {
- $method = 'get' . $name;
- if (('mapper' == $name) || !method_exists($this,$method)) {
- throw new Exception('Invalid guestbook property');
- }
- return $this->$method();
- }
-
- public function setOptions(array $options)
- {
- $methods = get_class_methods($this);
- foreach ($options as $key => $value) {
- $method = 'set' . ucfirst($key);
- if (in_array($method, $methods)) {
- $this->$method($value);
- }
- }
- return $this;
- }
-
- public function setComment($text)
- {
- $this->_comment = (string) $text;
- return $this;
- }
-
- public function getComment()
- {
- return $this->_comment;
- }
-
- public function setEmail($email)
- {
- $this->_email = (string) $email;
- return $this;
- }
-
- public function getEmail()
- {
- return $this->_email;
- }
-
- public function setCreated($ts)
- {
- $this->_created = $ts;
- return $this;
- }
-
- public function getCreated()
- {
- return $this->_created;
- }
-
- public function setId($id)
- {
- $this->_id = (int) $id;
- return $this;
- }
-
- public function getId()
- {
- return $this->_id;
- }
- }
最后,为了将所有这些部分连接在一起,我们来创建留言簿控制器(a guestbook controller),它将列出目前在数据库中已有的条目。
为了创建新的控制器,使用“zf create controller”命令:
- % zf create controller Guestbook
- Creating a controller at
- application/controllers/GuestbookController.php
- Creating an index action method in controller Guestbook
- Creating a view script for the index action method at
- application/views/scripts/guestbook/index.phtml
- Creating a controller test file at
- tests/application/controllers/GuestbookControllerTest.php
- Updating project profile '.zfproject.xml'
这将创建一个新的控制器,位于application/controllers/GuestbookController.php里的GuestbookController,它带有唯一的动作方法:indexAction。也会为这个控制器创建一个视图脚本目录,application/views/scripts/guestbook/,里面会有一个为index action创建的视图脚本
我们将使用“index”动作作为目标页面(landing page)去查看所有的留言簿条目
现在我们来使基本应用程序逻辑丰满起来,当进入indexAction(),我们将显示所有的留言簿条目,看起来像下面这样:
- // application/controllers/GuestbookController.php
-
- class GuestbookController extends Zend_Controller_Action
- {
- public function indexAction()
- {
- $guestbook = new Application_Model_GuestbookMapper();
- $this->view->entries = $guestbook->fetchAll();
- }
- }
当然,我们需要一个试图脚本和它一起运行。像下面这样编辑application/views/scripts/guestbook/index.phtml :
- <!-- application/views/scripts/guestbook/index.phtml -->
-
- <p><a href="<?php echo $this->url(
- array(
- 'controller' => 'guestbook',
- 'action' => 'sign'
- ),
- 'default',
- true) ?>">Sign Our Guestbook</a></p>
-
- Guestbook Entries: <br />
- <dl>
- <?php foreach ($this->entries as $entry): ?>
- <dt><?php echo $this->escape($entry->email) ?></dt>
- <dd><?php echo $this->escape($entry->comment) ?></dd>
- <?php endforeach ?>
- </dl>
Note
: Checkpoint
Now browse to "http://localhost/guestbook". You should see the following in your browser:
注:检查点
注
:
使用数据导入脚本
在本节中介绍的数据导入脚本(scripts/load.sqlite.php)能够被用作创建你所定义的每个环境下的数据库,以及导入简单的样本数据。在内部,它使用Zend_Console_Getopt,它提供了许多命令行开关。如果你提供 “-h”或者“--help”开关,它将给你可用的选择:
1.
Usage: load.sqlite.php
[
options
]
2.
--withdata|-w Load database with sample data
3.
--env|-e
[
]
Application environment
for
which to create database
4.
(
defaults to development
)
5.
--help|-h Help -- usage message
)]
]
“-e”开关允许你指定常量“APPLICATION_EVN”的值——它依次允许你去创建为每个环境定义的SQLLite数据库。确信当部署的时候为你的应用程序选择的环境运行脚本
创建表单
为了使留言簿有用,我们需要提交新条目的表单。
我们首先要做的是去创建实际的表单类。为创建空的表单类,执行:
- % zf create form Guestbook
- Creating a form at application/forms/Guestbook.php
- Updating project profile '.zfproject.xml'
这将创建带有类文件Guestbook.php的application/forms/目录。打开Guestbook.php文件并更新它,你将会看到:
- // application/forms/Guestbook.php
-
- class Application_Form_Guestbook extends Zend_Form
- {
- public function init()
- {
- // Set the method for the display form to POST
- $this->setMethod('post');
-
- // Add an email element
- $this->addElement('text', 'email', array(
- 'label' => 'Your email address:',
- 'required' => true,
- 'filters' => array('StringTrim'),
- 'validators' => array(
- 'EmailAddress',
- )
- ));
-
- // Add the comment element
- $this->addElement('textarea', 'comment', array(
- 'label' => 'Please Comment:',
- 'required' => true,
- 'validators' => array(
- array('validator' => 'StringLength', 'options' =>array(0, 20))
- )
- ));
-
- // Add a captcha
- $this->addElement('captcha', 'captcha', array(
- 'label' => 'Please enter the 5 letters displayed below:',
- 'required' => true,
- 'captcha' => array(
- 'captcha' => 'Figlet',
- 'wordLen' => 5,
- 'timeout' => 300
- )
- ));
-
- // Add the submit button
- $this->addElement('submit', 'submit', array(
- 'ignore' => true,
- 'label' => 'Sign Guestbook',
- ));
-
- // And finally add some CSRF protection
- $this->addElement('hash', 'csrf', array(
- 'ignore' => true,
- ));
- }
- }
上面的表单定义了五个元素:email address字段,评论字段,防止垃圾提交信息的CAPTCHA字段,提交按钮,和CSRF保护令牌(protection token)。
下一步,我们将增加signAction()到我们的GuestbookController,它将处理提交后的表单。为了创建动作和相关视图脚本,执行下面内容:
- % zf create action sign Guestbook
- Creating an action named sign inside controller
- at application/controllers/GuestbookController.php
- Updating project profile '.zfproject.xml'
- Creating a view script for the sign action method
- at application/views/scripts/guestbook/sign.phtml
- Updating project profile '.zfproject.xml'
就像你从输出中看到的,这将控制器中创建一个signAction()方法,以及相关的视图脚本。
我们来加入一些逻辑到我们的留言簿控制器提交动作(sign action)。我们需要首先检查是否得到的是POST还是用GET请求;如果是后者,我们将简单的将表单显示出来。然而,如果我们得到一个POST请求,我们将要去验证表单提交的数据。如果通过认证,则创建一个新的条目并存储它。这个逻辑看起来会像下面这样:
- // application/controllers/GuestbookController.php
-
- class GuestbookController extends Zend_Controller_Action
- {
- // snipping indexAction()...
-
- public function signAction()
- {
- $request = $this->getRequest();
- $form = new Application_Form_Guestbook();
-
- if ($this->getRequest()->isPost()) {
- if ($form->isValid($request->getPost())) {
- $comment = new Application_Model_Guestbook($form->getValues());
- $mapper = newApplication_Model_GuestbookMapper();
- $mapper->save($comment);
- return $this->_helper->redirector('index');
- }
- }
-
- $this->view->form = $form;
- }
- }
当然,我们也需要编辑视图脚本,编辑application/views/scripts/guestbook/sign.phtml:
- <!-- application/views/scripts/guestbook/sign.phtml -->
-
- Please use the form below to sign our guestbook!
-
- <?php
- $this->form->setAction($this->url());
- echo $this->form;
注
:
使表单看起来更美观
任何时候,没有人会对于这个表单的美观说好,无论如何,表单的外观是完全可定制的,具体请看在参考指南的装饰一节。
注:检查点:
Now browse to "http://localhost/guestbook/sign". You should see the following in your browser:
恭喜!