MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
PHP中MVC模式也称Web MVC,从上世纪70年代进化而来。MVC的目的是实现一种动态的程序设计,便于后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。软件系统通过对自身基本部份分离的同时,也赋予了各个基本部分应有的功能。
MVC各部分的职能:
一个典型的Web MVC流程:
网络上有大量优秀的MVC框架可供使用,本教程并不是为了开发一个全面的、终极的MVC框架解决方案,而是将它看作是一个很好的从内部学习PHP的机会,在此过程中,你将学习面向对象编程和MVC设计模式,并学习到开发中的一些注意事项。
更重要的是,你可以完全控制你的框架,并将你的想法融入到你开发的框架中。虽然不一定是做好的,但是你可以按照你的方式去开发功能和模块。
在开始开发前,让我们先来把项目建立好,假设我们建立的项目为 myphp-frame,MVC的框架可以命名为 MyPHP,那么接下来的第一步就是把目录结构先设置好。
虽然在这个教程中不会使用到上面的所有的目录,但是为了以后程序的可拓展性,在一开始就把程序目录设置好使非常必要的。下面就具体说说每个目录的作用:
在目录设置好以后,我们接下来就要来规定一下代码的规范:
上述的一些规则是为了能在程序中更好的进行互相的调用。接下来就开始真正的PHP MVC编程了
将所有的数据请求都重定向 index.php 文件,在 myphp-frame 目录下新建一个 .htaccess 文件,文件内容为:
<ifmodule mod_rewrite.c><span style="color: #000000;"> RewriteEngine On </span><span style="color: #008000;">#</span><span style="color: #008000;"> 确保请求路径不是一个文件名或目录</span> <span style="color: #000000;"> RewriteCond </span>%{REQUEST_FILENAME} !-<span style="color: #000000;">f RewriteCond </span>%{REQUEST_FILENAME} !-<span style="color: #000000;">d </span><span style="color: #008000;">#</span><span style="color: #008000;"> 重定向所有请求到 index.php?url=PATHNAME</span> <span style="color: #000000;"> RewriteRule </span>^(.*)$ index.php?url=$1 [PT,<span style="color: #000000;">L] </span></ifmodule>
这样做的主要原因有:
做完上面的操作,就应该知道我们需要做什么了,没错!在 myphp-frame目录下添加 index.php 文件,文件内容为:
<span style="color: #000000;">php </span><span style="color: #008000;">//</span><span style="color: #008000;"> 应用目录为当前目录</span> <span style="color: #008080;">define</span>('APP_PATH', __DIR__.'/'<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 开启调试模式</span> <span style="color: #008080;">define</span>('APP_DEBUG', <span style="color: #0000ff;">true</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 网站根URL</span> <span style="color: #008080;">define</span>('APP_URL', 'http://localhost/myphp'<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 加载框架</span> <span style="color: #0000ff;">require</span> './myphp/MyPHP.php';
注意,上面的PHP代码中,并没有添加PHP结束符号”?>”,这么做的主要原因是,对于只有 PHP 代码的文件,结束标志(“?>”)最好不存在,PHP自身并不需要结束符号,不添加结束符号可以很大程度上防止末尾被添加额外的注入内容,让程序更加安全。
在 index.php 中,我们对 myphp 文件夹下的 MyPHP.php 发起了请求,那么 MyPHP.php 这个启动文件中到底会包含哪些内容呢?
<span style="color: #000000;">php </span><span style="color: #008000;">//</span><span style="color: #008000;"> 初始化常量</span> <span style="color: #008080;">defined</span>('FRAME_PATH') or <span style="color: #008080;">define</span>('FRAME_PATH', __DIR__.'/'<span style="color: #000000;">); </span><span style="color: #008080;">defined</span>('APP_PATH') or <span style="color: #008080;">define</span>('APP_PATH', <span style="color: #008080;">dirname</span>(<span style="color: #800080;">$_SERVER</span>['SCRIPT_FILENAME']).'/'<span style="color: #000000;">); </span><span style="color: #008080;">defined</span>('APP_DEBUG') or <span style="color: #008080;">define</span>('APP_DEBUG', <span style="color: #0000ff;">false</span><span style="color: #000000;">); </span><span style="color: #008080;">defined</span>('CONFIG_PATH') or <span style="color: #008080;">define</span>('CONFIG_PATH', APP_PATH.'config/'<span style="color: #000000;">); </span><span style="color: #008080;">defined</span>('RUNTIME_PATH') or <span style="color: #008080;">define</span>('RUNTIME_PATH', APP_PATH.'runtime/'<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 包含配置文件</span> <span style="color: #0000ff;">require</span> APP_PATH . 'config/config.php'<span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;">包含核心框架类</span> <span style="color: #0000ff;">require</span> FRAME_PATH . 'Core.php'<span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;"> 实例化核心类</span> <span style="color: #800080;">$fast</span> = <span style="color: #0000ff;">new</span><span style="color: #000000;"> Core; </span><span style="color: #800080;">$fast</span>->run();
以上文件都其实可以直接在 index.php 文件中包含,常量也可以直接在 index.php 中定义,我们这么做的原因是为了在后期管理和拓展中更加的方便,所以把需要在一开始的时候就加载运行的程序统一放到一个单独的文件中引用。
先来看看config文件下的 config .php 文件,该文件的主要作用是设置一些程序的配置项及数据库连接等,主要内容为:
<span style="color: #000000;">php </span><span style="color: #008000;">/*</span><span style="color: #008000;">* 变量配置 *</span><span style="color: #008000;">*/</span> <span style="color: #008080;">define</span>('DB_NAME',<span style="color: #000000;"> mydb); </span><span style="color: #008080;">define</span>('DB_USER', 'root'<span style="color: #000000;">); </span><span style="color: #008080;">define</span>('DB_PASSWORD', 'root'<span style="color: #000000;">); </span><span style="color: #008080;">define</span>('DB_HOST', 'localhost');
应该说 config.php 涉及到的内容并不多,不过是一些基础数据库的设置,再来看看 myphp下的共用框架入口文件 Core.php 应该怎么写。
<span style="color: #000000;">php </span><span style="color: #008000;">/*</span><span style="color: #008000;">* * MyPHP核心框架 </span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> Core { </span><span style="color: #008000;">//</span><span style="color: #008000;"> 运行程序</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> run() { spl_autoload_register(</span><span style="color: #0000ff;">array</span>(<span style="color: #800080;">$this</span>, 'loadClass'<span style="color: #000000;">)); </span><span style="color: #800080;">$this</span>-><span style="color: #000000;">setReporting(); </span><span style="color: #800080;">$this</span>-><span style="color: #000000;">removeMagicQuotes(); </span><span style="color: #800080;">$this</span>-><span style="color: #000000;">unregisterGlobals(); </span><span style="color: #800080;">$this</span>-><span style="color: #000000;">Route(); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 路由处理</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> Route() { </span><span style="color: #800080;">$controllerName</span> = 'Index'<span style="color: #000000;">; </span><span style="color: #800080;">$action</span> = 'index'<span style="color: #000000;">; </span><span style="color: #0000ff;">if</span> (!<span style="color: #0000ff;">empty</span>(<span style="color: #800080;">$_GET</span>['url'<span style="color: #000000;">])) { </span><span style="color: #800080;">$url</span> = <span style="color: #800080;">$_GET</span>['url'<span style="color: #000000;">]; </span><span style="color: #800080;">$urlArray</span> = <span style="color: #008080;">explode</span>('/', <span style="color: #800080;">$url</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 获取控制器名</span> <span style="color: #800080;">$controllerName</span> = <span style="color: #008080;">ucfirst</span>(<span style="color: #800080;">$urlArray</span>[0<span style="color: #000000;">]); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 获取动作名</span> <span style="color: #008080;">array_shift</span>(<span style="color: #800080;">$urlArray</span><span style="color: #000000;">); </span><span style="color: #800080;">$action</span> = <span style="color: #0000ff;">empty</span>(<span style="color: #800080;">$urlArray</span>[0]) ? 'index' : <span style="color: #800080;">$urlArray</span>[0<span style="color: #000000;">]; </span><span style="color: #008000;">//</span><span style="color: #008000;">获取URL参数</span> <span style="color: #008080;">array_shift</span>(<span style="color: #800080;">$urlArray</span><span style="color: #000000;">); </span><span style="color: #800080;">$queryString</span> = <span style="color: #0000ff;">empty</span>(<span style="color: #800080;">$urlArray</span>) ? <span style="color: #0000ff;">array</span>() : <span style="color: #800080;">$urlArray</span><span style="color: #000000;">; } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 数据为空的处理</span> <span style="color: #800080;">$queryString</span> = <span style="color: #0000ff;">empty</span>(<span style="color: #800080;">$queryString</span>) ? <span style="color: #0000ff;">array</span>() : <span style="color: #800080;">$queryString</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;"> 实例化控制器</span> <span style="color: #800080;">$controller</span> = <span style="color: #800080;">$controllerName</span> . 'Controller'<span style="color: #000000;">; </span><span style="color: #800080;">$dispatch</span> = <span style="color: #0000ff;">new</span> <span style="color: #800080;">$controller</span>(<span style="color: #800080;">$controllerName</span>, <span style="color: #800080;">$action</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 如果控制器存和动作存在,这调用并传入URL参数</span> <span style="color: #0000ff;">if</span> ((int)<span style="color: #008080;">method_exists</span>(<span style="color: #800080;">$controller</span>, <span style="color: #800080;">$action</span><span style="color: #000000;">)) { </span><span style="color: #008080;">call_user_func_array</span>(<span style="color: #0000ff;">array</span>(<span style="color: #800080;">$dispatch</span>, <span style="color: #800080;">$action</span>), <span style="color: #800080;">$queryString</span><span style="color: #000000;">); } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #0000ff;">exit</span>(<span style="color: #800080;">$controller</span> . "控制器不存在"<span style="color: #000000;">); } } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 检测开发环境</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> setReporting() { </span><span style="color: #0000ff;">if</span> (APP_DEBUG === <span style="color: #0000ff;">true</span><span style="color: #000000;">) { </span><span style="color: #008080;">error_reporting</span>(<span style="color: #ff00ff;">E_ALL</span><span style="color: #000000;">); </span><span style="color: #008080;">ini_set</span>('display_errors','On'<span style="color: #000000;">); } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #008080;">error_reporting</span>(<span style="color: #ff00ff;">E_ALL</span><span style="color: #000000;">); </span><span style="color: #008080;">ini_set</span>('display_errors','Off'<span style="color: #000000;">); </span><span style="color: #008080;">ini_set</span>('log_errors', 'On'<span style="color: #000000;">); </span><span style="color: #008080;">ini_set</span>('error_log', RUNTIME_PATH. 'logs/error.log'<span style="color: #000000;">); } } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 删除敏感字符</span> <span style="color: #0000ff;">function</span> stripSlashesDeep(<span style="color: #800080;">$value</span><span style="color: #000000;">) { </span><span style="color: #800080;">$value</span> = <span style="color: #008080;">is_array</span>(<span style="color: #800080;">$value</span>) ? <span style="color: #008080;">array_map</span>('stripSlashesDeep', <span style="color: #800080;">$value</span>) : <span style="color: #008080;">stripslashes</span>(<span style="color: #800080;">$value</span><span style="color: #000000;">); </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$value</span><span style="color: #000000;">; } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 检测敏感字符并删除</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> removeMagicQuotes() { </span><span style="color: #0000ff;">if</span> ( <span style="color: #008080;">get_magic_quotes_gpc</span><span style="color: #000000;">()) { </span><span style="color: #800080;">$_GET</span> = stripSlashesDeep(<span style="color: #800080;">$_GET</span><span style="color: #000000;"> ); </span><span style="color: #800080;">$_POST</span> = stripSlashesDeep(<span style="color: #800080;">$_POST</span><span style="color: #000000;"> ); </span><span style="color: #800080;">$_COOKIE</span> = stripSlashesDeep(<span style="color: #800080;">$_COOKIE</span><span style="color: #000000;">); </span><span style="color: #800080;">$_SESSION</span> = stripSlashesDeep(<span style="color: #800080;">$_SESSION</span><span style="color: #000000;">); } } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 检测自定义全局变量(register globals)并移除</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> unregisterGlobals() { </span><span style="color: #0000ff;">if</span> (<span style="color: #008080;">ini_get</span>('register_globals'<span style="color: #000000;">)) { </span><span style="color: #800080;">$array</span> = <span style="color: #0000ff;">array</span>('_SESSION', '_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES'<span style="color: #000000;">); </span><span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$array</span> <span style="color: #0000ff;">as</span> <span style="color: #800080;">$value</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$GLOBALS</span>[<span style="color: #800080;">$value</span>] <span style="color: #0000ff;">as</span> <span style="color: #800080;">$key</span> => <span style="color: #800080;">$var</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span> (<span style="color: #800080;">$var</span> === <span style="color: #800080;">$GLOBALS</span>[<span style="color: #800080;">$key</span><span style="color: #000000;">]) { </span><span style="color: #0000ff;">unset</span>(<span style="color: #800080;">$GLOBALS</span>[<span style="color: #800080;">$key</span><span style="color: #000000;">]); } } } } } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 自动加载控制器和模型类</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">function</span> loadClass(<span style="color: #800080;">$class</span><span style="color: #000000;">) { </span><span style="color: #800080;">$frameworks</span> = FRAME_PATH . <span style="color: #800080;">$class</span> . '.class.php'<span style="color: #000000;">; </span><span style="color: #800080;">$controllers</span> = APP_PATH . 'application/controllers/' . <span style="color: #800080;">$class</span> . '.class.php'<span style="color: #000000;">; </span><span style="color: #800080;">$models</span> = APP_PATH . 'application/models/' . <span style="color: #800080;">$class</span> . '.class.php'<span style="color: #000000;">; </span><span style="color: #0000ff;">if</span> (<span style="color: #008080;">file_exists</span>(<span style="color: #800080;">$frameworks</span><span style="color: #000000;">)) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> 加载框架核心类</span> <span style="color: #0000ff;">include</span> <span style="color: #800080;">$frameworks</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">elseif</span> (<span style="color: #008080;">file_exists</span>(<span style="color: #800080;">$controllers</span><span style="color: #000000;">)) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> 加载应用控制器类</span> <span style="color: #0000ff;">include</span> <span style="color: #800080;">$controllers</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">elseif</span> (<span style="color: #008080;">file_exists</span>(<span style="color: #800080;">$models</span><span style="color: #000000;">)) { </span><span style="color: #008000;">//</span><span style="color: #008000;">加载应用模型类</span> <span style="color: #0000ff;">include</span> <span style="color: #800080;">$models</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #008000;">/*</span><span style="color: #008000;"> 错误代码 </span><span style="color: #008000;">*/</span><span style="color: #000000;"> } } }</span>
下面重点讲解主请求方法 callHook(),首先我们想看看我们的 URL 会这样:
yoursite.com/controllerName/actionName/queryString
callHook()的作用就是,从全局变量 $_GET['url']变量中获取 URL,并将其分割成三部分:$controller、$action 和 $queryString。
例如,URL链接为:myphp.com/item/view/1/first-item,那么
分割完成后,会实例化一个新的控制器:$controller.'Controller'(其中“.”是连字符),并调用其方法 $action。
接下来的操作就是在 myphp 中建立程序所需的基类,包括控制器、模型和视图的基类。
新建控制器基类为 Controller.class.php,控制器的主要功能就是总调度,具体具体内容如下:
<span style="color: #000000;">php </span><span style="color: #008000;">/*</span><span style="color: #008000;">* * 控制器基类 </span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> Controller { </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$_controller</span><span style="color: #000000;">; </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$_action</span><span style="color: #000000;">; </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$_view</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;"> 构造函数,初始化属性,并实例化对应模型</span> <span style="color: #0000ff;">function</span> __construct(<span style="color: #800080;">$controller</span>, <span style="color: #800080;">$action</span><span style="color: #000000;">) { </span><span style="color: #800080;">$this</span>->_controller = <span style="color: #800080;">$controller</span><span style="color: #000000;">; </span><span style="color: #800080;">$this</span>->_action = <span style="color: #800080;">$action</span><span style="color: #000000;">; </span><span style="color: #800080;">$this</span>->_view = <span style="color: #0000ff;">new</span> View(<span style="color: #800080;">$controller</span>, <span style="color: #800080;">$action</span><span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 分配变量</span> <span style="color: #0000ff;">function</span> assign(<span style="color: #800080;">$name</span>, <span style="color: #800080;">$value</span><span style="color: #000000;">) { </span><span style="color: #800080;">$this</span>->_view->assign(<span style="color: #800080;">$name</span>, <span style="color: #800080;">$value</span><span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 渲染视图</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> __destruct() { </span><span style="color: #800080;">$this</span>->_view-><span style="color: #000000;">render(); } }</span>
Controller 类实现所有控制器、模型和视图(View类)的通信。在执行析构函数时,我们可以调用 render() 来显示视图(view)文件。
新建模型基类为 Model.class.php,模型基类 Model.class.php 代码如下:
<span style="color: #000000;">php </span><span style="color: #0000ff;">class</span> Model <span style="color: #0000ff;">extends</span><span style="color: #000000;"> Sql { </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$_model</span><span style="color: #000000;">; </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$_table</span><span style="color: #000000;">; </span><span style="color: #0000ff;">function</span><span style="color: #000000;"> __construct() { </span><span style="color: #008000;">//</span><span style="color: #008000;"> 连接数据库</span> <span style="color: #800080;">$this</span>->connect(DB_HOST, DB_USER, DB_PASSWORD,<span style="color: #000000;"> DB_NAME); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 获取模型名称</span> <span style="color: #800080;">$this</span>->_model = <span style="color: #008080;">get_class</span>(<span style="color: #800080;">$this</span><span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->_model = <span style="color: #008080;">rtrim</span>(<span style="color: #800080;">$this</span>->_model, 'Model'<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 数据库表名与类名一致</span> <span style="color: #800080;">$this</span>->_table = <span style="color: #008080;">strtolower</span>(<span style="color: #800080;">$this</span>-><span style="color: #000000;">_model); } </span><span style="color: #0000ff;">function</span><span style="color: #000000;"> __destruct() { } }</span>
考虑到模型需要对数据库进行处理,所以单独建立一个数据库基类 Sql.class.php,模型基类继承 Sql.class.php,代码如下:
<span style="color: #000000;">php </span><span style="color: #0000ff;">class</span><span style="color: #000000;"> Sql { </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$_dbHandle</span><span style="color: #000000;">; </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$_result</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;"> 连接数据库</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> connect(<span style="color: #800080;">$host</span>, <span style="color: #800080;">$user</span>, <span style="color: #800080;">$pass</span>, <span style="color: #800080;">$dbname</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> { </span><span style="color: #800080;">$dsn</span> = <span style="color: #008080;">sprintf</span>("mysql:host=%s;dbname=%s;charset=utf8", <span style="color: #800080;">$host</span>, <span style="color: #800080;">$dbname</span><span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->_dbHandle = <span style="color: #0000ff;">new</span> PDO(<span style="color: #800080;">$dsn</span>, <span style="color: #800080;">$user</span>, <span style="color: #800080;">$pass</span>, <span style="color: #0000ff;">array</span>(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::<span style="color: #000000;">FETCH_ASSOC)); } </span><span style="color: #0000ff;">catch</span> (PDOException <span style="color: #800080;">$e</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">exit</span>('错误: ' . <span style="color: #800080;">$e</span>-><span style="color: #000000;">getMessage()); } } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 查询所有</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> selectAll() { </span><span style="color: #800080;">$sql</span> = <span style="color: #008080;">sprintf</span>("select * from `%s`", <span style="color: #800080;">$this</span>-><span style="color: #000000;">_table); </span><span style="color: #800080;">$sth</span> = <span style="color: #800080;">$this</span>->_dbHandle->prepare(<span style="color: #800080;">$sql</span><span style="color: #000000;">); </span><span style="color: #800080;">$sth</span>-><span style="color: #000000;">execute(); </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$sth</span>-><span style="color: #000000;">fetchAll(); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 根据条件 (id) 查询</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> select(<span style="color: #800080;">$id</span><span style="color: #000000;">) { </span><span style="color: #800080;">$sql</span> = <span style="color: #008080;">sprintf</span>("select * from `%s` where `id` = '%s'", <span style="color: #800080;">$this</span>->_table, <span style="color: #800080;">$id</span><span style="color: #000000;">); </span><span style="color: #800080;">$sth</span> = <span style="color: #800080;">$this</span>->_dbHandle->prepare(<span style="color: #800080;">$sql</span><span style="color: #000000;">); </span><span style="color: #800080;">$sth</span>-><span style="color: #000000;">execute(); </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$sth</span>-><span style="color: #000000;">fetch(); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 根据条件 (id) 删除</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> delete(<span style="color: #800080;">$id</span><span style="color: #000000;">) { </span><span style="color: #800080;">$sql</span> = <span style="color: #008080;">sprintf</span>("delete from `%s` where `id` = '%s'", <span style="color: #800080;">$this</span>->_table, <span style="color: #800080;">$id</span><span style="color: #000000;">); </span><span style="color: #800080;">$sth</span> = <span style="color: #800080;">$this</span>->_dbHandle->prepare(<span style="color: #800080;">$sql</span><span style="color: #000000;">); </span><span style="color: #800080;">$sth</span>-><span style="color: #000000;">execute(); </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$sth</span>-><span style="color: #000000;">rowCount(); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 自定义SQL查询,返回影响的行数</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> query(<span style="color: #800080;">$sql</span><span style="color: #000000;">) { </span><span style="color: #800080;">$sth</span> = <span style="color: #800080;">$this</span>->_dbHandle->prepare(<span style="color: #800080;">$sql</span><span style="color: #000000;">); </span><span style="color: #800080;">$sth</span>-><span style="color: #000000;">execute(); </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$sth</span>-><span style="color: #000000;">rowCount(); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 新增数据</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> add(<span style="color: #800080;">$data</span><span style="color: #000000;">) { </span><span style="color: #800080;">$sql</span> = <span style="color: #008080;">sprintf</span>("insert into `%s` %s", <span style="color: #800080;">$this</span>->_table, <span style="color: #800080;">$this</span>->formatInsert(<span style="color: #800080;">$data</span><span style="color: #000000;">)); </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$this</span>->query(<span style="color: #800080;">$sql</span><span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 修改数据</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> update(<span style="color: #800080;">$id</span>, <span style="color: #800080;">$data</span><span style="color: #000000;">) { </span><span style="color: #800080;">$sql</span> = <span style="color: #008080;">sprintf</span>("update `%s` set %s where `id` = '%s'", <span style="color: #800080;">$this</span>->_table, <span style="color: #800080;">$this</span>->formatUpdate(<span style="color: #800080;">$data</span>), <span style="color: #800080;">$id</span><span style="color: #000000;">); </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$this</span>->query(<span style="color: #800080;">$sql</span><span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 将数组转换成插入格式的sql语句</span> <span style="color: #0000ff;">private</span> <span style="color: #0000ff;">function</span> formatInsert(<span style="color: #800080;">$data</span><span style="color: #000000;">) { </span><span style="color: #800080;">$fields</span> = <span style="color: #0000ff;">array</span><span style="color: #000000;">(); </span><span style="color: #800080;">$values</span> = <span style="color: #0000ff;">array</span><span style="color: #000000;">(); </span><span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$data</span> <span style="color: #0000ff;">as</span> <span style="color: #800080;">$key</span> => <span style="color: #800080;">$value</span><span style="color: #000000;">) { </span><span style="color: #800080;">$fields</span>[] = <span style="color: #008080;">sprintf</span>("`%s`", <span style="color: #800080;">$key</span><span style="color: #000000;">); </span><span style="color: #800080;">$values</span>[] = <span style="color: #008080;">sprintf</span>("'%s'", <span style="color: #800080;">$value</span><span style="color: #000000;">); } </span><span style="color: #800080;">$field</span> = <span style="color: #008080;">implode</span>(',', <span style="color: #800080;">$fields</span><span style="color: #000000;">); </span><span style="color: #800080;">$value</span> = <span style="color: #008080;">implode</span>(',', <span style="color: #800080;">$values</span><span style="color: #000000;">); </span><span style="color: #0000ff;">return</span> <span style="color: #008080;">sprintf</span>("(%s) values (%s)", <span style="color: #800080;">$field</span>, <span style="color: #800080;">$value</span><span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 将数组转换成更新格式的sql语句</span> <span style="color: #0000ff;">private</span> <span style="color: #0000ff;">function</span> formatUpdate(<span style="color: #800080;">$data</span><span style="color: #000000;">) { </span><span style="color: #800080;">$fields</span> = <span style="color: #0000ff;">array</span><span style="color: #000000;">(); </span><span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$data</span> <span style="color: #0000ff;">as</span> <span style="color: #800080;">$key</span> => <span style="color: #800080;">$value</span><span style="color: #000000;">) { </span><span style="color: #800080;">$fields</span>[] = <span style="color: #008080;">sprintf</span>("`%s` = '%s'", <span style="color: #800080;">$key</span>, <span style="color: #800080;">$value</span><span style="color: #000000;">); } </span><span style="color: #0000ff;">return</span> <span style="color: #008080;">implode</span>(',', <span style="color: #800080;">$fields</span><span style="color: #000000;">); } }</span>
应该说,Sql.class.php 是框架的核心部分。为什么?因为通过它,我们创建了一个 SQL 抽象层,可以大大减少了数据库的编程工作。虽然 PDO 接口本来已经很简洁,但是抽象之后框架的可灵活性更高。
视图类 View.class.php 内容如下:
<span style="color: #000000;">php </span><span style="color: #008000;">/*</span><span style="color: #008000;">* * 视图基类 </span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> View { </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$variables</span> = <span style="color: #0000ff;">array</span><span style="color: #000000;">(); </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$_controller</span><span style="color: #000000;">; </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$_action</span><span style="color: #000000;">; </span><span style="color: #0000ff;">function</span> __construct(<span style="color: #800080;">$controller</span>, <span style="color: #800080;">$action</span><span style="color: #000000;">) { </span><span style="color: #800080;">$this</span>->_controller = <span style="color: #800080;">$controller</span><span style="color: #000000;">; </span><span style="color: #800080;">$this</span>->_action = <span style="color: #800080;">$action</span><span style="color: #000000;">; } </span><span style="color: #008000;">/*</span><span style="color: #008000;">* 分配变量 *</span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">function</span> assign(<span style="color: #800080;">$name</span>, <span style="color: #800080;">$value</span><span style="color: #000000;">) { </span><span style="color: #800080;">$this</span>->variables[<span style="color: #800080;">$name</span>] = <span style="color: #800080;">$value</span><span style="color: #000000;">; } </span><span style="color: #008000;">/*</span><span style="color: #008000;">* 渲染显示 *</span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> render() { </span><span style="color: #008080;">extract</span>(<span style="color: #800080;">$this</span>-><span style="color: #000000;">variables); </span><span style="color: #800080;">$defaultHeader</span> = APP_PATH . 'application/views/header.php'<span style="color: #000000;">; </span><span style="color: #800080;">$defaultFooter</span> = APP_PATH . 'application/views/footer.php'<span style="color: #000000;">; </span><span style="color: #800080;">$controllerHeader</span> = APP_PATH . 'application/views/' . <span style="color: #800080;">$this</span>->_controller . '/header.php'<span style="color: #000000;">; </span><span style="color: #800080;">$controllerFooter</span> = APP_PATH . 'application/views/' . <span style="color: #800080;">$this</span>->_controller . '/footer.php'<span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;"> 页头文件</span> <span style="color: #0000ff;">if</span> (<span style="color: #008080;">file_exists</span>(<span style="color: #800080;">$controllerHeader</span><span style="color: #000000;">)) { </span><span style="color: #0000ff;">include</span> (<span style="color: #800080;">$controllerHeader</span><span style="color: #000000;">); } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #0000ff;">include</span> (<span style="color: #800080;">$defaultHeader</span><span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 页内容文件</span> <span style="color: #0000ff;">include</span> (APP_PATH . 'application/views/' . <span style="color: #800080;">$this</span>->_controller . '/' . <span style="color: #800080;">$this</span>->_action . '.php'<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 页脚文件</span> <span style="color: #0000ff;">if</span> (<span style="color: #008080;">file_exists</span>(<span style="color: #800080;">$controllerFooter</span><span style="color: #000000;">)) { </span><span style="color: #0000ff;">include</span> (<span style="color: #800080;">$controllerFooter</span><span style="color: #000000;">); } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #0000ff;">include</span> (<span style="color: #800080;">$defaultFooter</span><span style="color: #000000;">); } } }</span>
这样我们的核心的PHP MVC框架就编写完成了,下面我们开始编写应用来测试框架功能。
在 SQL 中新建一个 mydb 数据库,使用下面的语句增加 item 数据表并插入2条记录:
CREATE DATABASE ` mydb ` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE ` mydb `;
CREATE TABLE `item` (
`id` int(11) NOT NULL auto_increment,
`item_name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `item` VALUES(1, 'Hello World.');
INSERT INTO `item` VALUES(2, 'Lets go!');
然后,我们还需要在 models 目录中创建一个 ItemModel.php 模型,内容如下:
<span style="color: #000000;">php </span><span style="color: #0000ff;">class</span> ItemModel <span style="color: #0000ff;">extends</span><span style="color: #000000;"> Model { </span><span style="color: #008000;">/*</span><span style="color: #008000;"> 业务逻辑层实现 </span><span style="color: #008000;">*/</span><span style="color: #000000;"> }</span>
模型内容为空。因为 Item 模型继承了 Model,所以它拥有 Model 的所有功能。
在 controllers 目录下创建一个 ItemController.php 控制器,内容如下:
<span style="color: #000000;">php </span><span style="color: #0000ff;">class</span> ItemController <span style="color: #0000ff;">extends</span><span style="color: #000000;"> Controller { </span><span style="color: #008000;">//</span><span style="color: #008000;"> 首页方法,测试框架自定义DB查询</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> index() { </span><span style="color: #800080;">$items</span> = (<span style="color: #0000ff;">new</span> ItemModel)-><span style="color: #000000;">selectAll(); </span><span style="color: #800080;">$this</span>->assign('title', '全部条目'<span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->assign('items', <span style="color: #800080;">$items</span><span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 添加记录,测试框架DB记录创建(Create)</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> add() { </span><span style="color: #800080;">$data</span>['item_name'] = <span style="color: #800080;">$_POST</span>['value'<span style="color: #000000;">]; </span><span style="color: #800080;">$count</span> = (<span style="color: #0000ff;">new</span> ItemModel)->add(<span style="color: #800080;">$data</span><span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->assign('title', '添加成功'<span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->assign('count', <span style="color: #800080;">$count</span><span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 查看记录,测试框架DB记录读取(Read)</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> view(<span style="color: #800080;">$id</span> = <span style="color: #0000ff;">null</span><span style="color: #000000;">) { </span><span style="color: #800080;">$item</span> = (<span style="color: #0000ff;">new</span> ItemModel)->select(<span style="color: #800080;">$id</span><span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->assign('title', '正在查看' . <span style="color: #800080;">$item</span>['item_name'<span style="color: #000000;">]); </span><span style="color: #800080;">$this</span>->assign('item', <span style="color: #800080;">$item</span><span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 更新记录,测试框架DB记录更新(Update)</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> update() { </span><span style="color: #800080;">$data</span> = <span style="color: #0000ff;">array</span>('id' => <span style="color: #800080;">$_POST</span>['id'], 'item_name' => <span style="color: #800080;">$_POST</span>['value'<span style="color: #000000;">]); </span><span style="color: #800080;">$count</span> = (<span style="color: #0000ff;">new</span> ItemModel)->update(<span style="color: #800080;">$data</span>['id'], <span style="color: #800080;">$data</span><span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->assign('title', '修改成功'<span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->assign('count', <span style="color: #800080;">$count</span><span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 删除记录,测试框架DB记录删除(Delete)</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> delete(<span style="color: #800080;">$id</span> = <span style="color: #0000ff;">null</span><span style="color: #000000;">) { </span><span style="color: #800080;">$count</span> = (<span style="color: #0000ff;">new</span> ItemModel)->delete(<span style="color: #800080;">$id</span><span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->assign('title', '删除成功'<span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->assign('count', <span style="color: #800080;">$count</span><span style="color: #000000;">); } }</span>
在 views 目录下新建 header.php 和 footer.php 两个页头页脚模板,内容如下。
header.php,内容:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title> <?php <span style="color: #0000ff;">echo <span style="color: #800080;">$title</span> ?></title> <style> .<span style="color: #000000;">item { width:<span style="color: #000000;">400px; } input { color:<span style="color: #008000;">#<span style="color: #008000;">222222; font-family:georgia,<span style="color: #000000;">times; font-size:<span style="color: #000000;">24px; font-weight:<span style="color: #000000;">normal; line-height:1.<span style="color: #000000;">2em; color:<span style="color: #000000;">black; } a { color:<span style="color: #000000;">blue; font-family:georgia,<span style="color: #000000;">times; font-size:<span style="color: #000000;">20px; font-weight:<span style="color: #000000;">normal; line-height:1.<span style="color: #000000;">2em; text-decoration:<span style="color: #000000;">none; } a:<span style="color: #000000;">hover { text-decoration:<span style="color: #000000;">underline; } h1 { color:<span style="color: #008000;">#<span style="color: #008000;">000000; font-size:<span style="color: #000000;">41px; letter-spacing:-<span style="color: #000000;">2px; line-height:<span style="color: #000000;">1em; font-family:helvetica,arial,sans-<span style="color: #000000;">serif; border-bottom:1px dotted <span style="color: #008000;">#<span style="color: #008000;">cccccc; <span style="color: #000000;"> } h2 { color:<span style="color: #008000;">#<span style="color: #008000;">000000; font-size:<span style="color: #000000;">34px; letter-spacing:-<span style="color: #000000;">2px; line-height:<span style="color: #000000;">1em; font-family:helvetica,arial,sans-<span style="color: #000000;">serif; } </style> <h1> <?php <span style="color: #0000ff;">echo <span style="color: #800080;">$title</span> ?></h1><span style="color: #000000;"> footer</span>.<span style="color: #000000;">php,内容: </span>
然后,在 views/item 创建以下几个视图文件。
index.php,浏览数据库内 item 表的所有记录,内容:
add.php,添加记录,内容:
<a style="color: #0000ff;">class="big" href="<?php echo APP_URL ?>/item/index">成功添加<?php <span style="color: #0000ff;">echo <span style="color: #800080;">$count</span> ?>条记录,点击返回</a>
view.php,查看单条记录,内容:
update.php,更改记录,内容:
<a style="color: #0000ff;">class="big" href="<?php echo APP_URL ?>/item/index">成功修改<?php <span style="color: #0000ff;">echo <span style="color: #800080;">$count</span> ?>项,点击返回</a>
delete.php,删除记录,内容:
<a href="<?php%20echo%20APP_URL%20?>/item/index">成功删除<?php <span style="color: #0000ff;">echo <span style="color: #800080;">$count</span> ?>项,点击返回</a>
这样,在浏览器中访问 myphp程序:http://localhost/myphp/item/index/,就可以看到效果了。