10月15日:
用MVC搭建一个框架,自动加载视图
使用影片网站后台管理的实战案例, 结合MVC的设计模式,使用php路由机制,搭建一个url驱动运行的CMS应用。
MVC的工作流程程如下:1. 浏览者->发出指令,后台调用控制器处理
2. 控制器->按指令选取一个合适的模型 (动作集)
3. 模型->按照控制器指令选取相应的数据 (功能集)
4. 控制器->按指令选取相应的视图
5. 视图->把第三步取到的数据按用户想要的样子显示出来 (显示集)
实例说明:
现有的案例采用面向过程的编写方法,我将它改写为面向对象的编写模式,用面向对象模式可以更好地组织控制器,和管理目标组(产品族:目前管理的产品有导演、演员和影片)
【1】编写一个Router.php来解析url。所有url采用全动态生成,约定a代表模块,page代表分页 * 入口地址统一为: index.php?a=模块,
例如:index.php?a=login。按照这种约定来来解析出的对应的控制器(login)的相应操作(loginAction);
【2】编写一个自动加载文件AutoLoad.php,实现所有类的动态懒加载;
【3】编写一个入口文件index.php,在此引入url解析的模块 - ->然后在 控制器中 -->派发到model执行 -->返回在controller里 派送到视图模版显示;
【4】编写一个控制器文件controller.php,这里主要是逻辑处理的操作,去请求数据结果 , 然后送去显示;
【5】编写model.php,这里主要实现数据库操作、方法库等与数据资源相关的操作;
【6】view模块中,这里主要功能是把获得的数据资源展示给用户,是PHP与html/js相关的内容混编的。
代码实现:(主要是model和controller两个核心模块代码实现的一部分)namespace controller;
use \model\DB;
class Action {
public static function loginAction() {
return 'login_tpl';
}
public static function logoutAction() {
if (session_destroy()) {
echo "
location.href='/mytest/cms/index.php';
";
}
return '';
}
public static function indexAction() {
self::getData(DB::DIRECTOR_TABLE, 'index');
return 'index_tpl';
}
public static function userAction() {
self::getData(DB::USER_TABLE, 'user');
return 'user_tpl';
}
public static function videoAction() {
self::getData(DB::VIDEO_TABLE, 'video');
return 'video_tpl';
}
private static function getData($table, $flag) {
// 分页
$gpage = isset($_GET['page']) ? $_GET['page'] : 1;
$limit = ($gpage - 1) * DB::NUM_PER_PAGE; //第几页,第一个数据是0开始,0*10=0
$limit = $limit . ',' . DB::NUM_PER_PAGE; //组装limit
// 条件
if ($table === DB::VIDEO_TABLE) {
if (!empty($_GET['va'])) {
if ($_GET['select'] == 1) {
$where = DB::table($table)
->field('tid')
->where(array('name' => $_GET['va']))
->find();
} else {
$where = DB::table($table)
->field('uid')
->where(array('name' => $_GET['va']))
->find();
// $where = find($db,$userTable,'uid',array('name'=>$_GET['va']));
}
} else {
$where = '';
}
// 查询影片
// $video = select($db,$videoTable,'*',$where,'add_time DESC',$limit);
$video = DB::table($table)
->field('*')
->where($where)
->orderBy('add_time', 'DESC')
->limit($limit)
->select();
if ($video) {
foreach ($video as &$v) {
$tname = DB::table($table)
->field('name')
->where(array('tid' => $v['tid']))
->find();
// find($db,$directorTable,'name',array('tid'=>$v['tid']));
$v['tname'] = $tname['name'];
$uname = DB::table($table)
->field('name')
->where(array('uid' => $v['uid']))
->find();
// find($db,$userTable,'name',array('uid'=>$v['uid']));
$v['uname'] = $uname['name'];
}
}
$list['video'] = $video;
// 查询导演,作为参照
$list['director'] = DB::table(DB::DIRECTOR_TABLE)
->field('*')
->where($where)
->select();
// select($db,$directorTable,'*','');
// 查询明星,作为参照
$list['user'] = DB::table(DB::USER_TABLE)
->field('*')
->where($where)
->select();
// select($db,$userTable,'*','');
} else {
if (!empty($_GET['va'])) {
if ($_GET['select'] == 1) {
$where['name'] = $_GET['va'];
} else {
$where['phone'] = $_GET['va'];
}
} else {
$where = '';
}
$list = DB::table($table)
->field('*')
->where($where)
->orderBy('add_time', 'DESC')
->limit($limit)
->select();
}
$count_number = DB::table($table)
->field('*')
->where($where)
->count_number();
if ($count_number > 0) {
$number = ceil($count_number / DB::NUM_PER_PAGE);
$page = '';
for ($p = 1; $p <= $number; $p++) {
$url = '/mytest/cms/index.php?a=' . $flag . '&page=' . $p;
if (!empty($_GET['va'])) {
$url .= '&va=' . $_GET['va'];
if (!empty($_GET['select'])) {
$url .= '&select=' . $_GET['select'];
}
}
if ($p == $gpage) {
$page .= '';
} else {
$page .= '';
}
$page .= $p;
$page .= '';
}
}
DB::$sql_list = $list;
DB::$sql_page = $page;
}
}namespace model;
//定义一个包含数据库连接参数的接口+增删改查
interface iDbconfig {
const TYPE = 'mysql';
const HOST = '127.0.0.1';
const DBNAME = 'test';
const USER_NAME = '*****';
const PASSWORD = '*****';
public function insert($data);
public function update($data);
public function select();
public function find();
public function delete();
public function count_number();
}
//定义Dbset类,实现接口方法
class DbSet implements iDbconfig {
private static $instance = null;
//SQL关键词
protected $table;
private $field;
private $where;
private $limit;
private $orderBy;
//构造函数中自动连接数据库
private function __construct() {
$dsn = iDbconfig::TYPE
. ':host='
. iDbconfig::HOST
. '; dbname='
. iDbconfig::DBNAME;
$user = iDbconfig::USER_NAME;
$password = iDbconfig::PASSWORD;
$pdo = new \PDO($dsn, $user, $password);
}
//禁止克隆
private function __clone() {}
//创建实例
public static function getInstance() {
private static $pdo;
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
//SQL 语句关键词处理
public function table($tableName) {
$this->table = $tableName;
return $this;
}
public function field($fields) {
$fieldlist = '';
if (is_array($fields)) {
foreach ($fields as $field) {
$fieldlist .= $field . ', ';
}
} else {
$fieldlist .= $fields;
}
$fieldlist = rtrim(trim($fieldlist), ',');
$this->field = empty($fieldlist) ? '*' : $fieldlist;
return $this;
}
public function where($where) {
$whereList = '';
if (is_array($where)) {
foreach ($where as $k => $v) {
$whereList .= $k . '="' . $v . '" ,';
}
} else {
$whereList .= $where;
}
$whereList = rtrim(trim($whereList), ',');
$this->where = empty($whereList) ? $whereList : ' WHERE ' . $whereList;
return $this;
}
public function limit($limit) {
$this->limit = empty($limit) ? $limit : ' LIMIT ' . $limit;
return $this;
}
public function orderBy($orderBy, $option = 'ASC') {
$sort = in_array($option, ['DESC', 'ASC']) ? $option : 'ASC';
$this->orderBy = empty($orderBy) ? $orderBy : ' ORDER BY ' . $orderBy . ' ' . $sort;
return $this;
}
//----------------以下数据库的增删改查方法---------------------------
public function insert($data) {
$fields = '';
$value = '';
foreach ($data as $key => $v) {
$fields = $fields . ',' . $key;
$value = $value . ',:' . $key;
}
$fields = '(' . ltrim($fields, ',') . ')';
$value = '(' . ltrim($value, ',') . ')';
$sql = 'INSERT INTO '
. $this->table
. ' '
. $fields
. ' VALUES '
. $value;
$stmt = $this->pdo->prepare($sql);
$stmt->execute($data);
return [
'count' => $stmt->rowCount(),
'id' => $this->pdo->lastInsertId(),
];
}
public function update($data) {
$keyArr = array_keys($data);
$set = '';
foreach ($keyArr as $value) {
$set .= $value . '=:' . $value . ',';
}
$set = rtrim($set, ',');
$sql = 'UPDATE '
. $this->table
. ' SET '
. $set
. $this->where;
$stmt = $this->pdo->prepare($sql);
$stmt->execute($data);
return $stmt->rowCount();
}
public function select() {
$sql = 'SELECT '
. $this->field
. ' FROM '
. $this->table
. $this->where
. $this->orderBy
. $this->limit;
$stmt = $this->pdo->prepare($sql);
if ($stmt->execute()) {
// if($stmt->rowCount()>0){
// $stmt->setFetchMode(\PDO::FETCH_ASSOC);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
// }
} else {
return false;
}
}
public function find() {
$sql = 'SELECT '
. $this->field
. ' FROM '
. $this->table
. $this->where
. $this->orderBy
. $this->limit;
$stmt = $this->pdo->prepare($sql);
$stmt->execute())
return $stmt->fetch(\PDO::FETCH_ASSOC);
}
public function delete() {
$sql = 'DELETE FROM '
. $this->table
. $this->where
. $this->orderBy
. $this->limit;
$stmt = $this->pdo->prepare($sql);
$stmt->execute();
return $stmt->rowCount();
}
public function count_number() {
$sql = 'SELECT count(*) as count_number '
. ' FROM '
. $this->table
. $this->where;
$stmt = $this->pdo->prepare($sql);
if ($stmt->execute()) {
if ($stmt->rowCount() > 0) {
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
$rows = $row['count_number'];
return $rows;
}
} else {
return false;
}
}
}
运行效果展示:
总结:
1、view视图虽然是用模版完成,但html中嵌入php、js混编,完成起来太繁琐,后期改版维护也不方便,应该有更好的办法;
2、MVC架构中,一个健壮的url地址管理机制是必须的;
3、model的数据转到view中,使用了全局变量,但实际上view中是要引入model中的部分内容的,所以我使用数据库连接类中的静态属性,来传递这个数据。
4、页面中演员、导演和影片的管理动作 可以建立一个工厂模式来实现,后期扩容方便。
通过本次练习,熟悉了MVC结构的使用,离熟练使用还差得很远,需要多多练习。