PHP 的 MVC 框架参考实现
MVC 模式在 Java 中表现的尤为出众,不光 Swing 是按照 MVC 来设计的,而且 Java 的 Web 框架也是 MVC1、MVC2 的。MVC 模式对于开发维护确有许多好处,所以 PHP 的框架,如 Zend、Symfony,PHP 的产品 Wordpress 和 Joomla 都应用了 MVC 模式。PHP 不像 Servlet 那样有成熟的规范,如 web.xml、servlet、filter 等,但变换着一些把式同样能实现出优雅的 MVC 模式。这里简单介绍一下 PHP 是如何实现 MVC 模式,参照了了 Zend 的实现,我觉得还有许多改进的地方。说明的时候会拿它的各部分与 Struts1 的 MVC 相比较。
在 HTTP 环境中的 MVC 模式一句话描述就是:控制器根据 URI,把请求转给相应的 Action,由 Action 调用模型方法处理或得到数据,再选择相应的视图呈现界面。用过 Struts1 的请保留一些 Struts1 的实现原理,现在来看 PHP 的实现方式。
本例参考了 《PHP 高级程序设计 模式、框架与测试》一书中关于 MVC 的介绍,因本人受 Struts 等 MVC 的影响,所以对原书中的示例进行了大刀阔斧、面目全非的改造。代码结构如下:

lib 目录中为本 MVC 的核心代码,application 目录中为应用代码,index.php 为入口文件兼具引导功能。
第一要素:统一口径(/index.php):
要让 HTTP 请求都能进入到我们的 MVC 框架来,需要流经一个统一的入口,这里就是 /index.php 文件,也就是必须全部用 http://localhost/MvcSample/index.php/controller=user&name=Unmi.. 这样的方式来访问,你可以用某种方式让其他的 php 文件被禁止直接访问。
在 Struts1 中,是在 web.xml 中配置由 ActionServlet 处理所有的 *.do 的请求,Struts2 也是在 web.xml 中配置由 FilterDispatcher 来拦截所有的请求。而 PHP 没有像 Java Web 那么多的规范,但可以借助于 mod_rewrite 模块,将某些请求转发给 /index.php 处理,具体做法是在应用的根目录下建立一个 .htaccess 文件,内容为:
RewriteEngine On
RewriteRule !\.(js|gif|png|css)$ index.php
意思为除图片、js、css 文件都把请求定向给 index.php,当然 .htaccess 还是应该进行更优化配置的,还有就是在 Apache 中要启用 mod_rewrite 模块。有了 .htaccess 后,你随便输入些像 http://localhost/MvcSample/sfsf/sdfsdf 的地址都不会是 404 错误,而是全部转向到了 /index.php。/index.php 便成了一个统一的入口,后面发生的事情可受控了。
来看看 index.php 文件的内容:
第二要素:前端控制器(lib/front.php)
在 index.php 中调用了前端控制器 FrontController 的 route() 路由方法,根据 URL 中的参数找到相应的控制器,调用相应的方法,获得数据并使用视图显示出来。例如有 URL http://localhost/MvcSample/index.php?controller=user&method=user_list&name=Unmi,在 route() 方法中就会调用 UserController 的 user_list 方法执行业务逻辑,获得相应的数据,最后使用视图 views/user/list.php 显示页面。
需要特别注意的是控制器名与脚本文件、视图文件的对应规则,可自行约定。脚本文件需要临时 include 进来,你也可以用 SPL 的自动加载机制来加载文件。看代码 front.php
这个 FrontController 的功能和 Struts1 的 ActiveServlet、DispatchAction 十分相似。因为 PHP 没有应用范围内全局的东西,像 Servlet 的 JVM,也没有像 JSP/ASP 那种 application 的变量,所以很多 PHP 的 MVC 框架都基本不用配置文件来配置 URL--Controller(Action)--View 间的映射关系。其实如果应用上内存高速缓存,如 Memcached,或内存数据数据库,如 Sqlit,我想也可以做出像 Struts 那样的 MVC 框架的。
第三要素:视图呈现数据(lib/view)
在 FrontController 的 route() 方法最后要调用视图来渲染数据,显示出来。视图最终要关联到某个模板,可以是 php 文件,也可以用 php 专门的模板组件。看看 view.php 的代码,继承自 ArrayObject 来存放显示数据,
第四要素:所有控制器的基类(controller.php)
你自己写的所有的控制器都必须继承自它,execute($params) 是 controller 的默认执行方法。这个控制器相当于 Struts1 中的 Action 类。
MVC 中的模型没什么好说的,它其实不影响到框架的实现,无论用什么框架,模型都是必不可少的。好啦,到现在,PHP MVC 的基础框架就完成了,开始来应用它了,应用代码在 application 目录中,还是分布来看具体的应用步骤:
第一步:实现自己的控制器(application/controllers/user_controller.php)
请求参数中没有 method 参数时执行 execute() 方法,当 method=user_list 时执行上面的 user_list 方法,它们分别转向到不同的模板文件,一个是 views/user/index.php,另一个是 views/user/list.php 文件。
第二:实现你的 Model(application/model/user_model.php)
这一步,前面也提过,没什么好说的,那是业务相关的东西,该怎么就怎么。
第三:显示界面的模板文件(application/views/user/index.php 和 application/views/user/list.php)
模板仍然是属于视图的范畴,是视图的某种表现方式之一,这里用到的是 php 文件作为模板,请看那两文件内容:
注意,在这两个 PHP 文件中是如何取得后台数据的。在控制器或模型类中可以有多个实现方法,而一个网站的 PHP 显示模板文件应该相对较多,所以模板文件进一步按控制器名再分一级目录来存放。
最后:看看执行效果
假设你你的 PHP 站点部署后要通过 http://localhost/MvcSample/index.php 来访问,比如直接把 MvcSample 目录拷到了 Apache 的 htdocs 目录中。
URL: http://localhost/MvcSample/index.php?controller=user
页面显示:
Hello, I am Unmi!
说明:首先进到 index.php,调用 FrontController.route() 方法,根据 controller=user,且无 method 参数,定位到 UserController.execute() 方法,最后用 views/user/index.php 来显示数据。
URL: http://localhost/MvcSample/index.php?controller=user&method=user_list&name=Unmi
页面显示:
用户列表:
说明:首先进到 index.php,调用 FrontController.route() 方法,根据 controller=user&method=user_list 参数,定位到 UserController.user_list() 方法,最后用 views/user/list.php 来显示数据。
如果是用 PHP 实现一个像 Struts 那样的 MVC 框架,我想应该比 Struts 来得简单。有闲时值得一试。 永久链接 https://yanbin.blog/php-mvc-reference-implementation/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
在 HTTP 环境中的 MVC 模式一句话描述就是:控制器根据 URI,把请求转给相应的 Action,由 Action 调用模型方法处理或得到数据,再选择相应的视图呈现界面。用过 Struts1 的请保留一些 Struts1 的实现原理,现在来看 PHP 的实现方式。
本例参考了 《PHP 高级程序设计 模式、框架与测试》一书中关于 MVC 的介绍,因本人受 Struts 等 MVC 的影响,所以对原书中的示例进行了大刀阔斧、面目全非的改造。代码结构如下:

lib 目录中为本 MVC 的核心代码,application 目录中为应用代码,index.php 为入口文件兼具引导功能。
第一要素:统一口径(/index.php):
要让 HTTP 请求都能进入到我们的 MVC 框架来,需要流经一个统一的入口,这里就是 /index.php 文件,也就是必须全部用 http://localhost/MvcSample/index.php/controller=user&name=Unmi.. 这样的方式来访问,你可以用某种方式让其他的 php 文件被禁止直接访问。
在 Struts1 中,是在 web.xml 中配置由 ActionServlet 处理所有的 *.do 的请求,Struts2 也是在 web.xml 中配置由 FilterDispatcher 来拦截所有的请求。而 PHP 没有像 Java Web 那么多的规范,但可以借助于 mod_rewrite 模块,将某些请求转发给 /index.php 处理,具体做法是在应用的根目录下建立一个 .htaccess 文件,内容为:
RewriteEngine On
RewriteRule !\.(js|gif|png|css)$ index.php
意思为除图片、js、css 文件都把请求定向给 index.php,当然 .htaccess 还是应该进行更优化配置的,还有就是在 Apache 中要启用 mod_rewrite 模块。有了 .htaccess 后,你随便输入些像 http://localhost/MvcSample/sfsf/sdfsdf 的地址都不会是 404 错误,而是全部转向到了 /index.php。/index.php 便成了一个统一的入口,后面发生的事情可受控了。
来看看 index.php 文件的内容:
1<?php
2//导入组件
3require_once('lib/front.php');
4require_once('lib/controller.php');
5require_once('lib/view.php');
6
7//初始化前端控制器
8$front = new FrontController();
9
10//转发请求到相应的控制器
11//route 就是路由的意思,就是分布请求,Struts 里是用 Dispatch 的概念
12$front->route();
13
14//显示页面数据
15echo $front->getBody();第二要素:前端控制器(lib/front.php)
在 index.php 中调用了前端控制器 FrontController 的 route() 路由方法,根据 URL 中的参数找到相应的控制器,调用相应的方法,获得数据并使用视图显示出来。例如有 URL http://localhost/MvcSample/index.php?controller=user&method=user_list&name=Unmi,在 route() 方法中就会调用 UserController 的 user_list 方法执行业务逻辑,获得相应的数据,最后使用视图 views/user/list.php 显示页面。
需要特别注意的是控制器名与脚本文件、视图文件的对应规则,可自行约定。脚本文件需要临时 include 进来,你也可以用 SPL 的自动加载机制来加载文件。看代码 front.php
1<?php
2
3class FrontController{
4 protected $_controller, $_method, $_params, $_body;
5
6 public function __construct(){
7 $this->_controller = $_REQUEST['controller'];
8 $this->_method = $_REQUEST['method'];
9 $this->_params = $_REQUEST;
10 }
11
12 public function route(){
13 //引入控制器文件
14 include dirname(__FILE__) .'/../application/controllers/'.$this->_controller.'_controller.php';
15 $controller_class_name = ucwords($this->_controller . 'Controller');
16 $rc = new ReflectionClass($controller_class_name);
17 $controller = $rc->newInstance();
18 $view_file;
19 if($rc->isSubclassOf('AbstractController')){
20 if(!empty($this->_method)){
21 if($rc->hasMethod($this->_method)){
22 $method = $rc->getMethod($this->_method);
23 $view_file = $method->invoke($controller,$this->_params);
24 }else{
25 throw new Exception("Method " . $this->_method . " not exists");
26 }
27 }else{
28 $view_file = $controller->execute($this->_params);
29 }
30 $view = new View();
31 //返回数据是数组则分散以键值对存到 View 里
32 if(is_array($controller->get_data())){
33 foreach($controller->get_data() as $key=>$val){
34 $view[$key] = $val;
35 }
36 }else{ //非数组则以 data 键存到 View 里
37 $view->data = $controller->get_data();
38 }
39 $result = $view->render($view_file);
40 $this->setBody($result);
41 }else{
42 throw new Exception("Not Extends AbstractController");
43 }
44 }
45
46 public function getController(){
47 return $this->_controller;
48 }
49
50 public function getParams(){
51 return $this->_params;
52 }
53
54 public function getBody(){
55 return $this->_body;
56 }
57
58 public function setBody($body){
59 $this->_body = $body;
60 }
61}这个 FrontController 的功能和 Struts1 的 ActiveServlet、DispatchAction 十分相似。因为 PHP 没有应用范围内全局的东西,像 Servlet 的 JVM,也没有像 JSP/ASP 那种 application 的变量,所以很多 PHP 的 MVC 框架都基本不用配置文件来配置 URL--Controller(Action)--View 间的映射关系。其实如果应用上内存高速缓存,如 Memcached,或内存数据数据库,如 Sqlit,我想也可以做出像 Struts 那样的 MVC 框架的。
第三要素:视图呈现数据(lib/view)
在 FrontController 的 route() 方法最后要调用视图来渲染数据,显示出来。视图最终要关联到某个模板,可以是 php 文件,也可以用 php 专门的模板组件。看看 view.php 的代码,继承自 ArrayObject 来存放显示数据,
1<?php
2
3class View extends ArrayObject{
4 public function __construct(){
5 parent::__construct(array(), ArrayObject::ARRAY_AS_PROPS);
6 }
7
8 public function render($file){
9 ob_start();
10 include($file);
11 return ob_get_clean();
12 }
13}第四要素:所有控制器的基类(controller.php)
1<?php
2//你的自定义控制器要继承这个抽象类
3abstract class AbstractController{
4 protected $_data; //存储数据
5
6 //默认的执行方法
7 abstract function execute($params);
8
9 function get_data(){
10 return $this->_data;
11 }
12}你自己写的所有的控制器都必须继承自它,execute($params) 是 controller 的默认执行方法。这个控制器相当于 Struts1 中的 Action 类。
MVC 中的模型没什么好说的,它其实不影响到框架的实现,无论用什么框架,模型都是必不可少的。好啦,到现在,PHP MVC 的基础框架就完成了,开始来应用它了,应用代码在 application 目录中,还是分布来看具体的应用步骤:
第一步:实现自己的控制器(application/controllers/user_controller.php)
1<?php
2//用户控制器
3class UserController extends AbstractController {
4
5 //默认执行方法,调用模型方法获取数据
6 //页面显示的数据设置给 $_data 进而传递给 View
7 public function execute($params){
8 include(dirname(__FILE__) . '/../models/user_model.php');
9 $user_model = new UserModel();
10 $this->_data = array('name'=>$user_model->wo_am_i());
11 return dirname(__FILE__) . '/../views/user/index.php';
12 }
13
14 //URL 中用 method=user_list 指定了参数则会执行这个方法
15 public function user_list($params){
16 include(dirname(__FILE__) . '/../models/user_model.php');
17 $user_model = new UserModel();
18 $this->_data = $user_model->get_user_list($params['id']);
19 return dirname(__FILE__) . '/../views/user/list.php';
20 }
21}请求参数中没有 method 参数时执行 execute() 方法,当 method=user_list 时执行上面的 user_list 方法,它们分别转向到不同的模板文件,一个是 views/user/index.php,另一个是 views/user/list.php 文件。
第二:实现你的 Model(application/model/user_model.php)
1<?php
2//用户模型,就是些业务方法,严格来讲模型是些数据对象
3class UserModel {
4 public function wo_am_i(){
5 return "Unmi";
6 }
7
8 public function get_user_list($id){
9 $array = array('Unmi','Fantasia','Kypfos');
10 return $array;
11 }
12}这一步,前面也提过,没什么好说的,那是业务相关的东西,该怎么就怎么。
第三:显示界面的模板文件(application/views/user/index.php 和 application/views/user/list.php)
模板仍然是属于视图的范畴,是视图的某种表现方式之一,这里用到的是 php 文件作为模板,请看那两文件内容:
1<!-- application/views/user/index.php -->
2Hello, I am <?php echo $this->name; ?>!
1<!-- application/views/user/list.php -->
2用户列表:
3<table border="1" style="border-collapse: collapse">
4<?php
5 foreach($this as $key=>$value){
6 echo "<tr><td>" . ($key+1) . "</td><td>$value</td></tr>";
7 }
8?>
9</table>
注意,在这两个 PHP 文件中是如何取得后台数据的。在控制器或模型类中可以有多个实现方法,而一个网站的 PHP 显示模板文件应该相对较多,所以模板文件进一步按控制器名再分一级目录来存放。
最后:看看执行效果
假设你你的 PHP 站点部署后要通过 http://localhost/MvcSample/index.php 来访问,比如直接把 MvcSample 目录拷到了 Apache 的 htdocs 目录中。
URL: http://localhost/MvcSample/index.php?controller=user
页面显示:
Hello, I am Unmi!
说明:首先进到 index.php,调用 FrontController.route() 方法,根据 controller=user,且无 method 参数,定位到 UserController.execute() 方法,最后用 views/user/index.php 来显示数据。
URL: http://localhost/MvcSample/index.php?controller=user&method=user_list&name=Unmi
页面显示:
用户列表:
| 1 | Unmi |
| 2 | Fantasia |
| 3 | Kypfos |
说明:首先进到 index.php,调用 FrontController.route() 方法,根据 controller=user&method=user_list 参数,定位到 UserController.user_list() 方法,最后用 views/user/list.php 来显示数据。
如果是用 PHP 实现一个像 Struts 那样的 MVC 框架,我想应该比 Struts 来得简单。有闲时值得一试。 永久链接 https://yanbin.blog/php-mvc-reference-implementation/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。