zf框架的思想及学习总结

时间:2023-03-09 19:32:09
zf框架的思想及学习总结

在Php的配置文件中可以设置日志文件

dos命令进入文件夹,然后利用命令:>zf.bat create project d:/hspzf
这样就可以在d盘进行创建项目文件了;
然后需要把框架的Zend库文件引入到整个lib文件中,这样才能运行起来,否则会报错!

可以设置四个地方来跟踪整个运行的顺序:
1.首先是:总控就是public中的Index文件中设置:
file_put_contents("d:/mylog.txt",__FILE__.date('Y-m-d H:i:s')."\r\n",FILE_APPEND);
2.第二个是:
引导文件中设置:
file_put_contents("d:/mylog.txt",__FILE__.date('Y-m-d H:i:s')."init..\r\n",FILE_APPEND);
这个设置的名字稍微有差异以示区别
3.在Index控制器正宗的初始化方法中设置:
file_put_contents("d:/mylog.txt",__FILE__.date('Y-m-d H:i:s')."init..\r\n",FILE_APPEND);
4.在index控制器的index方法中设置:
file_put_contents("d:/mylog.txt",__FILE__.date('Y-m-d H:i:s')."index Action\r\n",FILE_APPEND);
最后在日志文件中真的会出现四条记录
这四条记录显现了整个框架运作的顺序;

数据库的建立和插入数据:
create database testzf;
use testzf;
set names gbk;
在项目中加一个Model文件作为Mvc的model名:Message.php
<?php
//这个类与数据库中某张表是对应关系,通过Message对象实例可以完成对
//该表的crud操作
class Message extends Zend_Db_Table{
protected $_name='message';
protected $_primary='message_id';
}
这与数据库中的某张表是对应的,需要指定表的名字和主键
控制器如果需要从数据库中获取数据,那么可以引入这个model对应的文件,然后将在下面新new一个对象
利用对象的方法,来获取相应的数据,将数据传递给视图,那么这些数据就可以显示了
在配置文件的ini文件中可以去掉捕获异常为0的下面的内容,然后加上下面的内容:
[mysql]
db.adapter=PDO_MYSQL
db.params.host=localhost
db.params.username=root
db.params.password=root
db.params.dbname=testzf
表示连接数据库并且设置主机,用户名,密码,数据库名;

这个是在配置文件中设置数据库的连接参数
然后在引导文件中创建对象将一些配置信息进行加载进来:
$url=constant("APPLICATION_PATH").DIRECTORY_SEPARATOR.'configs'.DIRECTORY_SEPARATOR.'application.ini';

$dbconfig=new Zend_Config_Ini($url,"mysql");

$db=Zend_Db::factory( $dbconfig->db);
//var_dump($db);
// exit();

$db->query('SET NAMES UTF8');

Zend_Db_Table::setDefaultAdapter($db);

然后在控制器中引入model文件,然后进行创建模型获取参数,

error_log = "D:/php_error.log"
这个可以在php.ini文件中设置一下就可以获取系统的错误信息了;

可以增加一个重写的文件来规定项目的入口文件
.htaccess
RewriteEngine On
Rewrite ^.*$ enter.php这里就设置项目的入口文件
.表示除了换行的任意字符,*号表示任意多个字符,或者0个,然后并且以这个结尾
表示匹配任意字符

方法一定要小写,不然就会报错,并且即使路径大写也没有用
4:42

本来连接数据库的配置信息是可以写在启动文件中,也可以写控制中的初始化方法中,但是为了更加得简洁
那么我们可以创建一个父类的控制器,就是如果需要连接数据库,那么我们用这个控制器继承这个父类控制器

<?php
//抽象出一个父类,专门供其他的controller的来继承
class BaseController extends Zend_Controller_Action{
public function init()
{
$url=constant("APPLICATION_PATH").DIRECTORY_SEPARATOR.'configs'.DIRECTORY_SEPARATOR.'application.ini';
$dbconfig=new Zend_Config_Ini($url,"mysql");
$db=Zend_Db::factory( $dbconfig->db);
$db->query('SET NAMES UTF8');
Zend_Db_Table::setDefaultAdapter($db);
}
}
?>

如果不需要连接数据库的就直接继承Zend_Con..这个控制器就行
值得注意的是,如果继承了Base控制器的控制器不需要写Init方法,因为写了就会覆盖父类的控制器
的方法,其实这样设计是更加的灵活的!

下面我们来设置一个投票系统:
根据需求我们应该有三张表
创建数据库votedb;命令是:create databases votedb;
选项表item
create table item(
id bigint unsigned primary key auto_increment,
name varchar(64)not null,
description varchar(128) not null,
vote_count bigint unsigned)engine MyISAM
上面的是创建的一个选项表,就是用户投票的选项,这些选项肯定具备
基本的id,作为主键,然后又名字,比如苹果,西瓜,然后是描述
就是对苹果的描述
创建一个数据库:create database votedb;
记住前面不能少了database
然后use votedb就可以创建表了

--投票的日志表vote_log
第二个参数是varchar由于存在Ipv6所以不能确定长度所以设置20位
第三个利用BigInt来存储时间撮这样计算起来是更加容易的
这里没有做外键,因为是逻辑关系可以在程序中控制,这个存在这样一个
字段是因为需要Ip与对应的项目对应,不能说一个用户只能对一个商品进行
投票
create table vote_log(
id bigint unsigned primary key auto_increment,
ip varchar(20)not null,
vote_date bigint not null,
item_id bigint not null
)engine MyISAM

--过滤ip的表filter
下面的过滤一些捣乱的Ip地址防止自己的网站被攻击了
create table filter(
id bigint unsigned primary key auto_increment,
ip varchar(20)) engine MyISAM

下面需要创建一个后台管理的控制器用来专门控制后台的投票选项增加
<?php
require_once 'BaseController.php';
class AdminController extends BaseController
{
public function indexAction()
{
// action body
}
//进入到增加选项的页面
public function additemuiAction(){
$this->render('additemui');
/*$this->_forward();
$this->_redirect();*/
}
//完成一个添加的任务
public function additemAction(){
//获取用户输入的内容
$name=$this->getRequest()->getParam('name');
$description=$this->getRequest()->getParam('description');
$vote_count=$this->getRequest()->getParam('vote_count');
echo $name.'==='.$description."=====".$vote_count;
exit();
}

}
然后创建对应的视图文件来显示视图文件,就可以了显示内容
但是现在我们还需要创建对应的表模型来操作数据库,我们可以查手册
搜索Zend_Db_Table然后搜索知道需要设置表名,然后主键如果是id的话
就可以不指定了
因为在总控已经初始化了这个常量APPLICATION_PATH所以在以后的代码是可以
用的,

下面的就是创建一个模型然后添加数据到数据库,本来出错了最后查找原因是现实界面是
php文件所以显示出错,应该文件是.phtml的

投票的日志就是有字段ip,日期,选项,都是笛卡尔积组成的一一对应关系,
还有一张表示选项的表,就是有字段,选项,描述,投票数量,然后每次投票成功都会更新,
首先根据item_id和ip地址可以获得投票日志中的信息,看投过了几次,然后判断次数,如果合法
就可以增加一个投票的日志,然后再更新选项的投票的票数

本来常规的想法是把验证和获取购物大厅的数据放在一个控制器的,但是存在一个问题,比如从购物大厅,点击
超链接然后跳转到购物车,查看下购物车中加入了哪些物品,这是很常见,但是又需要回到购物大厅,那该怎么办
如果直接跳转到这个页面没有走控制器,那肯定是不行,的,因为没有走控制器,那么这个数据就没有了。所以需要走
控制器,但是这个控制器中还有一个验证,所以不好,不可能每次回来还需要输入密码
所以需要做成两个控制器,一个验证用的,一个获取数据的,获取验证session是否登录

控制器链:就是多个控制器链,就是多个控制器协同工作,即使是全局成功页面或者错误页面还是需要一个控制器
所以每个视图文件都需要走控制器,然后再在admin控制器中给变量赋值在成功页面是可以收得到的:
$this->view->info='增加过滤ip成功!';因为他的路径是admin-->Globs控制器—>ok页面
然后接受值:<?=$this->info?>

APPLICATION_PATH就是D:\web\votesys\application就是这个常量

在服务器的代码对用户提交的数据进行验证,如果提交的名字为空,那么就让它走全局的一个控制器
这个控制器可以显示错误信息进行提示,这是第一次见到,之前的提示都是js代码的提示错误信息;

zf中的render是指在本控制器中直接显示视图
而_forword是可以在本项目中的任意的控制之间跳转但是不能跳转到外网
_redirect可以跳转到外网的网站,还具有_forword这个功能就是在任意的控制之间跳转
投票之后不能及时的刷新页面可以采用这样的一句话:window.location.href='/index/index';
就是重新定向到这个index界面。然后这个投票的结果就会刷新了

创建新的数据库的时候最好在创建表的时候设置编码方式,比如设置set names gbk/utf8;这样在phpadmin中
自动设置这个编码方式为utf8,然后编程的文件都设置成utf8,这样中文就不会出错了,注意这期间根本不需要设置
文件代码的编码方式、

创建表的时候,开始可以不设置编码方式,然后插入数据之前可以设置下编码方式set names gbk;
然后再插入数据就可以了。
之前是创建表的时候,设置下编码方式为utf8,然后创建表,然后在设置编码方式为utf8,然后再插入数据
结果select * from student,结果发现中文还是不能显示,但是也不是显示问号,只是空白,所以这种方式不行
只能使用第一种方式进行设置编码方式,

数据库设计到多表查询的设计:
比如student表和课程表course这两个表存在多对多的关联关系
一种常见的错误设计student表的设计
student :
id name courseid grade coursename
很容易想到把课程的id 和成绩之类的设计到同一个表中,因为这种想法只想到了
这样设计比较好查询,因为这样设计直接可以通过一张表中的学生的Id就可以知道
他的选课的id设计还知道,选课的成绩,但是这样是不对,我们应该是知道3nf
其实质就是两个实体学生,课程存在多对多的关系时应该通过第三张关系表进行查询进行
关联起来,为什么因为,因为普通的设计会产生很多冗余,比如学生的id与课程产生的笛卡尔积
就是同一个学生的id,姓名或重复很多次,就是这样的冗余很不好,所以我们应该尽量就一个实体
的信息写在一个表,另一个实体写在另一个表,这样,中间只是通过他们的主键做一个关联的
表,这样即使产生的笛卡尔积,只是id进行关联必要的信息,不会重复附属的信息的;这就是3NF的精髓

所以这样三张表应该是这样设计的
student !!stucourse !! course

id name !!id sid cid grade !!id name credit

设计多表查询,就是学生的选课系统,
根据上面的四个条件进行查询,
$where='1=1';//select * from student where 1=1 order by sage limit 0,3;
$order='sage';
$count=3;
$offset=0;

$studentModel=new Student();

$res=$studentModel->fetchAll($where,$order,$count,$offset)->toArray();

现在需要查询计算机的系的:所以需要设置下where条件, $where="sdept='计算机系'";

现在使用sql的适配器进行注入的方式添加参数,因为一般查询条件可能是有用户输入进来的:
//创建数据库适配器
$studentModel=new Student();
$db=$studentModel->getAdapter();
$where=$db->quoteInto("sdept=?",'计算机系');

其实可以看出$db其实是$studentModel的一个方法然后获得了适配器,是一个对象,也就是说,模型产生
一个适配器对象,然后适配器对象调用自己的方法quoteInto以注入的方式获取用户输入的条件。本质上
适配器的这个注入的方式就是替换或者转移这个查询条件中的非法字符;然后返回一个where条件,所以总的
说来这个条件就是studentModel的内部进行处理了下,然后传递给Model

最后看下内部是怎么处理这个转移字符的:
//****显示计算机前三个学生的按照年龄排序,(考虑sql注入)
//创建数据库适配器
$db=$studentModel->getAdapter();
$where=$db->quoteInto("sdept=?",'计算"机系');

echo 'tt='.$where;
exit();
经过试验可以看出这个如果where条件中存在"等非法字符就可以转义就是在前面加上一个\表示经过了
转义

但是有时是需要注入两个参数该怎么办,按照惯性思维想到应该注入一个数组,但是结果是把数组中
的两个结果当作是一个结果是分别注入到两个参数中,
最后按照最笨的方法:
$db=$studentModel->getAdapter();
$where=$db->quoteInto("sdept=?",'计算机系').$db->quoteInto("AND ssex=?",'男');
//$res=$studentModel->fetchAll($where,$order,$count,$offset)->toArray();
echo $where;
exit();

数据模型产生一个适配器,然后适配器调用查询语句,对数据库进行查询,获得结果传递给界面

下面的是用适配器产生数据,并且增加了where条件
//取出所有的学生的名字和姓名
$db=$studentModel->getAdapter();
$sql=$db->quoteInto("select sname,ssex from student where ssex=?",'M');
//如果我们使用适配器去完成查询,则返回的数据是数组
$res=$db->query($sql)->fetchAll();
echo '<h>取出所有的学生的姓名和性别</h>';
echo '<pre>';
print_r($res);
echo '</pre>';

更新数据库中的数据的就用update方法来操作:

/* echo '<h1>修改课程100->99</h1>';
$set=array(
'ccredit'=>99
);
$where="cid='55'";
$course=new Course();
$course->update($set,$where);*/
只需要设置一个数组set和一个where条件,这里的where没有考虑注入,因为是写死的,如果考虑注入的问题
那么就可以考虑产生一个适配器,然后通过适配器的对where条件进行处理下,就可以了
下面如果是删除的话更加简单只需要指定下,$where条件就可以了
然后 echo '<h1>删除记录</h1>';
$course=new Course();
$where="cid='55'";
$course->delete($where);

删除就更加简单:

只有主键才能能够用到find这个方法:
查询学生表中有多少个系,去掉其中重复的:用关键字distinct
select distinct sdept from student;
分组查询:
$res=$db->query("select count(sdept),sdept from student group by sdept

having count(sdept)>1")->fetchAll();

查询每个系中的人数大于1的并且显示系名

这个就是还是查询一张表student只不过是按照系名进行分组,sdept是一个字段

后面的having表示对某个字段进行过滤

查询那些女生不超过10个的系的思路

首先思考如何查询出每个系中的女生的人数

值得注意的是分组查询中既然是分组查询,那么相当于每个组是一个查询单元,相当于每个组是一个查询记录,那么就可以使用聚集函数count,averg对每个分组里面的

内容进行统计。

select count(*),sdept from student where ssex='F' group by sdept;

然后思考对女生的个数起个别名,并且通过having进行过滤

select count(*) girls,sdept from student where ssex='F' group by sdept
having girls>200;
可以通过这个语句进行查询;

select count(*),'计算机系' from student where sdept='计算机系'
上面这条语句如果不能写select sdept来显示计算机系,因为这里的查询计算机系是
按照计算机系这个来查询,计算机系好像是一个父亲似的

注意查询的字段总是并列关系,就是都是就某一个单元而言的,count(*)是以计算机系所有的记录为一个单元,而如果是select sdept就是以student表的每一条记录为单元的

所以查询需要用计算机系,这样查询字段是并列关系。

select count(*),sdept from student where ssex='F' group by sdept;
但是这条语句是可以select sdept ,因为这里查询的sdept还是一个分组的信息
总的父亲相当于是一个所有的学生的集合,相当于是一个无限分类中的0

题目:显示各科考试不及格学生的名字,科目和分数
思路:首先考虑这个题目需要用到哪些表
名字用到student表,科目用到course表,分数用到选课表studentCourse表

然后需要查询三个首先在前面写三个问号表示需要查询三个字段,但是用到三张表可以预测
将来肯定会用到表别名所以暂时用?表示
select ??? from student s,course c,studcourse sc where sc.grade<60

可以看出现在后面的where条件已经对查询的结果做一个限制就是不及格的,但是现在如果直接
select *那么就会出现笛卡尔积,就会出现N*M*L条记录
所以现在升级为这个
select s.sname,c.cname,sc.grade from student s,course c,studcourse sc where sc.grade<60 AND sc.cid=c.cid;

查询出每个课程不及格的人数:
select count(*),cid from studcourse where grade<60 group by cid;

就是查询stucourse选课表然后根据课程的编号进行分组并且利用count进行
对每个组的数量进行统计,然后还显示cid

计算各科目不及格的学生的数量:
首先不及格的肯定是在选课表中的,但是只能够取到cid所以还需要通过
course这个表来查询出这个课程的名字”

select t1.*,c.cname from course c,(select count(*),cid from studcourse where grade<60 group by cid) t1
where t1.cid=c.cid;

select * from student where sid in (select sid from studcourse where cid=21)

select sid from studcourse where cid=21

1.显示凌青霞的选修的所有的课程ID号
select ??? from (select sc.grade,sc.cid from studcourse sc where sid=(select sid from student where sname='林青霞'));
上面是两层嵌套,就是一个表的信息查询作为一个中间表,然后中间表作为下一个表的查询来源
select ??? from (select sc.grade,sc.cid from studcourse sc where sid=(select sid from student where sname='林青霞')) temp,course c where temp.cid=c.cid;

上面就是在上面的基础上面的后面,添加一个表格,course,然后通过where条件进行限制下,
select ‘林青霞’,c.cname,temp.grade from (select sc.grade,sc.cid from studcourse sc where sid=(select sid from student where sname='林青霞')) temp,course c where temp.cid=c.cid;

注意每个Php文件实际上是一个类文件,但是需要注意的是文件里面写的类名与文件名要保持一致,哪怕S都不能少!

传递界面上的变量的时候需要注意就是注意{与php代码之间应该留有空格,这样才不会报错,不然会将两者看作是整体,无法解析
当我们需要从Session中取得用户名显示的时候,我们最好不要从视图界面中利用php代码取得,这样不好,因为视图文件是专门显示变量
用的,我们需要在控制器中取得,然后分配到视图中,然后视图中显示变量!其实网站系统的漏洞都是存在与接口中,因为用户只有
权利提交数据到服务器,然后服务器返回数据给用户,普通用户是没有权利不通过提交代码不通过接口让服务器解析的;
通过session存储登录信息的时候,从数据库中查询的user这是一个二维数组因为是fetchAll返回的数据,当然从理论上来将一个用户和密码
作为where条件查询,返回的用户记录只有一条,也就说user是一个只有一个长度的二位数组,但是在这一个长度的变量又是一个数组的形式
存储用户各种信息,用户名和密码,邮件等等;
所以登录的时候应该这样存储用户的信息:
session_start();
$_SESSION['loginuser']=$loginuser[0];
然后登陆之后的界面显示用户名又是在控制器中这样取得用户的:
session_start();
$this->view->loginuser=$_SESSION['loginuser'];

/**
* Class MyCart
* 这个类的一个对象实例表示一个购物车对象
* 购物车增删改差应该设计一些方法,这些方法本身可以写在控制器中不在表中封装方法,这样很麻烦
* 这样以后每次计算总价回每次去转下控制器
* 这就相当于以前的自己写mvc的模型和数据库的hlper的结合体

购物车中如果把一个用户购买的商品放在一个记录中不行,不好记录
在控制器中不光能够控制跳转到成功页面的成功提示,而且还能够
传递跳转的地址;
$this->view->info='添加商品ok';
$this->view->gourl="/hall/gohallui";
$this->_forward('ok','global');
下面成功页面拿到取得提示:
<script type="text/javascript">
alert('<?=$this->info?>');
//history.back();
window.location.href="<?=$this->gourl ?>";
</script>

input输入可以设置value渲染成按钮还是很好看的如下:
<td colspan="4"><input type="button" onclick="goMyCart();" value="查看购物车"/></td>

控制器中方法不能出现大写字母,不能才能驼峰命名法,凡是表示方法名字都是用小写字母:
购物车中查询该用户已经购买的商品:
select b.id,b.name,b.price,m.publishHouse from book b,mycart m where b.id=m.bookid;
购物车表和Book表通过bookid进行连接

shopping控制器中存在方法1显示购物车方法2增加商品
那么两个方法中都会创建购物车这个对象,所以会创建两个购物车对象,这样不划算可以采用单例模式
就是将创建的购物车放在session中!
购物车里面的Model对象定义一个属性作为总得价格,不要老是想到数组来解决问题
那个返回的购物车的内容在显示购物车的具体信息时只只能返回一个结果res

php_flag display_errors

on
php_value error_reporting 2039

try{
$this->delete("userid=$userId AND bookid=$id");

}catch (Exception $e){
echo $e->getMessage();
}

这是PHp中抛出异常的方法来检测

function delProduct($userId,$id){
if($this->delete("userid=$userId AND bookid=$id")>0) {
return true;
}else{
return false;
}
}
这个上面是之前代码写错了,写成了userid=$userId AND booid=$id就是bookid写成乐booid结果写错了,找错误找了好半天!

对于更新购物车里面商品的数量存在一个问题就是商品的信息是一个循环产生的,所以问题就来了
怎么设置每一个商品的name使得每一个商品的id的name是不同的,而且value也是不同,有的人的想法
就是进行拼接每个商品的Id作为name,但是这样在后台也不太好获取里面的值,那么就可以将商品的name
设置成

<input type='hidden' name='bookids[]' value=<?=$book['id']?> />
<td><input type="text" name='booknums[]' value='<?=$book['nums']?>' ></td>
这样在前台产生的代码就是:
<tr>
<td>4</td>
<td>JSP编程指南</td>
<td>10</td>
<td>电子工业出版社</td>
<input type='hidden' name='bookids[]' value=4 />
<td><input type="text" name='booknums[]' value='1' ></td>
<td><a href='/shopping/delproduct?id=4'>是否删除</a></td>
</tr>
<tr>
<td>2</td>
<td>php Web 服务开发</td>
<td>45</td>
<td>电子工业出版社</td>
<input type='hidden' name='bookids[]' value=2 />
<td><input type="text" name='booknums[]' value='1' ></td>
<td><a href='/shopping/delproduct?id=2'>是否删除</a></td>
</tr>

-------------------------------------
那么怎么样在后台获取这些数据呢?
$bookids=$this->getRequest()->getParam('bookids');
$booknums=$this->getRequest()->getParam('booknums');
$arr=$this->getRequest()->getParams();
print "<pre>";
//print_r($arr);
print_r($bookids);
print "</pre>";
for($i=0;$i<count($bookids);$i++)
{
echo $bookids[$i]."-".$booknums[$i]."<br/>";
}
可以看出getParam不带S是获取一个数组中的数字,如果带S是获得所有从前台获取的数据;
其实前台如果将name设置成数组,那么可以这样看,整个前台遍历的商品名称其实就是一个数组;只不过每个商品的名称和值是分开来放的而已
之前for后面加上;结果排错了十分钟;

发送电子邮件,可以自己搭建一个服务器,然后利用自己搭建的服务器进行发送电子邮件
当然也可以委托第三方的邮件服务器,发送邮件,给别人;这里必用质疑需要指定自己登陆
第三方邮件的用户名和密码,但是也可以指定邮件发送者的email地址,就是这个地方是可以
造价,进行伪造发件人;就是可以指定From="liuxue2011@sohu.com";这个就是邮件发送者email地址
还可以指定FromName="liuxue2011"

做一个php代码发送电子邮件,需要引入提前指定的php文件然后创建一个对象,
那么怎么做一个发送电子邮件作为连接确认注册,我们可以可以在设计用户表的时候增加两个字段
激活码,激活状态,如果没有激活,激活状态为0表示没有激活,就不能够登录。相反如果发送电子邮件,然后点击超链接带上三个参数,id=100&code=123456&checkcode=1213
其中checkcode是验证码存在session或者redis里面作为验证防止用户的利用ajax不停地查数据库对系统造成攻击!