您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > php

浅解用PHP实现MVC

时间:2019-12-16 14:13:42  来源:  作者:

MVC是一个老生常谈的问题,是为了解决一类共同问题总结出来的一套可复用的解决方案,这是软件设计模式产生的初衷。不管是客户端还是移动端,MVC的分层设计模式解决了软件开发中的可复用、单一职责、解耦的问题,php语言中的MVC设置模式也是如此。下面通过PHP语言细说MVC模式如何在PHP中应用,本文主要从如下几方面介绍:

Ø MVC的工作原理

Ø PHP开发框架

a) 开发框架的优势

b) 使用框架进行模块划分

Ø 一个简单MVC框架总体架构分析

a) URL访问方式【URL Parser】

b) 控制器【Controller】

c) 视图【View】

d) 运行时【Runtime】

1、MVC 的工作原理

MVC框架图:

视图View

代表用户交互的页面、可以包含html界面、Smarty模板等和界面相关的元素。MVC设计模式对于视图的处理仅限于视图上数据的采集和处理,以及用户的点击、拖动等事件的处理,而不包括在视图上的业务流程处理。业务流程会交给模型层(Model)处理。

模型Model

模型层是对业务流程、状态的处理以及业务规则的指定。业务流程的处理过程对其他层来说是黑箱操作,模型接受视图的请求处理数据,返回最终的处理结果。业务模型还有一个很重要的模型--数据模型,数据模型主要指实体对象的数据保存(持久化)。比如将一张订单保存到数据库,从数据库获取订单,所有和数据库相关的操作限定在该模型中。

控制器Controller

控制层是View层和Model层之间的一个桥梁,接收到用户的请求,将模型和视图匹配在一起,共同完成用户的请求。比如,用户点击一个链接,控制层接收到请求后,把信息传递给模型层,模型层处理完成之后返回视图给用户。

2、PHP开发框架

2.1、开发框架的优势

  • 框架提高开发效率和质量
  • 框架处理了许多基础性工作
  • 框架处理细节工作(事务处理、安全、数据流控制)
  • 框架结构性好、扩张性好
  • 框架划分子问题,易于控制、易于延展、易于分配资源

2.2、使用框架进行模块划分

一个典型的后台应用模块的划分

  • 平台操作管理
    • 登录管理
    • 操作界面管理
  • 系统管理频道
    • 常规管理
    • 公告管理
    • 友情链接挂你
  • 内容管理频道
    • 图片管理
    • 栏目管理
    • 文章管理
    • 幻灯片管理
  • 用户管理频道
    • 用户组管理
    • 用户管理

模块设置操作

  • 每个模块可以设置查看、添加、修改、删除、搜索等操作
  • 模块太大应该划分子模块,适合的模块数量为8~12个


3、一个简单MVC框架的分析

从以下五个方面来分析

  1. URL访问方式(URL Parser)
  2. 控制器(Controller)
  3. 视图(View)
  4. 模型(Model)
  5. 运行时(Runtime)

3.1、URL访问方式(URL Parser)

URL使用PATHINFO模式(index.php/index/index/),应用的访问方式都是采用单一入口的访问方式,所有访问一个应用中的具体模块及模块中的某个操作,都需要在URL中通过入口文件后的参数来访问和执行,所有访问都会变成由URL的参数来统一解析和调度,格式如下:

不带参数的URL
http://example.com/index.php/user/add
带有参数的URL
http://example.com/index.php/user/add/cid/5
http://example.com/index.php/user/add/cid/5/page/6

这种采用单一入口和PATHINFO模式的URL访问是MVC实现的基础,作为单一入口的框架的入口brophp.php文件则负责处理基本的信息,包括了

  • 路径信息:BroPHP框架的路径,用户项目的应用路径,项目的根路径等
  • 包含框架中的函数库文件
  • 包含全局的函数库文件,用户可以自己定义函数在这个文件中
  • __autoload()自动加载类
  • 页面缓存配置
  • 初使化时,创建项目的目录结构
  • 解析处理URL

1)路径信息处理

路径信息会保存在$GLOBALS全局数组中,后面的页面需要使用到直接从$GLOBALS中获取即可

//模板文件中所有要的路径,htmlcssJAVAscriptimagelink等中用到的路径,从WEB服务器的文档根开始
$spath = dirname($_SERVER["SCRIPT_NAME"]);
if ($spath == "/" || $spath == "\")
$spath = "";
$GLOBALS["root"] = $spath . '/'; //Web服务器根到项目的根
$GLOBALS["App"] = $_SERVER["SCRIPT_NAME"] . '/'; //当前应用脚本文件
$GLOBALS["url"] = $GLOBALS["app"] . $_GET["m"] . '/'; //访问到当前模块
$GLOBALS["public"] = $GLOBALS["root"] . 'public/'; //项目的全局资源目录
$GLOBALS["res"] = $GLOBALS["root"] . ltrim(APP_PATH, './') . "views/" . TPLSTYLE . "/resource/"; //当前应用模板的资源

2)包含框架中的函数库文件

函数库文件主要是一些常用的工具方法的集合,框架自带的functions.inc.php方法库包含了数据模型创建操作的一些列工具方法,可以开箱即用。此外用户也可以自定义函数库文件保存在对应模块目录下的commons/functions.inc.php位置,框架会自动引入。

 //包含框架中的函数库文件

 include BROPHP_PATH . 'commons/functions.inc.php';

 ​

 // 包含全局的函数库文件,用户可以自己定义函数在这个文件中

 $funfile = PROJECT_PATH . "commons/functions.inc.php";

 if (file_exists($funfile))

 include $funfile;

3)设置包含目录(类所在的全部目录)

这个步骤是__autoload()自动加载类的基础,__autoload()方法中include会自动从这些目录中寻找要包含的类

 //设置包含目录(类所在的全部目录), PATH_SEPARATOR 分隔符号 linux(:) windows(;)

 $include_path = get_include_path(); //原基目录

 $include_path .= PATH_SEPARATOR . BROPHP_PATH . "bases/"; //框架中基类所在的目录

 $include_path .= PATH_SEPARATOR . BROPHP_PATH . "classes/"; //框架中扩展类的目录

 $include_path .= PATH_SEPARATOR . BROPHP_PATH . "libs/"; //模板Smarty所在的目录

 $include_path .= PATH_SEPARATOR . PROJECT_PATH . "classes/"; //项目中用的到的工具类

 $controlerpath = PROJECT_PATH . "runtime/controls/" . TMPPATH; //生成控制器所在的路径

 $include_path .= PATH_SEPARATOR . $controlerpath; //当前应用的控制类所在的目录


 //设置include包含文件所在的所有目录

 set_include_path($include_path);

4)__autoload()自动加载类

__autoload()魔术方法是在用户创建一个没有包含的类的对象之前会调用,所以重写这个方法,在这个方法中处理类文件的包含,省去了类文件包含的工作,当然类名需要符合一定的规则才能使用自动包含,框架定义了类名的规则为“首字母大小的类名.clsss.php”

 //自动加载类

 function __autoload($className)

 {

 if ($className == "memcache") { //如果是系统的Memcache类则不包含

 return;

 } else if ($className == "Smarty") { //如果类名是Smarty类,则直接包含

 include "Smarty.class.php";

 } else { //如果是其他类,将类名转为小写

 include strtolower($className) . ".class.php";

 }

 Debug::addmsg("<b> $className </b>类", 1); //在debug中显示自动包含的类

 }

解析处理URL

解析处理URL步骤调用的是Prourl::parseUrl();

 /**

 * URL路由,转为PATHINFO的格式

 */

 static function parseUrl()

 {

 if (isset($_SERVER['PATH_INFO'])) {

 //获取 pathinfo

 $pathinfo = explode('/', trim($_SERVER['PATH_INFO'], "/"));

 ​

 // 获取 control

 $_GET['m'] = (!empty($pathinfo[0]) ? $pathinfo[0] : 'index');

 ​

 array_shift($pathinfo); //将数组开头的单元移出数组 

 ​

 // 获取 action

 $_GET['a'] = (!empty($pathinfo[0]) ? $pathinfo[0] : 'index');

 array_shift($pathinfo); //再将将数组开头的单元移出数组 

 ​

 for ($i = 0; $i < count($pathinfo); $i += 2) {

 $_GET[$pathinfo[$i]] = $pathinfo[$i + 1];

 }

 ​

 } else {

 $_GET["m"] = (!empty($_GET['m']) ? $_GET['m'] : 'index'); //默认是index模块

 $_GET["a"] = (!empty($_GET['a']) ? $_GET['a'] : 'index'); //默认是index动作

 ​

 if ($_SERVER["QUERY_STRING"]) {

 $m = $_GET["m"];

 unset($_GET["m"]); //去除数组中的m

 $a = $_GET["a"];

 unset($_GET["a"]); //去除数组中的a

 $query = http_build_query($_GET); //形成0=foo&1=bar&2=baz&3=boom&cow=milk格式

 //组成新的URL

 $url = $_SERVER["SCRIPT_NAME"] . "/{$m}/{$a}/" . str_replace(array("&", "="), "/", $query);

 header("Location:" . $url);

 }

 }

 }

访问login/index,解析保存在全局的GET数组中的信息如下:

m -> control 表示控制器
a -> action 表示操作

有了这些信息,动态创建控制器,发起对应的流程

 $className = ucfirst($_GET["m"]) . "Action";

 $controler = new $className();

 $controler->run();

3.2、控制器(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这种方式访问该操作

3.3、视图(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目录下面,如下所示:

3.4、模型(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

 }

3.5、运行时(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 = '/extendss+(.+?)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('/classs+(.+?)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('/classs+(.+?)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 = "<?phpntclass {$className} extends {$driver}{nt}";

 // 运行时生成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
}
}



Tags:MVC   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
想要了解Spring MVC框架的原理,探究框架是如何设计的,不错的学习方式是阅读源码,然后自己手写一个框架。本文带领大家简化的手写一个Spring MVC框架。Spring框架对于Java后端程...【详细内容】
2021-09-22  Tags: MVC  点击:(53)  评论:(0)  加入收藏
本篇文章我们来学习一下SpringMVC中是如何处理请求中的参数的。回想一下原生Servlet是如何处理请求参数的?我们需要使用HttpServletRequest调用getParameter方法进行获取,就像...【详细内容】
2021-08-16  Tags: MVC  点击:(70)  评论:(0)  加入收藏
背景:项目是老项目,而且比较旧为springmvc项目。项目使用的框架为公司内部自己开发,目前已经没有框架的源码可供修改,配置文件写在底层框架内,可以看到但修改不到。目的是为了实...【详细内容】
2021-08-11  Tags: MVC  点击:(70)  评论:(0)  加入收藏
一、什么是springmvc?我们知道三层架构的思想,并且如果你知道ssh的话,就会更加透彻地理解这个思想,struts2在web层,spring在中间控制,hibernate在dao层与数据库打交道,而前面刚写...【详细内容】
2021-07-14  Tags: MVC  点击:(94)  评论:(0)  加入收藏
现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了...【详细内容】
2021-05-27  Tags: MVC  点击:(195)  评论:(0)  加入收藏
在使用Spring MVC的时候,标准的配置是如下这样的: 注意注意:小编整理了一份Spring全家桶笔记:Spring+Spring Boot+Spring Cloud+Spring MVC,有需要的朋友可以私信“spring”免费...【详细内容】
2021-04-13  Tags: MVC  点击:(239)  评论:(0)  加入收藏
前言学了一遍SpringMVC以后,想着做一个总结,复习一下。复习写下面的总结的时候才发现,其实自己学得并不彻底、牢固、也没有学全,视频跟书本是要结合起来一起,每一位老师的视频可...【详细内容】
2021-03-08  Tags: MVC  点击:(193)  评论:(0)  加入收藏
单条SQL语句执行时,会被当成一个事务提交吗?以下内容摘自 《高性能MySQL》(第3版)“MySQL默认采用自动提交(AUTOCOMMIT)模式。也就是说,如果不是显式地开始一个事务,则每个查询都...【详细内容】
2020-11-04  Tags: MVC  点击:(121)  评论:(0)  加入收藏
对Java程序员来讲,做web开发最熟悉的框架莫过于SpringMVC了。之所以它能一统江湖,不是自己太优秀,而是对手太坑了,不知道大家还记不记得2017年左右Struts2爆出了一个大漏洞,自此...【详细内容】
2020-10-14  Tags: MVC  点击:(66)  评论:(0)  加入收藏
DispatcherServlet作为Spring MVC的核心控制器,初始化组件,处理客户端发送的请求,并返回 ModelAndView,进行视图渲染。主要是实现了父类 FrameworkServlet的抽象方法 doService()。...【详细内容】
2020-10-10  Tags: MVC  点击:(71)  评论:(0)  加入收藏
▌简易百科推荐
序言:前段时间织梦因为版权的问题在网上闹得沸沸扬扬,也提醒了众多开发者选择cms上应该谨慎使用,今天给大家展示一款自己搭建的内容管理系统,不用担心版权的问题,而且非常容易维...【详细内容】
2021-11-30  小程序软件开发    Tags:管理系统   点击:(34)  评论:(0)  加入收藏
准备安装包(PHP: Hypertext Preprocessor)下载安装包以及组件wget https://www.php.net/distributions/php-8.0.0.tar.bz2wget https://github.com/phpredis/phpredis/archive...【详细内容】
2021-11-09  mimic96    Tags:PHP   点击:(40)  评论:(0)  加入收藏
golang context 很好用,就使用php实现了github地址 : https://github.com/qq1060656096/php-go-context context使用闭坑指南1. 将一个Context参数作为第一个参数传递给传入和...【详细内容】
2021-11-05  1060656096    Tags:PHP   点击:(41)  评论:(0)  加入收藏
一段数组为例:$list = array:4 [ 0 => array:7 [ "id" => 56 "mer_id" => 7 "order_id" => "wx163265961408769974" "is_postage" => 0 "store_name" => "奇...【详细内容】
2021-09-29  七七小影视    Tags:PHP   点击:(65)  评论:(0)  加入收藏
利用JS的CryptoJS 3.x和PHP的openssl_encrypt,openssl_decrypt实现AES对称加密解密,由于需要两种语言对同一字符串的操作,而CryptoJS 的默认加密方式为“aes-256-cbc”,PHP端也...【详细内容】
2021-09-16  李老师tome    Tags:对称加密   点击:(79)  评论:(0)  加入收藏
1、checkdate()验证格利高里日期即:日期是否存在。checkdate(month,day,year);month必需。一个从 1 到 12 的数字,规定月。day必需。一个从 1 到 31 的数字,规定日。year必需。...【详细内容】
2021-08-31  七七小影视    Tags:时间函数   点击:(80)  评论:(0)  加入收藏
对于各类开发语言来说,整数都有一个最大的位数,如果超过位数就无法显示或者操作了。其实,这也是一种精度越界之后产生的精度丢失问题。在我们的 PHP 代码中,最大的整数非常大,我...【详细内容】
2021-08-26  硬核项目经理    Tags:PHP   点击:(83)  评论:(0)  加入收藏
遵从所有教材以及各类数据结构相关的书书籍,我们先从线性表开始入门。今天这篇文章更偏概念,是关于有线性表的一个知识点的汇总。上文说过,物理结构是用于确定数据以何种方式存...【详细内容】
2021-07-19  硬核项目经理    Tags:线性表   点击:(94)  评论:(0)  加入收藏
一、开启IIS全部功能。二、部署PHP1.官网下载并解压PHP: https://windows.php.net/downloads/releases/2.将php.ini-development文件改为php.ini3.修改php.ini(1)去掉注释,并修...【详细内容】
2021-07-15  炘蓝火诗  今日头条  Tags:PHP环境   点击:(129)  评论:(0)  加入收藏
一、环境说明本文中使用本地VM虚机部署测试。OS:CentOS Linux release 7.8.2003 (Core)虚机配置:2核CPU、4G内存①系统为CentOS 7.8 x64最小化安装,部署前已完成系统初始化、...【详细内容】
2021-06-25  IT运维笔记  今日头条  Tags:PHP8.0.7   点击:(141)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条