目标:基于自定义的mvc框架开发的案例(项目)
项目周期 需求分析
典型的业务逻辑: 电子商务:商城(京东),B2C,C2C(淘宝),团购,秒杀,代购
内容管理:新浪门户类,优酷视频管理,博客文章管理,微博
论坛:
我们的需求,商品管理案例!参考ecshop应该出现的商品管理的基本功能实现。
安装ecshop
ecshop,使用率非常高,电子商务平台(网上商城系统)。
上传拷贝php源代码 创建数据 基本配置
下载ecshop的源代码,解压,将upload目录拷贝(上传)到web目录:建议改成ecshop的名字
使用浏览器,访问到ecshop目录中的index.php即可。自动跳转到安装界面(如果需要重新安装,也可以请求到该地址)
检查环境,典型的需要:mysql扩展,gd扩展(图片处理扩展)
配置信息
数据库服务器部分:
管理员信息:
安装测试数据:
等待安装完毕:
Tips:建议选择PHP5.3X版本
设计
数据结构的设计
编码的设计
典型的编码,分成两大方向:二次开发,基于框架开发
二次开发:在已有的产品(业务逻辑都已经实现)上做修改,升级的开发方式。
优点:速度快,通用业务逻辑已经被实现。
缺点:定制型差。
典型的产品:
电子商务:ecshop,ecmall,shopex,zen-cart,oscommerce ,麦进斗
内容管理:dedecms,帝国cms,phpcms,drupal,wordpress
论坛:phpwind,discuz
框架开发:
框架,没有实现业务逻辑,只实现了底层代码。
优点:不用写重复的底层功能代码,直接用即可,定制性强。
缺点:周期较长。
典型的框架:ThinkPHP,Yii,Ci,Zend-framework,symphony
编码的实现 30%
测试,调试
上线,生产环境
升级,维护
HTML+PHP混编的编程方式
PHP代码与HTML代码 出现在同一文件中。
典型的是:
上面是PHP先实现所有的业务逻辑,下边再是HTML决定显示样式。
或者直接利用PHP echo ,输出需要的HTML代码。
PHP Code
1 2 3 4 5 6 7 8 9
|
|
echo '<table>'; while ($row = mysql_fetch_assoc($result)) { echo '<tr>'; echo '<td>',$row['Database'],'</td>'; echo '<td>','<a href="table.php?dbname=',urlencode($row['Database']),'">查看表 </a>','<a href="">删除库</a> ','</td>'; echo '</tr>'; } echo '<table>';
|
显示与逻辑相分离
将负责数据处理,业务逻辑处理的PHP代码,与负责显示效果处理的HTML(CSS,JS)等分开来进行管理。
典型的实现:将负责显示的HTML相关部分,拆分到独立的HTML中,在PHP处理完业务逻辑后,再将HTML代码加载到该文件中。
简单的说就是把原来混编的代码分开到2个页面。这2个页面要组合起来才能实现和原来一个混编文件一样的效果。
比如:match_list.html(展示)match_list.php(逻辑) require './match_list.html';
什么是模板template
template:参见match_list.html
使用html相关代码负责显示结构,使用动态代码php实现数据的展示,该类型的文件,在项目中,称之为模板文件,template.
要求:浏览器用户请求,必须请求负责逻辑功能的PHP文件才可以。
如何限制浏览器用户不能请求到html模板文件?
典型的实现方式2种:
- 将不允许用户浏览器访问到的文件,直接放置在网站的文档根目录之外(常用)。
-
将所有的模板文件集中到摸个目录中,在利用Apache的对访问权限的控制,设置某些目录是不可以访问的。
典型的可以利用Apache的分布式配置文件.htaccess来实现。
一:利用Apache的主配置文件httpd.conf来开启某个目录的对.htaccess文件的支持
如下的配置段
<Directory>
1 2
|
|
#表示对htdocs这个目录进行配置 <Directory "E:/Server/Apache/htdocs">
|
AllowOverride来允许目录中的.htaccess文件可用
1 2 3 4 5
|
|
#表示对htdocs这个目录进行配置 <Directory "E:/Server/Apache/htdocs"> #AllowOverride值为ALL,表示完全允许 AllowOverride All </Directory>
|
Tips:针对目录的设置,在当前目录及其后代目录都会生效。
二:利用.htaccess来设置目录的访问权限
在指定目录内,创建一个.htaccess templates目录内:增加Deny from All
1 2
|
|
.htaccess Deny from All
|
MVC的分层方式
场景:分析读取数据的部分,PHP的实现逻辑
典型的,数据的处理(增删改查),可能会在不同的业务逻辑中,反复的出现。应该将相同的数据处理部分,提取出来,需要的时候调用。
在业务逻辑处理,将数据处理的部分,单独拿出来。
PHP+HTML混编文件由HTML(显示部分)和PHP(业务逻辑部分)构成,业务逻辑部分又可以划分为业务流程和数据处理部分。
以上的项目的拆分分工方式称之为mvc的设计理想。
M:Model,模型,实现主要的业务逻辑,数据的处理
V: View,视图,主要负责显示部分
C:Controller,控制器,控制整体的流程进度
如果按照MVC来设计项目
用户浏览器去请求某个功能的控制器。
该控制还需要通过当前所要完成的功能,去调用模型来得到数据处理。
该控制还需要判断当前是否需要显示,去调用视图层模板来完成显示。
参考代码:控制(match_list.php)
1 2 3 4 5 6 7 8 9 10
|
|
<?php //比赛列表的控制器文件,Controller header('Content-Type:text/html; charset=utf-8');
//需要比赛数据 //调用模型match_data.php获得数据 require './match_data.php';
//调用视图,显示结果 require './template/match_list.html';
|
模型(match_data.php)
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
|
<?php //完成比赛的数据处理,Model,模型
//引入mysqldb类 require './MySQLDB.class.php'; //实例化对象 $db = new MySQLDB(array('pass'=>'1234abcd', 'dbname'=>'itcast')); //搜索 if (!empty($_POST)) { //搜索 //判断$_POST内的数据,去拼凑sql中的where条件部分 $where = 'where '; //拼凑名字部分 $where .= "p1.stu_name like '%{$_POST['name']}%' or p2.stu_name like '%{$_POST['name']}%'"; } else { //查询所有的比赛信息 $sql = "select p1.stu_id as p1_id, p2.stu_id as p2_id, p1.stu_name as p1_name, m.match_result, p2.stu_name as p2_name, m.match_time from `match` as m left join player as p1 on m.player1=p1.stu_id left join player as p2 on m.player2=p2.stu_id;"; $rows = $db->fetchAll($sql); }
|
视图(match_list.html)
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
|
<!-- 比赛列表的视图模板文件,view,视图层功能文件 --> 比赛公告板 <hr> <?php if (!empty($rows)) :?> <?php foreach($rows as $row) :?> <a href="player.php?id=<?php echo $row['p1_id'];?>"><?php echo $row['p1_name'];?></a> <?php echo $row['match_result'];?> <a href="player.php?id=<?php echo $row['p2_id'];?>"><?php echo $row['p2_name'];?></a> <?php echo $row['match_time'];?> <br> <?php endForeach;?> <?php else:?> 对不起,没有记录! <?php endIf;?>
|
分层的目的在于管理代码和代码重用。
mvc设计思想
mvc框架,基于mvc设计思想实现的框架,称之为MVC框架。
Model,模型部分
模型类
典型的模型的应该如何实现?
模型,完成主要的业务逻辑处理(数据的处理)
典型的模型时:为需要操作的每类数据表,会独立的建立一个模型类进行管理。
基于该表的每个操作,应该对当前模型类的一个方法。
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
|
<?php //MatchModel.class.php /** * 用于操作match表的模型类 */ class MatchModel {
/** * 用于获取比赛列表的方法 * * @return array 当前的列表需要的二维数组数据 */ public function getList() { //引入mysqldb类 require './MySQLDB.class.php'; //实例化对象 $db = new MySQLDB(array('pass'=>'root', 'dbname'=>'itcast')); //查询所有的比赛信息 $sql = "select m.match_id, p1.stu_id as p1_id, p2.stu_id as p2_id, p1.stu_name as p1_name, m.match_result, p2.stu_name as p2_name, m.match_time from `match` as m left join player as p1 on m.player1=p1.stu_id left join player as p2 on m.player2=p2.stu_id;"; return $db->fetchAll($sql); } }
|
控制器中,需要得到模型对象,调用相应方法,才能获得相应数据。
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12 13
|
|
<?php //match_list.php //比赛列表的控制器文件,Controller header('Content-Type: text/html; charset=utf-8');
//需要比赛数据 //调用比赛Match模型获得数据 require './MatchModel.class.php'; $model_match = new MatchModel; $rows = $model_match->getList();
//调用视图,显示结果 require './template/match_list.html';
|
练习:删除比赛
增加一个链接请求到删除比赛的流程控制的控制器文件。
修改列表视图,template/match_list.html
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
|
<!-- 比赛列表的视图模板文件,view,视图层功能文件 --> 比赛公告板 <hr> <?php if (!empty($rows)) :?> <?php foreach($rows as $row) :?> <a href="player.php?id=<?php echo $row['p1_id'];?>"><?php echo $row['p1_name'];?></a> <?php echo $row['match_result'];?> <a href="player.php?id=<?php echo $row['p2_id'];?>"><?php echo $row['p2_name'];?></a> <?php echo $row['match_time'];?>
<a href="match_del.php?id=<?php echo $row['match_id'];?>">删除</a>//增加删除链接 <br> <?php endForeach;?> <?php else:?> 对不起,没有记录! <?php endIf;?>
|
控制器
增加match_del.php控制器文件并完成相应功能的调用:
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12
|
|
<?php //match_del.php header('Content-Type: text/html; charset=utf-8'); //比赛删除的控制器
//调用模型将数据删除 require './MatchModel.class.php'; $model_match = new MatchModel; $model_match->delById($_GET['id']);
//调用视图展示删除结果(直接跳转到列表功能即可) header('Location: match_list.php');
|
模型
为MatchModel增加一个delById的方法
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
|
/** * 利用id删除 * * @param $match_id * * @return bool */ public function delById($match_id) { //引入mysqldb类 require './MySQLDB.class.php'; //实例化对象 $db = new MySQLDB(array('pass'=>'root', 'dbname'=>'itcast'));
//删除语句 $sql = "delete from `match` where match_id='{$match_id}'"; return $db->query($sql); }
|
视图 该功能没有视图参与
基础模型类
为其他模型类提供基础代码的模型的基础类
Model.class.php
PHP Code
1 2 3 4 5 6 7
|
|
<?php /** * 基础模型类 */ class Model { }
|
其他模型类
PHP Code
1 2 3 4 5 6 7 8 9
|
|
<?php //MatchModel.class.php require './Model.class.php'; /** * 用于操作match表的模型类 */ class MatchModel extends Model { }
|
在基础模型类中,增加可以获得MySQLDB类对象的方法
PHP Code
1 2 3 4 5 6 7 8 9
|
|
<?php class Model { protected function initDB() { //引入mysqldb类 require './MySQLDB.class.php'; //实例化对象 $db = new MySQLDB(array('pass'=>'root','dbname'=>'itcast')); } }
|
应该在每次实例化模型类对象时,就完成MySQLDB类对象的初始化工作。
增加基础模型类的构造方法:
在初始化mysqlDB类对象保存到当前的一个属性上,可以保证在模型对象中都可以使用该属性:
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
|
<?php class Model { protected $db;//初始化成功的MySQLDB类的对象 public functin __construct() { //初始化数据库操作对象 $this->initDB(); } protected function initDB() { //引入mysqldb类 require './MySQLDB.class.php'; //实例化对象 $this->db = new MySQLDB(array('pass'=>'root','dbname'=>'itcast')); } }
|
模型中,使用$this->db即可操作到当前mysqlDB类对象。
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
|
//MatchModel.class.php /** * 利用id删除 * * @param $match_id * * @return bool */ public function delById($match_id) { //引入mysqldb类 require './MySQLDB.class.php'; //实例化对象 $db = new MySQLDB(array('pass'=>'root', 'dbname'=>'itcast'));
//删除语句 $sql = "delete from `match` where match_id='{$match_id}'"; return $this->db->query($sql);//$this->db }
|
最终结构模型:
view,视图层
利用模板引擎技术,搭建性能,语法更加快速,简洁的视图层语法。
Smarty,就是模板引擎技术
使用PHP的流程控制的标签语法,来完成模板部分
PHP Code
1 2 3 4 5 6 7 8 9 10 11
|
|
<?php if (!empty($rows)) :?> <?php foreach($rows as $row) :?> <a href="player.php?id=<?php echo $row['p1_id'];?>"><?php echo $row['p1_name'];?></a> <?php echo $row['match_result'];?> <a href="player.php?id=<?php echo $row['p2_id'];?>"><?php echo $row['p2_name'];?></a> <?php echo $row['match_time'];?> <br> <?php endForeach;?> <?php else:?> 对不起,没有记录! <?php endIf;?>
|
Controller,控制器
将同一个模块的操作,整理到一个控制器文件内,将控制器语法oop化
增加模块的控制器文件,module
PHP Code
1 2 3 4 5 6 7 8 9
|
|
<?php //match_controller.php //所有的关于比赛模块功能代码
//比赛列表
//比赛删除
//其他比赛功能
|
一个文件多个功能,如何判断,当前用户浏览器,需要执行哪个功能?
要求用户在请求时,携带能够标识当前操作的参数才可以,入股没有参数,则认为是默认动作:
例如:
列表: match_controller.php?a=list
删除: match_controller.php?a=del
默认: match_controller.php?a=list
PHP Code
1 2
|
|
//先确定用户所传递的参数 $action = isset($_GET['a']) ? $_GET['a'] : 'list';
|
判断action的值:
PHP Code
1 2 3 4 5
|
|
if ($action == 'list') { echo 'match list<br>'; }elseif ($action == 'del') { echo 'match delete<br>'; }
|
在相应的部分执行正确的控制器代码即可:
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
|
if ($action == 'list') { //echo 'match list<br>'; //需要比赛数据 //调用比赛Match模型获得数据 require './MatchModel.class.php'; $model_match = new MatchModel; $rows = $model_match->getList(); //调用视图,显示结果 require './template/match_list.html'; } //比赛删除 elseif ($action == 'del') { //echo 'match delete<br>'; //调用模型将数据删除 require './MatchModel.class.php'; $model_match = new MatchModel; $model_match->delById($_GET['id']); //调用视图展示删除结果(直接跳转到列表功能即可) header('Locaton: match_list.php'); }
|
将来模板内生成的连接地址,就成为controller.php?a=action
1 2 3 4 5 6
|
|
//match_list.html <a href="MatchController.php?a=del&id=<?php echo $row['match_id'];?>">删除</a>
//match_controller.php //调用视图展示删除结果(直接跳转到列表功能即可) header('Location: match_controller.php?a=list');
|
Tips:三个模块
模板(muban),Template,结构有html代码充当,而数据由动态代码(PHP)充当的负责展示文件。
模型(moxing),Model,mvc中负责处理数据,完成业务逻辑的部分
模块(mokuai),Module,几个相关的功能的集合,一个包含了多个动作的控制器文件。
控制器类
将模块的控制器类文件,由一个类的对象来实现。
结果:
模块控制器文件 控制器类文件
每个功能 控制器类一个方法
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
|
<?php /** * 比赛管理模块的控制器类 */ class MatchController { /** * 比赛列表动作 */ public function listAction() { } /** * 比赛删除动作 */ public function delAction() { } }
|
实现原来的业务逻辑即可
现在的执行功能就是调用控制器对象的方法,如何调用?
实例化控制类对象,一句当前的请求参数调用对象方法。
增加一个额外的用于实例化控制器类对象的代码,调用控制器对象方法
入口文件
进入到项目内,使项目的代码可以运转起来
PHP Code
1 2 3 4 5 6 7 8 9 10 11
|
|
<?php //index.php //项目的入口文件 require './MatchController.class.php'; //得到控制器类对象 $controller = new MatchController; //根据请求a参数,决定调用哪个方法 $a = isset($_GET['a']) ? $_GET['a'] : 'list'; $action = $a.'Action';//拼凑方法名 //调用 $controller->$action();//可变方法名
|
练习,学生列表
控制器
增加一个学生管理模块的控制器类
内增加一个学生列表list功能
PHP Code
1 2 3 4 5 6
|
|
<?php class StudentController { public function listAction() { } }
|
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12
|
|
<?php class StudentController { public function listAction() { //载入学生模型 require './StudentModel.class.php'; $model_student = new StudentModel; //得到学生列表数据 $list = $model_stuent->getList(); //调用一个学生列表视图模板,展示页面 require './template/student_list.html'; } }
|
模型
增加一个学生模型类
StudentModel.class.php
PHP Code
1 2 3 4 5 6 7
|
|
<?php require './Model.class.php'; class StudentModel extends Model { public function getList() { return $this->db->fetchAll('select * from student'); } }
|
视图
增加学生类表模板文件
student_list.html
PHP Code
1 2 3 4 5 6
|
|
<h1>学生列表</h1> <?php foreach($list as $row) : ?> <?php echo $row['stu_name'];?> <?php echo $row['gender'];?> <?php echo $row['height'];?> <?php endForeach; ?>
|
请求到该工作,则可以执行
增加一个入口文件
student.php
PHP Code
1 2 3 4 5 6
|
|
<?php //项目入口文件 require './StudentController.class.php'; //得到控制器类对象 $controller = new StudentController; //根据请求a参数,决定调用哪方法
|
单一入口
此时,会出现功能几乎完全一致,只有当前需要的控制器类不一样的入口文件。
使用一个入口文件,实现对所有的控制器类的加载,实例化工作(单一入口)
确定,入口文件需要的控制器类名即可,类似于参数a,在增加一个请求的参数c,表示,当前浏览器用户,需要看到的控制器名。
学生列表:index.php?c=Student&a=list
比赛列表: index.php?c=Match&a=del
入口文件的处理
PHP Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
|
<?php //项目的入口文件 //确定请求的参数 //根据c,确定当前的控制器类标识 $c = isset($_GET['c']) ? $_GET['c'] : 'Match'; //根据请求a参数,决定调用哪个方法 $a = isset($_GET['a']) ? $_GET['a'] : 'list'; //载入控制器类文件,并得到控制器类对象 $controller_name = $c.'Controller'; require './'.$controller_name.'.class.php'; //得到控制器类对象 $controller = new $controller_name;//可变类名 //得到当前的方法 $action_name = $a.'Action';//拼凑方法名 //调用 $controller->$action_name();//可变方法名
|
测试:localhost/six/index.php?c=Student&a=list localhost/six/index.php?c=Match&a=list
PHP Code
1 2 3
|
|
<a href="index.php?c=Match&a=del&id=<?php echo $row['match_id'];?>">删除</a>
header('Location: index.php?c=Match&a=list');
|
强调:
有了单一入口,就要求,用户的所有的请求都要从单一入口文件请求到。
项目的布局
目录分成2个目录
体现框架代码和业务逻辑代码的区别
将基础模型,mysqlDB类定义文件放在框架代码:
将业务逻辑相应的控制器,模型,视图文件分别放在application目录中
入口文件,通常放置在站点根目录:
修改项目中使用的路径
PHP Code
1 2 3 4 5 6 7 8 9
|
|
目录结构 /index.php 入口文件 /application 应用程序目录 /model 模型类目录 /view 视图类目录 /controller 控制器类目录 /framework 框架代码目录 /Model.class.php 基础模型类 /MySQLDB.class.php mysql数据库的操作类
|