在我们开始之前,让我们思考这样一些问题:这些类将放在什么地方,我们如何找到他们?我们创建的默认项目实例化一个自动加载器(autoloader)。我们可以把其他的自动加载器附加到它身上,这样它知道到哪里找到不同的类。典型的,我们想让我们大量的 MVC 类在同一个树状结构下分组--在本例中,是 application/--而且大部分情况使用一个通用前缀。
Zend_Controller_Front 有一个模块(modules)的理念,这些模块是独立的迷你应用程序。模块模仿 zf 工具在 application/ 下建立的目录结构,而且在它们(模块)内部的类被假设以一个通用前缀(模块的名字)开头。application/ 本身是一个模块--默认(default)或者应用(application)模块。这样,我们想为在这个目录内的资源创建自动加载。
Zend_Application_Module_Autoloader 提供了必要的功能来把一个模块下大量的资源映射到合适的目录,同时提供了一套标准的命名机制。类的一个实例将在 bootstrap 对象初始化的过程中被默认创建,你的应用程序的 bootstrap 将会默认的使用模块前缀 Application。这样,我们的模型,表单以及表格类将会以 Application 这个类前缀开头。
现在,让我们想一下什么组成了一个留言本。典型的,他们是一些简单的列表,包含有留言,时间,同时,经常的,电子邮件地址。假设我们把它们存储在一个数据库中,我们可能还想为每一个条目分配一个独一无二的标识符(unique identifier)。我们可能还希望可以保存每一个条目,捕获每一个条目,同时检索所有的条目。这样,一个简单的留言本模型 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。他们同样将帮助确保只有列入我们白名单的属性才能被对象使用。
find() 和 fetch() 提供了捕获一个单独的条目或者全部条目的功能,而 save() 则负责把一个条目保存到数据库中。
从现在开始,我们可开始考虑建立我们的数据库。
首先,我们需要初始化我们的 Db 资源。如同 Layout 和 View 资源,我们可以为 Db 资源提供配置。我们可以使用 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 文件,在那你可以看到在对应的部分,添加了以下的内容:
; 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
在 Windows,你将在文件浏览器中创建这些目录,并设置权限,让任何人都可以写入这个目录。
至此,我们有了一个到数据库的连接,在我们的例子中,这是一个到位于我们 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");
同时,为了我们能有一些可以操作的数据,让我们创建一些信息来使我们的应用程序更有趣。
-
- -- 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 脚本:
- <?php
-
-
- // 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;
现在我们的留言本应用程序有了一整套运转正常的数据库和表格。下面的几个步骤将创建我们的应用程序代码。包括创建一个数据源(在我们的例子中,我们将使用 Zend_Db_Table),和一个数据映射器来把数据源链接到我们的模型。最后我们还将创建控制器,来和这个模型互动,以便显示存在的条目,同时加入新的条目。
我们将使用一个 Table Data Gateway 来链接到我们的数据源;Zend_Db_Table 提供了这个功能。开始之前,让我们创建一个以 Zend_Db_Table 为基础的表格类。正如我们在 layout 和 database adapter 做的,我们可以使用 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
-
-
-
- class Application_Model_DbTable_Guestbook extends Zend_Db_Table_Abstract
- {
-
- protected $_name = 'guestbook';
- }
注意类的前缀:Application_Model_DbTable。我们模块的类前缀,Application_,是第一个部分,然后是我们的组件,Model_DbTable;后面的部分是被映射到模块的 models/DbTable 目录。
当扩展 Zend_Db_Table 的时候,所需要提供的只是一个表格的名字,以及一个可选择的 primary key (如果它不是 id)。
现在让我们创建一个 Data Mapper。一个 Data Mapper 把一个对象映射到数据库。在我们的例子中,它将映射我们的模型,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 客户工具:
% zf create model GuestbookMapper
Creating a model at application/models/GuestbookMapper.php
Updating project profile '.zfproject.xml'
现在,编辑在 application/models/GuestbookMapper.php 文件中的 Application_Model_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 类,通过传递一个数据数组给构造函数或者一个 setOptions() 方法,来使它简单的填充这个模型。最后的模型类,在 application/models/Guestbook.php 文件中,看起来象这样:
- >?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;
- }
- }
最后,为了把这些元素链接起来,让我们创建一个 guestbook 控制器,它将列出数据库中现有的条目,同时可以添加新的条目进数据库。
为了创建一个新的控制器,使用 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'
这将创建一个新控制器,GuestbookController,在 application/controllers/GuestbookController.php 文件中,带有一个单独的行为方法,indexAction()。它同时为这个控制器创建一个视图脚本目录, application/views/scripts/guestbook/,内有与这个 index 行为相对应的视图脚本。
我们使用 index 行为作为一个登陆页面来浏览留言本条目。
现在,让我们更新基本的应用程序逻辑。对于一个 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,如下显示:
- <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>
注意:检查
现在浏览 http://localhost/guestbook,在你的浏览器中你应该能看到如下内容: