MVC草案
对MVC草案代码的一些实例说明
1.举个实例来说明在草案中说明的代码的不可控性、冗余性和代码造成的逻辑思维混乱问题。
在找得快这个项目排除bug过程中,我遇到过这样一个实例,客户发过来的要求只是要我把认证那一块的一个电话号码给改成他们自己的,客户bug描述就是下面这张图片
ok,我是一个对系统不是很熟悉的程序员,我按着最原始的方法,找到访问这个页面的最原始URL是:http://kppw.com/index.php?do=user&view=payitem&op=auth&auth_code=realname,我通过URL,知道了去control文件夹下面找user.php
1 <?php 2 defined ( 'IN_KEKE' ) or exit('Access Denied'); 3 keke_lang_class::package_init("user"); 4 $do and keke_lang_class::loadlang($do); 5 $views = array('index','setting','finance','employer','witkey','trans','message','collect','payitem'); 6 !in_array($view,$views) && $view ='index'; 7 ($view || $op=='basic' )and keke_lang_class::loadlang("{$do}_{$view}"); 8 $view == 'setting' and keke_lang_class::loadlang("{$do}_{$op}"); 9 $op and keke_lang_class::loadlang("{$do}_{$view}_{$op}"); 10 $_K['is_rewrite'] = 0 ; 11 kekezu::check_login (); 12 $user_info=$kekezu->_userinfo; 13 $origin_url="index.php?do=$do&view=$view"; 14 $page_title=$_lang['user_center']; 15 $nav=array( 16 "index"=>array($_lang['manage_tpl'],"meter"), 17 "setting"=>array($_lang['person_config'],"cog"), 18 "finance"=>array($_lang['finance_manage'],"chart-line2"), 19 "employer"=>array($_lang['employer_buyer'],"buyer"), 20 "witkey"=>array($_lang['witkey_seller'],"seller"), 21 "trans"=>array($_lang['process_right'],"hand-1"), 22 "message"=>array($_lang['info_center'],"sound-high"), 23 "collect"=>array($_lang['my_collect'],"star-fav"), 24 "payitem"=>array($_lang['add_service'],"bookmark-2")); 25 $user_type = intval($user_info['user_type']); 26 $user_type==1 and $w=" auth_code!='enterprise' " or ($user_type==2 and $w=" auth_code!='realname' "); 27 $auth_item_list = keke_auth_base_class::get_auth_item ( null, null, 1 ,$w); 28 require 'user/user_'.$view.'.php';
通过这个文件最下面一行,我知道了要找到user文件夹下面的user_payitem文件,看了这个文件后我有点经验了,以后只要看到do=user&view=xxx的时候,我就知道去user文件夹下面找user_xxx.php了,OK,现在我们来到了user_payitem.php下面了,打开看看代码
1 <?php 2 defined ( 'IN_KEKE' ) or exit('Access Denied'); 3 $ops = array ('auth', 'toolbox','promotion','payitem_task'); 4 in_array ( $op, $ops ) or $op = "auth"; 5 $sub_nav =array( 6 array("auth"=>array(kekezu::lang("auth"),"document"), 7 "toolbox"=>array(kekezu::lang("toolbox"),"icon16 box"), 8 "promotion"=>array(kekezu::lang("prom_make_money"),"layers-1")) 9 ); 10 $auth_item_list or $op = 'toolbox'; 11 require 'user_' . $op . '.php';
很不幸,还没找到客户需要我们该的地方,因为这里面又是一个路由控制,OK,看了这个文件后又增加了我们的经验,原来payitem里面有op控制着我们增值服务左边导航的内容,
以后增值服务里面的内容可以直接来找这个文件了,在看看我们上面找到的URL,op不正是auth吗,嘿嘿,看来的确是这样的,其他的OP值就应该对应着工具箱等等了。满心兴奋的看到最后一行,悲剧了,还没结束了,又要去找user_XXXXX,(这次的XXXX代表这OP变量的值,上次是VIEW的),无可奈何,屁颠屁颠的再次打开目录树,看着那么多让人眼疼的文件,在里面翻录这userXXX,好吧,终于让我找到你了,user_auth.php,打开瞧瞧,看是什么东东
1 <?php 2 defined ( 'IN_KEKE' ) or exit ( 'Access Denied' ); 3 keke_lang_class::package_init ( 'auth' ); 4 keke_lang_class::loadlang ( 'auth_add' ); 5 $keys = array_keys ( $auth_item_list ); 6 $auth_code or $auth_code = $keys [0]; 7 $auth_code or kekezu::show_msg ( $_lang['param_error'], "index.php?do=auth",3,'','warning' ); 8 if($auth_item_list[$auth_code]){ 9 $auth_class = "keke_auth_".$auth_code."_class"; 10 $auth_obj = new $auth_class ( $auth_code ); 11 $auth_item = $auth_item_list [$auth_code]; 12 $auth_dir = $auth_item ['auth_dir']; 13 $auth_info = $auth_obj->get_user_auth_info ( $uid,0,$show_id); 14 require "auth/$auth_code/control/auth_add.php"; 15 }else{ 16 kekezu::show_msg($_lang['param_unlaw_or_no_open'],"index.php",3,'','warning'); 17 }
简单瞧了瞧,代码还不是很难看懂,可到了最后一行,Oh,my GOD!还没完了啊(拍桌子,扰头中,真没勇气找下去了,但是没办法,工作需要,睡觉咱是码农呢?),耐下心来,仔细看了看是要去auth文件夹里面找$auth_code这个变量所对应的文件夹下面control夹子里面的add.php,哦,一下子就从user文件夹里跑到一个我陌生的网站根目录下面的auth文件夹里去了,前途真是未知啊,在user夹子面忙活了这么久,原来是白忙活了,auth文件夹又不熟悉,不晓得会怎么样呢?来都来了,看看吧。哎呀,这个夹子的目录页够深的啊,先找到上面$auth_code所分析出来的值realname文件夹了,打开一看果然有control夹子,再进去,哦,终于找到传说中的add.php了,真是千呼万唤始出来啊。
1 <?php 2 defined ( 'IN_KEKE' ) or exit('Access Denied'); 3 $page_title= $_lang['realname_auth']; 4 $step_arr=array("step1"=>array( $_lang['step_one'], $_lang['auth_intro']), 5 "step2"=>array( $_lang['step_two'], $_lang['fill_in_realname_auth_info']), 6 "step3"=>array( $_lang['step_three'], $_lang['waiting_for_background_check']), 7 "step4"=>array( $_lang['step_four'], $_lang['background_check_pass'])); 8 $auth_step= keke_auth_realname_class::get_auth_step($auth_step,$auth_info); 9 $verify = 0; 10 $ac_url = $origin_url . "&op=$op&auth_code=$auth_code&ver=".intval($ver); 11 switch ($auth_step){ 12 case "step1": 13 break; 14 case "step2": 15 $sbt_add and $auth_obj->add_auth($fds,'id_pic'); 16 break; 17 case "step3": 18 break; 19 case "step4": 20 break; 21 } 22 require keke_tpl_class::template ( 'auth/' . $auth_dir . '/tpl/' . $_K ['template'] . '/auth_add' );
哈哈,终于找到目标模板文件了,结束了长期的bug目标定位,但是通过add.php文件找到的模板文件找到的却是一个$basic_config变量控制的,可是我在逻辑层add.php怎么都没找到这个变量是在哪里赋值的,没办法一步步再回溯过去,如果有经验你就会知道,就算是你回溯到user.php,甚至是control下面的index.php都找不到这个变量赋值的地方,如果我们有经验我们就知道这个变量是在最开始的index里面赋值的,找到这个变量直接在这之前重新赋值所需要的电话号码就可以了.OK,页面现在是达到客户的需求了,显示正常,可是我们再有经验的人会知道,这样问题是解决了,其实是存在隐患的,真正的解决办法是去后台配置中心,配置下电话号码就搞定了。
好吧,这里不得不说,经验哥很犀利,甚至让人崇拜。可是又怎么样呢,我们徐经理在要根据用户类型去掉企业认证的时候也是这个页面模板里面指示的是$auth_item_list变量,可add.php控制层里面没有,再往上找不知道该找谁了,因为AUTH夹子是在根目录下面的,因为有经验就得逆向找,就算此时回想可以通过URL找到逆向的入口,可是你只会最终在user.php里面找到你所可以下手的地方。
所以经验是个潘多拉的盒子:正确的判断来自经验,而错误的判断产生经验。
2.另外两个问题等我说玩了MVC是如何来解决这个问题的时候到家自然的就会体会到了。
MVC的思想在这个例子中M就是一个payitem文件夹,代表着payitem模块,该模块由左边的认证、工具箱、推广赚钱3个C即控制器组成,没一个控制器就是一个类文件所以我们的认证就有auth_class了,然后我们会看到可能会有许多认证,变成类了之后我们可以有一个虚拟的父类,然后所有的子类就是实例化了的类,比如我们这个问题中谈到的realname_auth就可以由auth_class派生出来,完成相对应的功能。OK,思想在这里了,那我快速的实现出来可能就是这样:
A.访问实名认证的URL改为统一的路由模式,改变原来冗长的URL访问方式:index.php?m=payitem&c=realname&c=init;这句话是什么意思呢,就是访问所有模块目录集合下面的payitem模块(目录)下面的realname类里面的init方法来调用相应的模板展示给浏览器
B.所以目录结构是这样的
代码是这样
1 <?php 2 /** 3 * auth.php 4 * auth class //所有认证的基类 5 */ 6 abstract class auth { 7 //所有认证都需要使用到的逻辑处理方法,在父类里面实现,//子类里面直接$this->方法名 8 public function all_call(){ 9 10 } 11 } 12 13 ?> 14 <?php 15 class realname extends auth { 16 public function init(){//默认的显示页面 17 $view->dispaly();//显示模板 18 } 19 public function step1(){//第一步后会发生什么,需要处理的逻辑 20 21 } 22 } 23 24 ?>
这样思路就很清晰,不用去找文件,直接定位bug进行问题分析,然后入手该bug,以前的找文件全部由核心控制器来完成就好了,所有相关的操作都对于文件里面相应的方法,比如点击第一步会发生什么,删除需要怎样处理,增加页面该如何展示等,都是一个方法,哪里有了什么问题,直接看该方法里面的内容,进行分析就好了,简短的伪代码如下
1 <?php 2 class realname extends auth { 3 public function init(){//默认的显示页面 4 $view->dispaly();//显示模板 5 } 6 public function step1(){//第一步后会发生什么,需要处理的逻辑 7 8 } 9 public function ajax(){//ajax处理 10 11 } 12 public function pass(){//通过实名认证 13 14 } 15 public function nopass(){//不通过 16 17 } 18 public function edit(){//提交编辑 19 20 } 21 public function add(){//增加 22 23 } 24 public function del(){//删除 25 } 26 public function mu_del(){//批量删除 27 28 } 29 } 30 31 ?>
具体的实例可以看我做的友情链接、网站首页、用户中心首页等
看着这样优雅可维护的代码,怎一个爽字了得啊。
其它两个问题,相信通过上面的对比一下就能看出,所有的路由文件少了,重复复制copy的代码可以用类之间继承的关系来处理,只用写一次了,控制器的增删该查,再也不是用文件分开,或者说是全部都嵌套在一起了,包括ajax,再也不会看到那么多switch case了和逻辑代码混合在一起了
最后附上原来的编码风格,和现在的对比
原来完成友情链接的增删该查的功能文件(没计入路由文件),需要两个
这个是列表页面完成的功能,包含删除、批量删除、和展示列表,全部混在一起admin_tpl_link.php
1 <?php 2 defined ( 'ADMIN_KEKE' ) or exit ( 'Access Denied' ); 3 kekezu::admin_check_role (30); 4 $t_obj = keke_table_class::get_instance ( "witkey_link" ); 5 $page and $page=intval ( $page ) or $page = 1; 6 $slt_page_size and $slt_page_size=intval ( $slt_page_size ) or $slt_page_size = 10; 7 $url = "index.php?do=$do&view=$view&page=$page&slt_page_size=$slt_page_size&txt_link_id=$txt_link_id&txt_link_name=$txt_link_name&ord[]=$ord[0]&ord[]=$ord[1]"; 8 if ($ac == 'del') { //这里是删除 9 if ($link_id) { 10 $res = $t_obj->del ( "link_id", $link_id, $url ); 11 kekezu::admin_system_log ( $_lang['links_delete'].$link_id ); 12 kekezu::admin_show_msg ( $_lang['delete_success'], $url,3,'','success' ); 13 } else { 14 kekezu::admin_show_msg ( $_lang['delete_fail'], $url ,3,'','warning'); 15 } 16 } elseif (isset ( $sbt_action ) && $sbt_action == $_lang['mulit_delete']) { //这是批量删 17 empty ( $ckb ) and kekezu::admin_show_msg ( $_lang['choose_operate_item'], 'index.php?do=' . $do . '&view=' . $view ,3,'','warning'); 18 $res = $t_obj->del ( "link_id", $ckb ); 19 if ($res) { 20 kekezu::admin_system_log ( $_lang['links_delete'] . implode ( ",", $ckb ) ); 21 kekezu::admin_show_msg ( $_lang['mulit_operate_success'], $url,3,'','success' ); 22 } else { 23 kekezu::admin_show_msg ( $_lang['mulit_operate_fail'], $url,3,'','warning' ); 24 } 25 } else {//这里是查询 26 $where = ' 1 = 1 '; 27 $txt_link_id and $where .= " and link_id = ".intval($txt_link_id); 28 $txt_link_name and $where .= " and link_name like '%" .$txt_link_name. "%'"; 29 if($ord [1]){ 30 $where .= " order by $ord[0] $ord[1] "; 31 }else{ 32 $where .= " order by link_id desc"; 33 } 34 $d = $t_obj->get_grid ( $where, $url, $page, $slt_page_size,null,1,'ajax_dom'); 35 $link_arr = $d [data]; 36 $pages = $d [pages]; 37 }//最后这里是页面展示 38 require $template_obj->template ( 'control/admin/tpl/admin_' . $do . '_' . $view );
这个是编辑和删除所需要的功能,也混杂在一起admin_tpl_edit_link.php
1 <?php 2 defined ( 'ADMIN_KEKE' ) or exit ( 'Access Denied' ); 3 kekezu::admin_check_role ( 30 ); 4 $link_obj = new Keke_witkey_link_class (); 5 if ($link_id) { 6 $link_info = $link_obj->setWhere ( 'link_id=' . $link_id ); 7 $link_info = $link_obj->query_keke_witkey_link (); 8 $link_info = $link_info [0]; 9 strpos($link_info['link_pic'],"data/")!==FALSE and $mode = 2 or $mode = 1; 10 } 11 if ($sbt_edit) { 12 $link_obj->setLink_type ( 1 ); 13 $link_obj->setLink_name ( $txt_link_name ); 14 if($showMode==1){ 15 $link_pic = $txt_link_pic; 16 }elseif($showMode==2){ 17 $link_pic = keke_file_class::upload_file("fle_link_pic"); 18 } 19 if(!$link_pic){ 20 $link_pic = 0; 21 } 22 $link_obj->setLink_pic ( $link_pic ); 23 $link_obj->setLink_url ( $txt_link_url ); 24 $link_obj->setLink_status ( 1 ); 25 $link_obj->setListorder ( intval ( $txt_listorder ) ); 26 $link_obj->setOn_time ( time () ); 27 if ($hdn_link_id) { 28 $link_obj->setLink_id ( $hdn_link_id ); 29 $res = $link_obj->edit_keke_witkey_link (); 30 if ($res) { 31 kekezu::admin_system_log ( $_lang['links_edit'] . $hdn_link_id ); 32 kekezu::admin_show_msg ( $_lang['links_edit_success'], 'index.php?do=' . $do . '&view=link&page='.$page,3,'','success' ); 33 } 34 } else { 35 $res = $link_obj->create_keke_witkey_link (); 36 if ($res) { 37 kekezu::admin_system_log ( $_lang['links_add'] . $res ); 38 kekezu::admin_show_msg ( $_lang['links_edit_success'], 'index.php?do=' . $do . '&view=link&page='.$page,3,'','success' ); 39 } 40 } 41 } 42 require $kekezu->_tpl_obj->template ( 'control/admin/tpl/admin_' . $do . '_' . $view );
下面是现在编码风格的伪代码,只需要一个文件,并且有结构的把多个功能分开,思维清晰link.php(友情链接控制器类)
1 <?php 2 class link { 3 public function index(){//列表页面 4 //do somethings; 5 $this->display();//让对应模板页面显示出来 6 } 7 public function del(){//删除单个功能 8 9 } 10 public function mu_del(){ 11 //点击批量删除需要 实现的逻辑代码 12 } 13 public function add(){ 14 //添加功能 15 } 16 public function edti(){ 17 //编辑功能 18 } 19 20 }
哪里有问题就找哪里,点击哪里时候出现问题,就去那里找,思路清晰,可维护性和容易度都大大的提高
详细的实现代码在这里
1 <?php 2 class link extends keke_control_class{ 3 public function __construct(){ 4 keke_lang_class::package_init("admin"); 5 keke_lang_class::loadlang("admin_tpl"); 6 keke_lang_class::loadlang("admin_tpl_link"); 7 keke_lang_class::loadlang("admin_tpl_edit_link"); 8 $this->_linkobj = keke_table_class::get_instance ( "witkey_link" ); 9 } 10 public function index(){ 11 global $_lang; 12 if($_REQUEST['sbt_action']=='批量删除'){ 13 $this->mulit_delete();die; 14 } 15 $t_obj = keke_table_class::get_instance ( "witkey_link" ); 16 $page = $_POST['page']; 17 $slt_page_size = $_POST['slt_page_size']; 18 $txt_link_id = $_POST['txt_link_id']; 19 //echo $txt_link_id;die; 20 $txt_link_name = $_POST['txt_link_name']; 21 $ord = $_POST['ord']; 22 $page and $page=intval ( $page ) or $page = 1; 23 $slt_page_size and $slt_page_size=intval ( $slt_page_size ) or $slt_page_size = 10; 24 $url = "index.php?do=$do&view=$view&page=$page&slt_page_size=$slt_page_size&txt_link_id=$txt_link_id&txt_link_name=$txt_link_name&ord[]=$ord[0]&ord[]=$ord[1]"; 25 $where = ' 1 = 1 '; 26 $txt_link_id and $where .= " and link_id = ".intval($txt_link_id); 27 $txt_link_name and $where .= " and link_name like '%" .$txt_link_name. "%'"; 28 if($ord [1]){ 29 $where .= " order by $ord[0] $ord[1] "; 30 }else{ 31 $where .= " order by link_id desc"; 32 } 33 $d = $t_obj->get_grid ( $where, $url, $page, $slt_page_size,null,1,'ajax_dom'); 34 $link_arr = $d [data]; 35 $pages = $d [pages]; 36 require keke_tpl_class::template('control/admin/tpl/admin_tpl_link'); 37 } 38 public function edit(){ 39 global $_lang; 40 $link_obj = new Keke_witkey_link_class (); 41 if ($_GET['link_id']) { 42 $link_info = $link_obj->setWhere ( 'link_id=' . $_GET['link_id'] ); 43 $link_info = $link_obj->query_keke_witkey_link (); 44 $link_info = $link_info [0]; 45 strpos($link_info['link_pic'],"data/")!==FALSE and $mode = 2 or $mode = 1; 46 } 47 if ($_REQUEST['sbt_edit']) { 48 $link_obj->setLink_type ( 1 ); 49 $link_obj->setLink_name ( $_REQUEST['txt_link_name'] ); 50 if($_REQUEST['showMode']==1){ 51 $link_pic = $_REQUEST['txt_link_pic']; 52 }elseif($_REQUEST['showMode']==2){ 53 $link_pic = keke_file_class::upload_file("fle_link_pic"); 54 } 55 if(!$link_pic){ 56 $link_pic = 0; 57 } 58 $txt_link_url = $_REQUEST['txt_link_url']; 59 $txt_listorder = $_REQUEST['txt_listorder']; 60 $hdn_link_id = $_REQUEST['hdn_link_id']; 61 $link_obj->setLink_pic ( $link_pic ); 62 $link_obj->setLink_url ( $txt_link_url ); 63 $link_obj->setLink_status ( 1 ); 64 $link_obj->setListorder ( intval ( $txt_listorder ) ); 65 $link_obj->setOn_time ( time () ); 66 if ($hdn_link_id) { 67 $link_obj->setLink_id ( $hdn_link_id ); 68 $res = $link_obj->edit_keke_witkey_link (); 69 if ($res) { 70 kekezu::admin_system_log ( $_lang['links_edit'] . $hdn_link_id ); 71 kekezu::admin_show_msg ( $_lang['links_edit_success'], "index.php?m=admin&c=link&a=index&page={$_REQUEST['page']}",3,'','success' ); 72 } 73 } else { 74 $res = $link_obj->create_keke_witkey_link (); 75 if ($res) { 76 kekezu::admin_system_log ( $_lang['links_add'] . $res ); 77 kekezu::admin_show_msg ( $_lang['links_edit_success'], 'index.php?m=admin&c=link&a=index',3,'','success' ); 78 } 79 } 80 } 81 require keke_tpl_class::template('control/admin/tpl/admin_tpl_edit_link'); 82 } 83 public function del(){ 84 global $_lang; 85 $t_obj = keke_table_class::get_instance ( "witkey_link" ); 86 $link_id = $_REQUEST['link_id']; 87 $url = 'index.php?m=admin&c=link&a=index'; 88 if ($link_id) { 89 $res = $t_obj->del ( "link_id", $link_id, $url ); 90 kekezu::admin_system_log ( $_lang['links_delete'].$link_id ); 91 kekezu::admin_show_msg ( $_lang['delete_success'], $url,3,'','success' ); 92 } else { 93 kekezu::admin_show_msg ( $_lang['delete_fail'], $url ,3,'','warning'); 94 } 95 } 96 public function mulit_delete(){ 97 global $_lang; 98 $ckb = $_REQUEST['ckb']; 99 $url = 'index.php?m=admin&c=link&a=index'; 100 empty ( $ckb ) and kekezu::admin_show_msg ( $_lang['choose_operate_item'], "" ,3,'','warning'); 101 $res = $this->_linkobj->del ( "link_id", $ckb ); 102 if ($res) { 103 kekezu::admin_system_log ( $_lang['links_delete'] . implode ( ",", $ckb ) ); 104 kekezu::admin_show_msg ( $_lang['mulit_operate_success'], $url,3,'','success' ); 105 } else { 106 kekezu::admin_show_msg ( $_lang['mulit_operate_fail'], $url,3,'','warning' ); 107 } 108 } 109 } 110 ?>