MVC 编程模式
MVC 简介
MVC 是一个编程思想,将一个项目的很多文件进行功能上的分组。
优点
- 编程的模块化更加明确
- 后期的升级,维护很方便
- 后期添加功能非常方便
- 可以将代码的可重用性发挥到极致。
MVC 组成和作用
M(Model)模型,用来操作数据
V(View)视图,展示的页面,一般都是 html 页面
C(Controller)控制器,接受用户的请求并且根据请求的需要调用视图和模型
MVC 演化一
将上一讲的封装的 MyPDO 类拷贝到站点下,文件名改名为 “index.php”
显示 Products 表的所有数据
<?php
class MyPDO{
private $type; // 数据库类型
private $host; // 主机地址
private $port; // 端口号
private $dbname; // 数据库名
private $charset; // 字符编码
private $user; // 用户名
private $pwd; // 密码
private $pdo; //PDO 对象
private static $instance; // 私有的静态属性保存 MyPDO 的单例
private function __construct($config) { // 私有的构造函数阻止在类的外部实例化对象
$this->initParam($config);
$this->initPDO();
$this->initException();
}
private function __clone(){ // 私有的__clone() 阻止在类的外部 clone 对象
}
public static function getInstance($config=array()){ // 公有的静态方法获取 MyPDO 的单例
if(!self::$instance instanceof self)
self::$instance=new self($config);
return self::$instance;
}
/*
* 初始化成员变量
* @param $config array 配置数组
*/
private function initParam($config){
$this->type=isset($config[‘type’])?$config[‘type’]:’mysql’;
$this->host=isset($config[‘host’])?$config[‘host’]:’127.0.0.1′;
$this->port=isset($config[‘port’])?$config[‘port’]:’3306′;
$this->dbname=isset($config[‘dbname’])?$config[‘dbname’]:”;
$this->charset=isset($config[‘charset’])?$config[‘charset’]:’utf8′;
$this->user=isset($config[‘user’])?$config[‘user’]:”;
$this->pwd=isset($config[‘pwd’])?$config[‘pwd’]:”;
}
/*
* 实例化 pdo
*/
private function initPDO(){
try{
$dsn=”{$this->type}:host={$this->host};port={$this->port};dbname={$this->dbname};charset={$this->charset}”;
$this->pdo=new PDO($dsn, $this->user, $this->pwd);
} catch (Exception $e) {
$this->showException($e);
}
}
/*
* 设置自动抛出异常
*/
private function initException(){
$this->pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
}
/*
* 显示异常信息
* @param $e object 错误对象
* @parram $sql string 错误的 SQL 语句
*/
private function showException($e,$sql=”){
if($sql!=”)
echo “SQL 语句执行失败 <br> 错误的 SQL 语句是:{$sql}<br>”;
echo ‘错误信息:’.$e->getMessage(),'<br>’;
echo ‘错误编号:’.$e->getCode(),'<br>’;
echo ‘错误文件:’.$e->getFile(),'<br>’;
echo ‘错误行号:’.$e->getLine(),'<br>’;
exit;
}
/*
* 执行数据操作语句
* @return 成功返回受影响的记录数,失败返回 false
*/
public function exec($sql){
try
{
return $this->pdo->exec($sql);
}catch(PDOException $e){
$this->showException($e, $sql);
}
}
/*
* 返回插入数据的自动增长的编号
*/
public function lastInsertId(){
return $this->pdo->lastInsertId();
}
/*
* 获取 PDOStatement 对象
* @param $sql string SQL 语句
* @return PDOStatement 对象
*/
private function getPDOStatement($sql){
try
{
return $this->pdo->query($sql);
} catch (Exception $e) {
$this->showException($e);
}
}
/*
* 判断匹配类型
* @param $type string 用户需要的类型
* @return 常量
*/
private function getFetchType($type){
$allow=array(‘num’,’assoc’,’both’);
if(!in_array($type, $allow))
$type=’assoc’;
switch($type){
case ‘num’:
return PDO::FETCH_NUM;
case ‘assoc’:
return PDO::FETCH_ASSOC;
case ‘both’:
return PDO::FETCH_BOTH;
}
}
/*
* 获取一条记录
* @param $sql string SQL 语句
* @param $type string 匹配类型 num,assoc,both
* @return array 一维数组
*/
public function fetchRow($sql,$type=”){
$stmt= $this->getPDOStatement($sql);
$fetch_const= $this->getFetchType($type);
return $stmt->fetch($fetch_const);
}
/*
* 匹配所有数据
* @return array 二维数组
*/
public function fetchAll($sql,$type=”){
$stmt= $this->getPDOStatement($sql);
$fetch_const= $this->getFetchType($type);
return $stmt->fetchAll($fetch_const);
}
/*
* 获取遗憾一列的数据
*/
public function fetchColumn($sql){
try{
$stmt= $this->getPDOStatement($sql);
return $stmt->fetchColumn();
} catch (Exception $e) {
$this->showException($e);
}
}
}
?>
<!doctype html>
<html>
<head>
<meta charset=”utf-8″>
<title> 无标题文档 </title>
<style type=”text/css”>
table{
width:780px;
border:solid #000 1px;
margin:auto;
}
td,th{
border:solid #000 1px;
}
</style>
</head>
<body>
<?php
$config=array(
‘dbname’ => ‘data’,
‘user’ => ‘root’,
‘pwd’ => ‘root’
);
$mypdo= MyPDO::getInstance($config);
$list=$mypdo->fetchAll(‘select * from products’);
?>
<table>
<tr>
<th> 编号 </th>
<th> 商品名称 </th>
<th> 规格 </th>
<th> 价格 </th>
<th> 库存量 </th>
</tr>
<?php foreach($list as $rows):?>
<tr>
<td><?php echo $rows[‘proID’]?></td>
<td><?php echo $rows[‘proname’]?></td>
<td><?php echo $rows[‘proguige’]?></td>
<td><?php echo $rows[‘proprice’]?></td>
<td><?php echo $rows[‘proamount’]?></td>
</tr>
<?php endforeach;?>
</table>
</body>
</html>
运行结果
MVC 演化二
演化一中,所有的业务逻辑都在同一个页面上,没有将功能分组。
优化方案:
- 将 MyPDO 类单独保存一个文件(MyPDO.class.php)
- 将页面显示保存到视图中(products_list.html)
- 在 index.php 中自动加载类
代码实现
- 在站点下创建 MyPDO.class.php 页面,将 MyPDO 类拷贝到此页面中
- 在站点下创建 products_list.html 页面,将视图代码拷贝到此页面中
<!doctype html>
<html>
<head>
<meta charset=”utf-8″>
<title> 无标题文档 </title>
<style type=”text/css”>
table{
width:780px;
border:solid #000 1px;
margin:auto;
}
td,th{
border:solid #000 1px;
}
</style>
</head>
<body>
<table>
<tr>
<th> 编号 </th>
<th> 商品名称 </th>
<th> 规格 </th>
<th> 价格 </th>
<th> 库存量 </th>
</tr>
<?php foreach($list as $rows):?>
<tr>
<td><?php echo $rows[‘proID’]?></td>
<td><?php echo $rows[‘proname’]?></td>
<td><?php echo $rows[‘proguige’]?></td>
<td><?php echo $rows[‘proprice’]?></td>
<td><?php echo $rows[‘proamount’]?></td>
</tr>
<?php endforeach;?>
</table>
</body>
</html>
- index.php 页面代码如下:
<?php
// 自动加载类
function __autoload($class_name) {
require “./{$class_name}.class.php”;
}
// 连接数据库
$config=array(
‘dbname’ => ‘data’,
‘user’ => ‘root’,
‘pwd’ => ‘root’
);
$mypdo= MyPDO::getInstance($config);
$list=$mypdo->fetchAll(‘select * from products’);
// 调用视图
require ‘./products_list.html’;
演化二完毕后, 站点中有三个文件
MVC 演化三
在演化二中,index.php 页面(充当的是控制器)有连接数据库并获取数据的代码,这些代码应该放到模型中
优化方案:
1、一个表对应一个模型,模型名和表名同名
2、模型名以 Model 结尾,比如 User 表对应的模型:UserModel
3、文件名与类名同名
4、类文件以. class.php 结尾
代码实现:
- 在站点中创建 ProductsModel.class.php 页面
<?php
/**
*Products 表的模型,用来对 Products 表进行操作
*/
class ProductsModel {
// 获取 products 表的所有数据
public function getList() {
$config=array(
‘dbname’ => ‘data’,
‘user’ => ‘root’,
‘pwd’ => ‘root’
);
$mypdo= MyPDO::getInstance($config);
return $mypdo->fetchAll(‘select * from products’);
}
}
- index.php 页面中代码更改如下
<?php
// 自动加载类
function __autoload($class_name) {
require “./{$class_name}.class.php”;
}
// 调用模型
$model=new ProductsModel();
$list=$model->getList();
// 调用视图
require ‘./products_list.html’;
演化三结束,文件结构如下
MVC 演化四
在演化三中,如果有多个表模型,那么获取 mypdo 对象的代码需要写多次。这个是代码冗余
解决方法:
将模型的公共代码封装到基础模型中,所有的表模型都继承基础模型
代码实现
- 在站点下创建基础模型类(Model.class.php)
<?php
// 基础模型类
class Model {
protected $mypdo; // 保存 mypdo 对象
public function __construct() {
$this->initMyPDO();
}
// 初始化 mypdo 对象
private function initMyPDO() {
$config=array(
‘dbname’ => ‘data’,
‘user’ => ‘root’,
‘pwd’ => ‘root’
);
$this->mypdo= MyPDO::getInstance($config);
}
}
- 更改 ProductsModel.class.php
class ProductsModel extends Model {
// 获取 products 表的所有数据
public function getList() {
return $this->mypdo->fetchAll(‘select * from products’);
}
}
演化四完毕后,结构如下:
MVC 演化五
在演化四中,只能请求一个功能,因为控制器放在 index.php 中,项目中功能很多,如何实现多个功能?
- 一个模块对应一个控制器,比如商品模块,新闻模块
- 控制器以 Controller 结尾
- 控制器中方法名以 Action 结尾
访问控制器
通过传递不同的参数来访问不同的方法
c 表示控制器名 a 表示方法名
代码实现
- 在站点下创建 ProductsController.class.php 页面(Products 控制器)
<?php
/**
* 商品控制器
*/
class ProductsController {
// 显示商品
public function listAction() {
// 调用模型
$model=new ProductsModel();
$list=$model->getList();
// 调用视图
require ‘./products_list.html’;
}
}
- 更改 index.php 页面
<?php
// 自动加载类
function __autoload($class_name) {
require “./{$class_name}.class.php”;
}
// 确定路由
$c=isset($_GET[‘c’])?$_GET[‘c’]:’Products’; // 获取控制器名
$a=isset($_GET[‘a’])?$_GET[‘a’]:’list’; // 获取方法名
$c=ucfirst(strtolower($c)); // 首字母大写
$a=strtolower($a); // 全部小写
$controller_name=$c.’Controller’; // 拼接控制器类名
$action_name=$a.’Action’; // 拼接方法名
// 请求分发
$controller=new $controller_name();
$controller->$action_name();
Index.php 的作用:确定路由和请求分发
通过传递参数确定路由
删除商品
每实现一个功能,将对应的代码放到对应 MVC 中
视图(products_list.html)
运行结果
控制器(ProductsController)
模型(ProductsModel)
运行成功