IT技术研习社,专注互联网技术研究与分享,喜欢的朋友可以点击【关注】;把经验传递给有梦想的人;
接着上一期,这篇文章重点讲控制器、视图、模型
控制器(Controller)
1)控制器的声明
功能模块的控制器类保存在controls目录中,类名和模块名相同,下面是登录模块,定义一个Login类(类的首字母需要大写)保存的文件名为login.class.php
class Login extends Action { function __construct() { parent::__construct(); } function index() {//登陆页面 $GLOBALS['debug'] = 0; $this->display(); } function islogin() { if ($_POST['user_username'] == null && $_POST['user_password'] == null) {//如果用户名为空 $this->error('用户名和密码不能为空', 1, ''); } $_POST['user_password'] = md5($_POST['user_password']); $_POST['user_repassword'] = md5($_POST['user_repassword']); if ($_POST['user_repassword'] != $_POST['user_password']) {//如果用户输入的两次密码不一致 $this->error('两次密码不一致', 1, ''); } $user = D('user'); $date = $user->field('uid,user_password')->where(array('user_username' => $_POST['user_username']))->find(); $_POST['uid'] = $date['uid']; if ($_POST['user_password'] != $date['user_password']) {//如果输入的密码与数据库密码不匹配 $this->error('密码不正确', 1, ''); } if (strtoupper($_POST['code']) != $_SESSION['code']) {//如果输入的验证码不正确 $this->error('验证码输入不正确', 1, ''); } $_SESSION = $_POST;//把posts所有的数据压入session $date = $user->query('SELECT free_user_group.group_muser,free_user_group.group_mweb,free_user_group.group_marticle,free_user_group.group_sendarticle,free_user_group.group_mimage,free_user_group.group_sendcomment,free_user_group.group_sendmessage,free_user.user_lock FROM free_user,free_user_group WHERE free_user.uid=' . $_SESSION['uid'] . ' AND free_user.gid=free_user_group.gid', 'select'); if ($date[0]['user_lock']) { $this->error('您的帐号已被锁定,请与管理员联系后再登录', 3, 'index/index'); } else { if ($date[0]['group_muser'] || $date[0]['group_marticle'] || $date[0]['group_mweb'] || $date[0]['group_mimage']) { //查询数据库中是否开启自动记录操作 $opernote = D('foreground'); //$_SESSION['oper']=D('OperDate'); $isOpenNote = $opernote->where(array('fid' => '1'))->field('operateNotes')->find(); //$_SESSION['operAuthor']=$operAuthior->where(array('id'=>'1'))->find(); $_SESSION['isOpenNotes'] = $isOpenNote['operateNotes']; $_SESSION['islogin'] = true; $_SESSION = array_merge($date[0], $_SESSION); $user->where($_SESSION['uid'])->update('user_onlinestatus=user_onlinestatus+1'); $this->success('登陆成功', 1, 'index/index'); } else { $this->error('您的权限不够无法进入后台', 1, ''); } } } function logout() {//退出时销毁session $user = D('user'); $_SESSION['islogin'] = false; $_SESSION = array(); if (isset($_COOKIE[session_name()])) { setCookie(session_name(), '', time() - 3600, '/'); } session_destroy(); $this->redirect('index'); } function code() {//显示验证码 echo new Vcode(); } }
common.class.php 类
class Common extends Action { function init() { if (!(isset($_SESSION['islogin']) && $_SESSION['islogin'] == true)) { $this->redirect("login/index"); } $this->assign('session', $_SESSION); } }
2) 操作的声明
每个操作对应的是控制器中的一个方法,比如在上面的Login控制器中
· code()是一个获取验证码的操作,可以通过 yourhost:port/…/Login/code 这种方式访问该操作
· logout()是一个退出登录的操作,可以通过yourhost:port/…/Login/logout这种方式访问该操作
视图(View)
视图的显示是基于 Smarty 模板引擎的,继承了Smarty类,并且重写了__construct,display,is_cached,clear_cache 方法。
<?php
class Mytpl extends Smarty
{
/**
* 构造方法,用于初使化Smarty对象中的成员属性
*
*/ function __construct()
{
$this->template_dir = APP_PATH . "views/" . TPLSTYLE; //模板目录
$this->compile_dir = PROJECT_PATH . " runtime /comps/" . TPLSTYLE . "/" . TMPPATH; //里的文件是自动生成的,合成的文件
$this->caching = CSTART; //设置缓存开启
$this->cache_dir = PROJECT_PATH . "runtime/cache/" . TPLSTYLE; //设置缓存的目录
$this->cache_lifetime = CTIME; //设置缓存的时间
$this->left_delimiter = "<{"; //模板文件中使用的“左”分隔符号
$this->right_delimiter = "}>"; //模板文件中使用的“右”分隔符号
parent::__construct(); //调用父类被覆盖的构造方法
}
/*
* 重载父类Smarty类中的方法
* @param string $resource_name 模板的位置
* @param mixed $cache_id 缓存的ID
*/ function display($resource_name = null, $cache_id = null, $compile_id = null)
{
//将部分全局变量直接分配到模板中使用
$this->assign("root", rtrim($GLOBALS["root"], "/"));
$this->assign("app", rtrim($GLOBALS["app"], "/"));
$this->assign("url", rtrim($GLOBALS["url"], "/"));
$this->assign("public", rtrim($GLOBALS["public"], "/"));
$this->assign("res", rtrim($GLOBALS["res"], "/"));
if (is_null($resource_name)) {
$resource_name = "{$_GET["m"]}/{$_GET["a"]}." . TPLPREFIX;
} else if (strstr($resource_name, "/")) {
$resource_name = $resource_name . "." . TPLPREFIX;
} else {
$resource_name = $_GET["m"] . "/" . $resource_name . "." . TPLPREFIX;
}
Debug::addmsg("使用模板 <b> $resource_name </b>");
parent::display($resource_name, $cache_id, $compile_id);
}
/*
* 重载父类的Smarty类中的方法
* @param string $tpl_file 模板文件
* @param mixed $cache_id 缓存的ID
*/ function is_cached($tpl_file = null, $cache_id = null, $compile_id = null)
{
if (is_null($tpl_file)) {
$tpl_file = "{$_GET["m"]}/{$_GET["a"]}." . TPLPREFIX;
} else if (strstr($tpl_file, "/")) {
$tpl_file = $tpl_file . "." . TPLPREFIX;
} else {
$tpl_file = $_GET["m"] . "/" . $tpl_file . "." . TPLPREFIX;
}
return parent::is_cached($tpl_file, $cache_id, $compile_id);
}
/*
* 重载父类的Smarty类中的方法
* @param string $tpl_file 模板文件
* @param mixed $cache_id 缓存的ID
*/
function clear_cache($tpl_file = null, $cache_id = null, $compile_id = null, $exp_time = null)
{
if (is_null($tpl_file)) {
$tpl_file = "{$_GET["m"]}/{$_GET["a"]}." . TPLPREFIX;
} else if (strstr($tpl_file, "/")) {
$tpl_file = $tpl_file . "." . TPLPREFIX;
} else {
$tpl_file = $_GET["m"] . "/" . $tpl_file . "." . TPLPREFIX;
}
return parent::clear_cache($tpl_file = null, $cache_id = null, $compile_id = null, $exp_time = null);
}
}
比如访问登录页面
function index() {//登陆页面 $GLOBALS['debug'] = 0; $this->display(); }
类Mytpl的构造方法会自动初始化Smarty的模板目录、编译目录、缓存目录等Smarty模板引擎需要的内容
$this->template_dir = APP_PATH . "views/" . TPLSTYLE; //模板目录 $this->compile_dir = PROJECT_PATH . "runtime/comps/" . TPLSTYLE . "/" . TMPPATH; //里的文件是自动生成的,合成的文件 $this->caching = CSTART; //设置缓存开启 $this->cache_dir = PROJECT_PATH . "runtime/cache/" . TPLSTYLE; //设置缓存的目录
主要内容如下:
template_dir = "./admin/views/default" compile_dir = "./runtime/comps/default/admin_php/" cache_dir = "./runtime/cache/default" cache_lifetime = 604800
在Login控制器中调用无参的$this->display();方法,会自动从$this->template_dir文件夹下面查找模板文件,模板文件的是保存在_GET[“m”]子文件夹下的名称为_GET[“a”]的文件,比如,Login控制器对应的index模板位于如下位置:
最后使用Smarty模板引擎完成页面内容的渲染工作,最终把编译后的模板文件保存在$this->compile_dir目录下面,如下所示:
模型(Model)
模型层分为业务模型和数据模型,业务模型用于处理业务流程中的数据验证、数据处理、结果输出等等步骤;数据模型处理数据的持久化(增删改查等操作),数据模型承担了重要的责任,所以会围绕数据模型的底层处理展开来说。
· insert
· delete
· update
模型层的基类是抽象的DB类,有以下几个重要的公有属性
protected $tabName = ""; //表名,自动获取
protected $fieldList = array(); //表字段结构,自动获取
protected $auto;
// sql 的初使化
protected $sql = array("field" => "", "where" => "", "order" => "", "limit" => "", "group" => "", "having" => "");
$sql变量保存了以下信息
· field 表字段
· where where字句
· order order by 字句
· limit limit 字句
· group group 字句
· having having 字句
调用field() where() order() limit() group() having()方法,会把对应的参数值保存在$sql关联数key对应的value中,这些方法在实际中不存在,而是通过重写__call方法实现了
/** *连贯操作调用field() where() order() limit() group() having()方法,组合SQL语句 */ function __call($methodName, $args) { $methodName = strtolower($methodName); if (array_key_exists($methodName, $this->sql)) { if (empty($args[0]) || (is_string($args[0]) && trim($args[0]) === '')) { $this->sql[$methodName] = ""; } else { $this->sql[$methodName] = $args; } if ($methodName == "limit") { if ($args[0] == "0") $this->sql[$methodName] = $args; } } else { Debug::addmsg("<font color='red'>调用类" . get_class($this) . "中的方法{$methodName}()不存在!</font>"); } return $this; }
比如执行下面的代码:
$date=$user->field('uid,user_password')->where(array('user_username'=>$_POST['user_username']))->find();
最终$sql中保存的数据如下:
arra ( "field" => 'uid,user_password', "where" => array('user_username'=>"xxxxx") )
表名称和表字段结构
protected $fieldList = array(); 和 $tabName,在dpdp类setTable方法中自动获取
/** * 自动获取表结构 */ function setTable($tabName) { $cachefile = PROJECT_PATH . "runtime/data/" . $tabName . ".php"; $this->tabName = TABPREFIX . $tabName; //加前缀的表名 if (!file_exists($cachefile)) { try { $ PDO = self::connect(); $stmt = $pdo->prepare("desc {$this->tabName}"); $stmt->execute(); $auto = "yno"; $fields = array(); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { if ($row["Key"] == "PRI") { $fields["pri"] = strtolower($row["Field"]); } else { $fields[] = strtolower($row["Field"]); } if ($row["Extra"] == "auto_increment") $auto = "yes"; } //如果表中没有主键,则将第一列当作主键 if (!array_key_exists("pri", $fields)) { $fields["pri"] = array_shift($fields); } if (!DEBUG) file_put_contents($cachefile, "<?php " . json _encode($fields) . $auto); $this->fieldList = $fields; $this->auto = $auto; } catch (PDOException $e) { Debug::addmsg("<font color='red'>异常:" . $e->getMessage() . '</font>'); } } else { $json = ltrim(file_get_contents($cachefile), "<?ph "); $this->auto = substr($json, -3); $json = substr($json, 0, -3); $this->fieldList = (array)json_decode($json, true); } Debug::addmsg("表<b>{$this->tabName}</b>结构:" . implode(",", $this->fieldList), 2); //debug }
运行时(Runtime)
· 处理模块类的隐式集成common类,处理统一的业务,比如用户验证
· 处理运行时生成对应数据库驱动的数据模型
1)运行时数据模型
以一个简单的数据库表查询作为例子,
function test_query() { $article = D("article"); $data = $article->query("SELECT * FROM article", "select"); print_r($data); }
整体步骤流程:
整体步骤流程: D("article") -> Structure::model($className, $app); -> $model->setTable($className); $article->query("SELECT * FROM article", "select");
1、 D(“article”)中D方法的职责如下:
· 运行时生成数据模型
· 获取数据模型对应的数据库表的字段以及其他表信息
/** * 创建Models中的数据库操作对象 * @param string $className 类名或表名 * @param string $app 应用名,访问其他应用的Model * @return object 数据库连接对象 */ function D($className = null, $app = "") { $db = null; //如果没有传表名或类名,则直接创建DB对象,但不能对表进行操作 if (is_null($className)) { $class = "D" . DRIVER; $db = new $class; } else { $className = strtolower($className); $model = Structure::model($className, $app); $model = new $model(); //如果表结构不存在,则获取表结构 $model->setTable($className); $db = $model; } if ($app == "") $db->path = APP_PATH; else $db->path = PROJECT_PATH . strtolower($app) . '/'; return $db; }
1.1、 运行时数据模型
运行时生成数据模型由Structure::model这个方法处理
static function model($className, $app) { //父类名,使用PDO链接对应的父类名为Dpdo $driver = "D" . DRIVER; $rumtimeModelPath = PROJECT_PATH . "runtime/models/" . TMPPATH; if ($app == "") { // 数据模型类源码的位置:eg ./test/models/article.class.php,用户可以自定义数据模型保存在该位置 $src = APP_PATH . "models/" . strtolower($className) . ".class.php"; // 数据模型父类源码的位置(___表示占位符,后面会有替换步骤) eg ./test/models/___.class.php $psrc = APP_PATH . "models/___.class.php"; // 运行时数据模型类名称,规则为原始类名添加"Model"后缀 $className = ucfirst($className) . 'Model'; // 运行时数据模型父类名称(___表示占位符,后面会有替换步骤) $parentClass = '___model'; // 运行时保存的数据模型类位置 /Users/aron/git-repo/PhpLearning/Foundation/26-brophp/runtime/models/Foundation_26-brophp_test_php/articlemodel.class.php $to = $rumtimeModelPath . strtolower($className) . ".class.php"; // 运行时保存的数据模型父类位置 eg /Users/aron/git-repo/PhpLearning/Foundation/26-brophp/runtime/models/Foundation_26-brophp_test_php/___model.class.php $pto = $rumtimeModelPath . $parentClass . ".class.php"; } else { $src = PROJECT_PATH . $app . "/models/" . strtolower($className) . ".class.php"; $psrc = PROJECT_PATH . $app . "/models/___.class.php"; $className = ucfirst($app) . ucfirst($className) . 'Model'; $parentClass = ucfirst($app) . '___model'; $to = $rumtimeModelPath . strtolower($className) . ".class.php"; $pto = $rumtimeModelPath . $parentClass . ".class.php"; } // 如果有原model存在,用户自定义了数据模型类 if (file_exists($src)) { $classContent = file_get_contents($src); $super = '/extends\s+(.+?)\s*{/i'; // 如果已经有父类 if (preg_match($super, $classContent, $arr)) { $psrc = str_replace("___", strtolower($arr[1]), $psrc); $pto = str_replace("___", strtolower($arr[1]), $pto); if (file_exists($psrc)) { if (!file_exists($pto) || filemtime($psrc) > filemtime($pto)) { $pclassContent = file_get_contents($psrc); $pclassContent = preg_replace('/class\s+(.+?)\s*{/i', 'class ' . $arr[1] . 'Model extends ' . $driver . ' {', $pclassContent, 1); file_put_contents($pto, $pclassContent); } } else { Debug::addmsg("<font color='red'>文件{$psrc}不存在!</font>"); } $driver = $arr[1] . "Model"; include_once $pto; } if (!file_exists($to) || filemtime($src) > filemtime($to)) { $classContent = preg_replace('/class\s+(.+?)\s*{/i', 'class ' . $className . ' extends ' . $driver . ' {', $classContent, 1); // 生成model file_put_contents($to, $classContent); } } else { // 数据模型不存在,用户没有定义对应的数据模型,如果没有生成,则生成该数据模型 if (!file_exists($to)) { // 继承Driver对应的父类,PDO的父类为Dpdo,mysqli的父类为Dmysqli $classContent = "<?php\n\tclass {$className} extends {$driver}{\n\t}"; // 运行时生成model file_put_contents($to, $classContent); } } // 包含数据模型类,返回数据模型的类名 include_once $to; return $className; }
1.2、 获取数据库结构信息
获取数据模型对应的数据库表的字段以及其他表信息由setTable该方法处理,不同的数据驱动程序处理数据库操作的方法是不同的,所以对应的数据库驱动类需要重写该方法,下面是PDO驱动对应的setTable方法
/** * 自动获取表结构 */ function setTable($tabName) { $cachefile = PROJECT_PATH . "runtime/data/" . $tabName . ".php"; $this->tabName = TABPREFIX . $tabName; //加前缀的表名 if (!file_exists($cachefile)) { try { $pdo = self::connect(); $stmt = $pdo->prepare("desc {$this->tabName}"); $stmt->execute(); $auto = "yno"; $fields = array(); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { if ($row["Key"] == "PRI") { $fields["pri"] = strtolower($row["Field"]); } else { $fields[] = strtolower($row["Field"]); } if ($row["Extra"] == "auto_increment") $auto = "yes"; } //如果表中没有主键,则将第一列当作主键 if (!array_key_exists("pri", $fields)) { $fields["pri"] = array_shift($fields); } if (!DEBUG) file_put_contents($cachefile, "<?php " . json_encode($fields) . $auto); $this->fieldList = $fields; $this->auto = $auto; } catch (PDOException $e) { Debug::addmsg("<font color='red'>异常:" . $e->getMessage() . '</font>'); } } else { $json = ltrim(file_get_contents($cachefile), "<?ph "); $this->auto = substr($json, -3); $json = substr($json, 0, -3); $this->fieldList = (array)json_decode($json, true); } Debug::addmsg("表<b>{$this->tabName}</b>结构:" . implode(",", $this->fieldList), 2); //debug }
2、 查询
$article->query(“SELECT * FROM article”, “select”);代码执行的是查询的功能,查询方法是最基础的方法,上层的total()、select()、find()、insert()、update()、delete() 等数据库操作的方法都依赖于该方法的处理,不同的数据驱动程序处理数据库操作的方法是不同的,所以对应的数据库驱动类需要重写该方法,下面是PDO驱动对应的query方法
/** * 执行SQL语句的方法 * @param string $sql 用户查询的SQL语句 * @param string $method SQL语句的类型(select,find,total,insert,update,other) * @param array $data 为prepare方法中的?参数绑定值 * @return mixed 根据不同的SQL语句返回值 */ function query($sql, $method, $data = array()) { $startTime = microtime(true); $this->setNull(); //初使化sql $value = $this->escape_string_array($data); $marr = explode("::", $method); $method = strtolower(array_pop($marr)); if (strtolower($method) == trim("total")) { $sql = preg_replace('/select.*?from/i', 'SELECT count(*) as count FROM', $sql); } $addcache = false; $memkey = $this->sql($sql, $value); if (defined("USEMEM")) { global $mem; if ($method == "select" || $method == "find" || $method == "total") { $data = $mem->getCache($memkey); if ($data) { return $data; //直接从memserver中取,不再向下执行 } else { $addcache = true; } } } try { $return = null; $pdo = self::connect(); $stmt = $pdo->prepare($sql); //准备好一个语句 $result = $stmt->execute($value); //执行一个准备好的语句 //如果使用mem,并且不是查找语句 if (isset($mem) && !$addcache) { if ($stmt->rowCount() > 0) { $mem->delCache($this->tabName); //清除缓存 Debug::addmsg("清除表<b>{$this->tabName}</b>在Memcache中所有缓存!"); //debug } } switch ($method) { case "select": //查所有满足条件的 $data = $stmt->fetchAll(PDO::FETCH_ASSOC); if ($addcache) { $mem->addCache($this->tabName, $memkey, $data); } $return = $data; break; case "find": //只要一条记录的 $data = $stmt->fetch(PDO::FETCH_ASSOC); if ($addcache) { $mem->addCache($this->tabName, $memkey, $data); } $return = $data; break; case "total": //返回总记录数 $row = $stmt->fetch(PDO::FETCH_NUM); if ($addcache) { $mem->addCache($this->tabName, $memkey, $row[0]); } $return = $row[0]; break; case "insert": //插入数据 返回最后插入的ID if ($this->auto == "yes") $return = $pdo->lastInsertId(); else $return = $result; break; case "delete": case "update": //update $return = $stmt->rowCount(); break; default: $return = $result; } $stopTime = microtime(true); $ys = round(($stopTime - $startTime), 4); Debug::addmsg('[用时<font color="red">' . $ys . '</font>秒] - ' . $memkey, 2); //debug return $return; } catch (PDOException $e) { Debug::addmsg("<font color='red'>SQL error: " . $e->getMessage() . '</font>'); Debug::addmsg("请查看:<font color='#005500'>" . $memkey . '</font>'); //debug } }