二、前期准备
2.1 配置apache的mod_rewrite
ZF中为了实现MVC模式,对整个应用的所有请求全部通过一个启动文件(一般是根目录下的index.php)来分配到不同的控制器进行处理(Zend_Controller_Front实现了Front Controller设计模式,所有的请求都通过front controller(前端控制器)并分发(dispatch)到不同的控制器来处理,分发的过程基于请求的URL。),因此需要对操作类URL进行URL重写,以下对apache的mod_rewrite模块进行设置。
先可以通过php提供的phpinfo()函数查看环境配置,通过Ctrl+F查找到“Loaded Modules”,其中列出了所有apache2handler已经开启的模块,如果里面包括“mod_rewrite”,则已经支持,不再需要继续设置。
如果没有开启“mod_rewrite”,则打开目录 您的apache安装目录“/apache/conf/” 下的 httpd.conf 文件,通过Ctrl+F查找到“LoadModule rewrite_module”,将前面的"#"号删除即可。如果没有查找到,则到“LoadModule” 区域,在最后一行加入“LoadModule rewrite_module modules/mod_rewrite.so”(必选独占一行),然后重启apache服务器即可。
2.2 让apache服务器支持".htaccess"
如何让自己的本地APACHE服务器支持".htaccess"呢?其实只要简单修改一下apache的httpd.conf设置就可以让APACHE支持.htaccess了。打开httpd.conf文件(在那里? APACHE目录的CONF目录里面),用文本编辑器打开后,查找
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
改为
<Directory />
Options FollowSymLinks
AllowOverride All
</Directory>
就可以了
2.3 建立开发目录
假设我们开发的目录为 C:/webroot/guestbook ,以下文件夹以 “./” 代替。我们采用ZF官方手册上推荐的结构。
./application 应用代码文件夹,按照MVC模式建立结构
controllers/ MVC控制器文件夹,放置ZF的控制器层文件
models/ MVC模型文件夹,放置ZF的模型层文件
views/ MVC视图文件夹,放置ZF的视图层文件
filters/ 视图辅助--过滤器类文件夹
helpers/ 视图辅助--视图辅助类文件夹
scripts/ 视图文件夹
./library ZF框架代码文件夹,将ZF安装包解压后的Zend文件夹放入其中
./public 供前端访问的文件夹,主要用于存放图片、js脚本和css样式单
images/ 图片文件夹
scripts/ 脚本文件夹
styles/ 样式表文件夹
也许有一些文件夹的解释大家还不是很明白,不着急,在以后编码过程中,会慢慢理解。
2.4 建立 “.htaccess” 文件
如果是在windows平台下,刚开始还真不知道怎么建立".htaccess"文件,因为这个文件实际上没有文件名,仅仅只有扩展名,通过普通方式是无法建立这个文件的,别着急,马上告诉你三种方法:
三种方法都是先建立一个htaccess.txt的文本文件(当然,这个文本文件的名字你可以随便取),然后有三种方式给这个文件重命名:(1)用记事本打开,点击文件--另存为,在文件名窗口输入".htaccess",注意是整个绿色部分,也就是包含英文引号,然后点击保存就行了。(2)进入cmd命令窗口,通过cd切换当刚建立htaccess.txt文件的文件夹,然后输入命令:rename htaccess.txt .htaccess ,然后点击键盘Enter键即可。(3)通过ftp连接htaccess.txt所在文件夹,通过ftp软件重命名。
那么我们须要建立几个“.htaccess” 文件,里面又输入什么内容呢?基本的原则是这样,根目录必须有一个,用于重定向(URl重写)所有的请求都会转到到index.php(交给前端控制器);"./application"文件夹下须要一个,用于拒绝所有针对该文件夹内容的直接访问(比如 http://localhost/application/models/User.php),这样做是因为所有访问请求必选通过前端控制器来分配访问,其次为了安全;“./library”文件夹下的“.htaccess”同前;"./public"文件夹下必选建一个,因为这个文件夹的文件全部是供前端直接访问的,因此须要撤销URl重写。以下是4个“./htaccess”文件的内容:
./.htaccess
RewriteEngine on
RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php
./application/.htaccess
deny from all
./library/.htaccess
deny from all
./public
RewriteEngine off
实际上,apache的url重写功能也可以直接在httpd.conf文件中进行设置,这里就不具体讲了,网上相关的教程也很多。
三、创建应用的入口文件./index.php
实际上,这个文件不一定须要命名为index.php,关键看你在url重写的时候把访问请求定向到了哪个文件,但是由于index.php是apache默认的欢迎页面,建议采用index.php命名。通过URL重写,应用所有的访问请求将定向到”./index.php“,在这个文件中,将初始化一个ZF的前端控制器,这个控制器将负责对所有的请求进行分配,即分配到指定的控制器,下面我们来看一下一个假设请求的访问流程(假设我们的请求是:http://localhost/register/form):
http://localhost/register/form---->[文件]./index.php---->[ZF类]Zend_Controller_Front实例---->[文件夹]./application/controllers---->[ZF控制器类]registerController---->[控制器类中定义的处理函数]formAction---->[视图层文件]./application/views/script/register/form.phtml---->返回结果
以下是ZF官方手册中对控制器的解释
Zend_Controller是Zend Framework的MVC体系的核心部份。MVC指Model-View-Controller,是一个用于分离应用逻辑和表现逻辑的设计模式。Zend_Controller_Front实现了Front Controller设计模式,所有的请求都通过front controller(前端控制器)并分发(dispatch)到不同的控制器来处理,分发的过程基于请求的URL。
Zend_Controller体系具有可扩展性,可以通过继承已有的类或者自己写个新的类来实现各种接口和抽象类,也可以编写插件或者助手类(helper)来增强系统的功能。
在讨论控制器之前,你应该先理解Zend Framework是如何处理HTTP请求的。默认情况下,URL的第一个部份会映射到一个控制器,第二个部份则映射到控制器类中的Action(即控制器类内部的一个方法)。例如:URLhttp://framework.zend.com/roadmap/components,其服务器路径为/roadmap/components,则会映射到roadmap控制器和components Action。如果不存在action,则会调用index这个action。如果控制器不存在,则会自动调用index控制器。(按照Apache的命名惯例,自动映射到DirectoryIndex文件)
接下来,Zend_Controller的dispatcher会根据控制器的名称找到具体的控制器类。通常它会把控制器名称加上Controller。因此,上例中roadmap控制器与类RoadmapController相对应。
类似地,action会映射到控制器类中的一个类方法。默认情下,会被转成小写字母,然后加上Action字符串。因此,上例中components这个action与 componentsAction相对应。最终我们访问URL调用的是RoadmapController->componentsAction()。
=DIV========================================
下面通过代码来解释index.php的创建:
<?php
// 全局处理,主要包括报错级别,时区
error_reporting(E_ALL|E_STRICT); //调试时可以设置为E_ALL
date_default_timezone_set('Etc/GMT-8'); //php5需要设置时区,注意GMT后面的-8符合与真实的是相反的
//php_ini的全局引用路径等的设置,这里最重要的是将我们建立的./library/目录加入到了全局引用路径中,这样才能访问Zend框架提供的功能
set_include_path('.'.PATH_SEPARATOR.'./library'.PATH_SEPARATOR.'./application/models/'.PATH_SEPARATOR.get_include_path());
//引用ZF的类加载类,用于通过ZF更加简洁地引用需要的ZF其它类文件,当然这个文件就需要通过传统方式引用了
include "Zend/Loader.php";
// 加载ZF应用中常用的ZF类
Zend_Loader::loadClass('Zend_Controller_Front'); //必须引用,控制器类
Zend_Loader::loadClass('Zend_Config_Ini'); //用于读取ini类型的配置文件
Zend_Loader::loadClass('Zend_Registry'); //用于注册对象供整个应用空间使用,如数据库链接对象
Zend_Loader::loadClass('Zend_Db'); //数据库类
Zend_Loader::loadClass('Zend_Db_Table'); //数据库扩展类,用于与表相关的操作
// 加载配置。主要是数据库相关的配置,可以分多个组,便于更改,建议使用,也可以直接在这里配置
$config = new Zend_Config_Ini('./application/config.ini', 'general');
$registry = Zend_Registry::getInstance();
$registry->set('config', $config);
//初始化数据库对象,并注册到整个应用空间
$db = Zend_Db::factory($config->db->adapter,$config->db->config->toArray());
$db->query('SET NAMES gbk');
Zend_Db_Table::setDefaultAdapter($db);
$registry->set('db',$db);
// 加载控制器,建立前端控制器对象,并设置控制器类路径
$frontController = Zend_Controller_Front::getInstance();
$frontController->throwExceptions(true);
$frontController->setBaseUrl('/guestbook'); //主要在应用不在根目录的时候使用
$frontController->setControllerDirectory('./application/controllers');
// 运行,这里进行了错误处理,当ZF找不到想要的控制器时输出ZF的错误信息,你也可以进行自定义的处理
try{
$frontController->dispatch();
}catch(Zend_Controller_Dispatcher_Exception $e){
echo $e->getMessage();die;
}
?>
四、设计数据库
本篇主要是设计Mysql数据库,提及了两个数据库设计中比较重要的问题,并推荐了3篇相关的文章,希望对大家有所帮助。
4.1 字段类型
设计数据库,字段类型的选择是非常重要的,原则是尽量选择占用空间最小的数据类型,尽量采用确定长度的数据类型(如CHAR)而不用变长的数据 类型(如VARCHAR)。例如表`zf_guestbook`的字段`mana_id`,长度仅仅设为(5),实际可以更小,就是考虑到留言本的管理员 数量很有限,其编号不会增长到那么大,哪怕你经常删除了重新添加;又例如表`zf_guestbook`的其它两个字段
都设为CHAR类型,而不是VARCHAR类型,也是因为在本应用中我们可以确认其最大长度,故使用定长的数据类型(当然CHAR类型字段会占用固定的储存空间,如果你设置的过大而实际上使用的比例确很小就会造成存储空间的浪费,这就要根据具体的应用来权衡了)。
4.2 建立索引
一个大型应用,数据库索引的设计和优化是至关重要的,学问就大了去了,当然也有一些基本的原则,相信大家看了《Mysql索引分析和优化》之后就会有个比较感性的认识了。
虽然本教程的数据库应用非常简单,但是我一向坚持按照最优的方式来设计和制作东西,所以再小也要考虑的全面些,无奈我对数据库的设计也只是入门级别的,就不敢班门弄斧了,所以在本篇之前转载了几篇觉得很好的数据库设计相关的文章,大家有时间可以仔细看下:
Mysql索引分析和优化
高效的MySQL数据库应用原则
数据库设计方法、规范与技巧
通常,在数据库、表、字段命名上我喜欢用前缀(通过下划线分隔),也建议大家这样做,这样做有很多好处,比如便于标识,形成统一的风格,数据库名称如果前缀相同在phpmyadmin管理的时候它会自动按照前缀进行分组,不同的表前缀可以使用同一个数据库支持多个相同应用,等等。
====以下是通过phpmyadmin导出的Sql代码=========================================================
-- phpMyAdmin SQL Dump
-- version 2.10.1
-- http://www.phpmyadmin.net
--
-- 主机: localhost
-- 生成日期: 2007 年 11 月 06 日 12:43
-- 服务器版本: 5.0.41
-- PHP 版本: 5.2.2
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
--
-- 数据库: `zf_guestbook`
--
CREATE DATABASE `zf_guestbook` DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
-- --------------------------------------------------------
USE `zf_guestbook`;
--
-- 表的结构 `gb_manager` 管理员表,用于存储留言管理员的帐号信息
--
DROP TABLE IF EXISTS `gb_manager`;
CREATE TABLE IF NOT EXISTS `gb_manager` (
`mana_id` tinyint(5) NOT NULL auto_increment,
`mana_name` char(20) collate utf8_unicode_ci NOT NULL,
`mana_pass` char(32) collate utf8_unicode_ci NOT NULL,
PRIMARY KEY (`mana_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
--
-- 表的结构 `gb_message` 留言表,用于存储访客留言,其中 `mess_ip`是为了跟踪留言人的地区信息
--
DROP TABLE IF EXISTS `gb_message`;
CREATE TABLE IF NOT EXISTS `gb_message` (
`mess_id` int(11) NOT NULL auto_increment,
`mess_name` char(50) collate utf8_unicode_ci NOT NULL,
`mess_content` text collate utf8_unicode_ci NOT NULL,
`mess_addtime` int(11) NOT NULL,
`mess_ip` int(10) NOT NULL,
PRIMARY KEY (`mess_id`),
KEY `mess_addtime` (`mess_addtime`,`mess_ip`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;
====SQL代码结束=======================================================
4.3 配置文件编写
下面我们来写数据库连接的配置文件,我们采用ZF推荐的方式,ini类型的配置文件,在本教程《实战学习 Zend Framework 之--留言本[03]--创建应用的入口文件./index.php》中讲到了在index.php中读取ini配置文件的方法,再将相关代码贴过来下:
// 加载配置。主要是数据库相关的配置,可以分多个组,便于更改,建议使用,也可以直接在这里配置
$config = new Zend_Config_Ini('./application/config.ini', 'general');
$registry = Zend_Registry::getInstance();
$registry->set('config', $config);
新建一个config.ini的文本文件,存放在application文件夹下(这个文件夹通过.htaccess禁止了直接http访问请求的)。内容如下:
[general]
db.adapter = PDO_MYSQL
db.config.host = localhost
db.config.username = gbuser
db.config.password = guestbook
db.config.dbname = zf_guestbook
当然,你也可以把数据库配置直接使用php代码写在index文件里面,但是推荐采用这种方式,这样做有几种好处,首先通过配置代替编程,更加容易更改你的数据库配置,数据库相关的信息变了只用改下这个文本文件,而不用动php代码了;其次,通过在最前面"[ ]"中指定配置组名称,可以在一个配置文件里面采用多种数据库配置,便于大型应用对数据库变换的要求。
五、显示留言列表
在留言本这个应用中,我们设计以下功能:留言列表(分页)、新增留言、删除(管理员登陆后)。首先我们来看显示留言列表这个功能。
5.1 概括梳理
经过前面的学习,应该对ZF现MVC的套路有了一些了解了,那么要显示留言列表我们的程序涉及到哪些方面,如果通过ZF提高的MVC框架来实现呢?当访问留言本应用时,我们在首页显示留言列表,并在页面最下方显示新增留言的表单。既然时首页,那么我们的控制器就命名为IndexController.php,当然是存放在/application/controllers目录下面了;紧接着是模型层,既然是显示留言,肯定是gb_message表,那么我们把模型层命名为Message.php(与表明相关联,便于识别),存放在/application/models目录下;最后是视图层,那就是我们的模板页面了,模板页面推荐与控制器同名,即index.phtml,存放在/application/views/scripts/index目录下(这里需要在scripts目录下根据控制器名称建立子目录,便于通过ZF无路径调用并更好的管理模板文件)。现在我们来一起梳理下相关的文件和目录:
/application/controllers/IndexController.php 控制器层
/application/models/Message.php 模型层
/application/views/scripts/index 视图层
以下部分当然是编写三个文件的源代码了,我将会把源代码详细列下,并配上详细说明,有问题的可以留言!
5.2 代码及注释
IndexController.php
<?php
//控制器必须继承自Zend_Controller_Action类
class IndexController extends Zend_Controller_Action
{
//初始化函数,相当于PHP5类中的construct函数,会在类初始化时自动调用
function init()
{
//初始化视图层,并获取应用根路径赋值给视图层
$this->initView();
$this->view->baseUrl = $this->_request->getBaseUrl();
//加载模型类,就是数据库操作类,放在init函数中加载的好处是不用在具体的xxxAction函数中分别加载
Zend_Loader::loadClass('Message');
}
//Action方法,负责执行具体的任务
function indexAction()
{
//定义每页显示的记录条数
$psize = 10;
//获取当前页码,如果不为正整数则重新赋值为1
$page = (int)$this->_request->getParam('page'); //注意,这就是ZF中获取get参数的方法,下面会总结
if($page<1){$page = 1;}
//初始化Message类并获取留言分页列表数据。listMessage函数接收两个参数,分别是每页记录数和当前页码
$message = new Message();
$count = $message->countAll();
$list = $message->listMessage($psize, $page);
//生成分页页码链接,为了简化起见,将所有页码一股脑儿显示出来
$pstr = "";
$maxpage = ceil($count/$psize);
for($i=1; $i<=$maxpage; $i++)
{
if ($page==$i)
{
$pstr .= "<b>{$i}</b>";
}
else
{
$pstr .= "<a href='/index/page/{$i}>{$i}</a>";
}
}
//向视图层赋值并输出视图
$this->view->title = "留言本@ZF Powered"; //将页面标题(html中的title标签)赋值给视图层
$this->view->list = $list;
$this->view->page = $pstr;
$this->render(); //输出视图
}
//析构函数,注销不再需要的资源,可选(PHP5会自动回收)
function __destruct()
{
unset($this->view);
}
}
?>
*****************IndexController涉及到的知识点总结*********************
(1)init函数,控制器的初始化函数,相当于类的构造函数,会在初始化是自动调用,因此最好将整个控制器公用的一些资源放在这个函数中,比如一些公用类的加载和初始化、全局的变量等。
(2)Action函数,实际上是一些形如xxxAction的函数,每一个Action函数负责执行一个具体的请求(或者说任务),这个函数名称中的xxx就是由 http://localhost/xxx 中的xxx,不明白的可以看看ZF的URL解析规则,前面有讲到。
(3)ZF获取参数的方法:get参数==$this->_request->getParam('xxx');,post参数==$this->_request->getPost('xxx');,$this当然是指向当前控制器的。
(4)向视图层赋值。如果在init函数中执行了$this->initView();,则直接调用$this->view->变量名=值;就行了。实际上initView()就是建立一个新的视图实例并赋值给控制器的变量view(当然还有其它相关的操作)。
(5)视图输出。如果严格按照ZF推荐的方式进行目录安排和命名,则显示视图只需调用$this->render();,这样ZF会自动在/application/views/scripts/目录下查找和当前控制器名称同名的目录下的和当前action同名的.phtml文件作为模板显示输出。
Message.php
<?php
//模型类,必须继承自Zend_Db_Table
class Message extends Zend_Db_Table
{
//默认情况下,zend_db_table类会将其类名当作数据库中表名,不是则重新定义,详见ZF手册
protected $_name = 'gb_message';
//重构主键,本例中不需使用,ZF默认表的主键为id,如果不是则重新定义,便于Db类的一些便捷操作,详见ZF手册
protected $_primary = 'mess_id';
//构造函数,获取在入口文件中注册的数据库连接对象
function __construct()
{
parent::__construct();
$this->db = Zend_Registry::get('db');
}
//获取数据总量供分页使用,以下用到Db::select对象,详细请参看ZF手册
function countAll()
{
$select = $this->db->select();
$select->from($this->_name, 'mess_id');
$count = count($this->db->fetchAll($select));
if($count)
{
return $count;
}
return 0;
}
//获取分页后数据,注意最好给参数制定默认值
function listMessage($page=1,$count=20)
{
$select = $this->db->select();
//制定表和获取字段列表
$select->from($this->_name,'*');
//制定排序依据
$select->order('mess_addtime DESC');
//分页获取数据并返回
$select->limitPage($page,$count);
return $this->db->fetchAll($select);
}
//删除留言
function delMessage($mess_id)
{
//使用$this->db->quoteInto来生成sql语句防止sql注入
$where = $this->db->quoteInto('mess_id =?',$mess_id);
$this->db->delete($this->_name,$where);
}
}
?>
*****************Message涉及到的知识点总结*********************
(1)ZF框架Db类表名、主键等相关命名规则和操作,比较对,在本教程中就不再赘述,自己去看ZF手册吧,讲的很清楚。
(2)Db::select对象,为我们提高了一种不受数据库约束构建select的sql语句的工具,详见ZF手册9.2。
(3)ZF提高的防sql注入的方法,安全一定要注意,ZF提供了quote方法和quoteInto方法来帮助构造sql语句,详细请看下ZF手册“第 9 章 Zend_Db”。
PS:大家不要嫌我懒,老是说“见ZF手册”,实际上是因为手册上已经讲解的很详细了,在这里就不需要赘述了,而且本教程是实例教程,偏向于实践,条条款款的东西就少写了。
index.phtml
//be continue 今天累了,明天接着写吧