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 "<script> location.href='/mytest/cms/index.php'; </script>"; } 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 .= '<a class="btn btn-success" href="' . $url . '">'; } else { $page .= '<a class="btn btn-default" href="' . $url . '">'; } $page .= $p; $page .= '</a>'; } } 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结构的使用,离熟练使用还差得很远,需要多多练习。