使用PHP搭建自己的MVC框架

一、什么是MVC

MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。

MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。软件系统通过对自身基本部份分离的同时也赋予了各个基本部分应有的功能。专业人员可以通过自身的专长分组:

  • (控制器Controller)- 负责转发请求,对请求进行处理。

  • (视图View) – 界面设计人员进行图形界面设计。

  • (模型Model) – 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。

mvc
模型(Model) “数据模型”(Model)用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型”有对数据直接访问的权力,例如对数据库的访问。“模型”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。但是模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解在数据模型上发生的改变。

视图(View) 视图层能够实现数据有目的的显示(理论上,这不是必需的)。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能,视图需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册。

控制器(Controller) 控制器起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改变。

二、为什么要自己开发MVC框架

网络上有大量优秀的MVC框架可供使用,本教程并不是为了开发一个全面的、终极的MVC框架解决方案,而是将它看作是一个很好的从内部学习PHP的机会,在此过程中,你将学习面向对象编程和设计模式,并学习到开放中的一些注意事项。

更重要的是,你可以完全控制你的框架,并将你的想法融入到你开发的框架中。虽然不一定是做好的,但是你可以按照你的方式去开发功能和模块。

三、开始开发自己的MVC框架

在开始开发前,让我们先来把项目建立好,假设我们建立的项目为todo,那么接下来的第一步就是把目录结构先设置好。

todo

虽然在这个教程中不会使用到上面的所有的目录,但是为了以后程序的可拓展性,在一开始就把程序目录设置好使非常必要的。下面就具体说说每个目录的作用:

  • application – 存放程序代码

  • config – 存放程序配置或数据库配置

  • db – 用来存放数据库备份内容

  • library – 存放框架代码

  • public – 存放静态文件

  • scripts – 存放命令行工具

  • tmp – 存放临时数据

在目录设置好以后,我们接下来就要来顶一下一些代码的规范:

  1. MySQL的表名需小写并采用复数形式,如items,cars

  2. 模块名(Models)需首字母大写,并采用单数模式,如Item,Car

  3. 控制器(Controllers)需首字母大写,采用复数形式并在名称中添加“Controller”,如ItemsController, CarsController

  4. 视图(Views)采用复数形式,并在后面添加行为作为文件,如:items/view.php, cars/buy.php

上述的一些规则是为了能在程序钟更好的进行互相的调用。接下来就开始真正的编码了。

第一步将所有的的请求都重定向到public目录下,解决方案是在todo文件下添加一个.htaccesss文件,文件内容为:

 RewriteEngine on
RewriteRule    ^$    public/    [L]
RewriteRule    (.*) public/$1    [L] 

在我们把所有的请求都重定向到public目录下以后,我们就需要将所有的数据请求都再重定向到public下的index.php文件,于是就需要在public文件夹下也新建一个.htaccess文件,文件内容为:

 RewriteEngine On
#如果文件存在就直接访问目录不进行RewriteRule
RewriteCond %{REQUEST_FILENAME} !-f
#如果目录存在就直接访问目录不进行RewriteRule
RewriteCond %{REQUEST_FILENAME} !-d
#将所有其他URL重写到 index.php/URL
RewriteRule ^(.*)$ index.php?url=$1 [PT,L] 

这么做的主要原因有:

  1. 可以使程序有一个单一的入口,将所有除静态程序以外的程序都重定向到index.php上;

  2. 可以用来生成利于SEO的URL,想要更好的配置URL,后期可能会需要URL路由,这里先不做介绍了。

做完上面的操作,就应该知道我们需要做什么了,没错!在public目录下添加index.php文件,文件内容为:

注意上面的PHP代码中,并没有添加PHP结束符号”?>”,这么做的主要原因是:对于只包含PHP代码的文件,结束标志(“?>”)最好不存在,PHP自身并不需要结束符号,不添加结束符号可以很大程度上防止末尾被添加额外的注入内容,让程序更加安全。

在index.php中,我们对library文件夹下的bootstrap.php发起了请求,那么bootstrap.php这个启动文件中到底会包含哪些内容呢?

以上文件都可以直接在index.php文件中引用,我们这么做的原因是为了在后期管理和拓展中更加的方便,所以把需要在一开始的时候就加载运行的程序统一放到一个单独的文件中引用。

先来看看config文件下的config .php文件,该文件的主要作用是设置一些程序的配置项及数据库连接等,主要内容为:

应该说config.php涉及到的内容并不多,不过是一些基础数据的一些设置,再来看看library下的共用文件shared.php应该怎么写。

 $var) {
                  if ($var === $GLOBALS[$key]) {
                      unset($GLOBALS[$key]);
                  }
               }
           }
       }
    }

    /* 主请求方法,主要目的拆分URL请求 */
    function callHook() {
        global $url;
        $urlArray = array();
        $urlArray = explode("/",$url);
        $controller = $urlArray[0];
        array_shift($urlArray);
        $action = $urlArray[0];
        array_shift($urlArray);
        $queryString = $urlArray;
        $controllerName = $controller;
        $controller = ucwords($controller);
        $model = rtrim($controller, 's');
        $controller .= 'Controller';
        $dispatch = new $controller($model,$controllerName,$action);
        if ((int)method_exists($controller, $action)) {
           call_user_func_array(array($dispatch,$action),$queryString);
        } else {
           /* 生成错误代码 */
        }
    }

    /* 自动加载控制器和模型 */
    function __autoload($className) {
        if (file_exists(ROOT . DS . 'library' . DS . strtolower($className) . '.class.php')) {
            require_once(ROOT . DS . 'library' . DS . strtolower($className) . '.class.php');
        } else if (file_exists(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '.php')) {
            require_once(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '.php');
        } else if (file_exists(ROOT . DS . 'application' . DS . 'models' . DS . strtolower($className) . '.php')) {
            require_once(ROOT . DS . 'application' . DS . 'models' . DS . strtolower($className) . '.php');
        } else {
           /* 生成错误代码 */
        }
    }

    setReporting();
    removeMagicQuotes();
    unregisterGlobals();
    callHook();

接下来的操作就是在library中建立程序所需要的基类,包括控制器、模型和视图的基类。

新建控制器基类为controller.class.php,控制器的主要功能就是总调度,具体具体内容如下:

_controller = $controller;
            $this->_action = $action;
            $this->_model = $model;
            $this->$model =& new $model;
            $this->_template =& new Template($controller,$action);
        }
        function set($name,$value) {
            $this->_template->set($name,$value);
        }
        function __destruct() {
            $this->_template->render();
        }
    }

新建控制器基类为model.class.php,考虑到模型需要对数据库进行处理,所以可以新建一个数据库基类sqlquery.class.php,模型去继承sqlquery.class.php。

新建sqlquery.class.php,代码如下:

_dbHandle = @mysql_connect($address, $account, $pwd);
            if ($this->_dbHandle != 0) {
               if (mysql_select_db($name, $this->_dbHandle)) {
                   return 1;
               }else {
                   return 0;
               }
            }else {
               return 0;
            }
         }
        /** 中断数据库连接 **/
        function disconnect() {
             if (@mysql_close($this->_dbHandle) != 0) {
                 return 1;
             } else {
                 return 0;
             }
        }
        /** 查询所有数据表内容 **/
        function selectAll() {
            $query = 'select * from `'.$this->_table.'`';
            return $this->query($query);
        }
        /** 查询数据表指定列内容 **/
        function select($id) {
            $query = 'select * from `'.$this->_table.'` where `id` = \''.mysql_real_escape_string($id).'\'';
            return $this->query($query, 1);
        }
        /** 自定义SQL查询语句 **/
        function query($query, $singleResult = 0) {
            $this->_result = mysql_query($query, $this->_dbHandle);
            if (preg_match("/select/i",$query)) {
                 $result = array();
                 $table = array();
                 $field = array();
                 $tempResults = array();
                 $numOfFields = mysql_num_fields($this->_result);
                 for ($i = 0; $i < $numOfFields; ++$i) {
                      array_push($table,mysql_field_table($this->_result, $i));
                      array_push($field,mysql_field_name($this->_result, $i));
                  }
                 while ($row = mysql_fetch_row($this->_result)) {
                     for ($i = 0;$i < $numOfFields; ++$i) {
                        $table[$i] = trim(ucfirst($table[$i]),"s");
                        $tempResults[$table[$i]][$field[$i]] = $row[$i];
                     }
                     if ($singleResult == 1) {
                         mysql_free_result($this->_result);
                         return $tempResults;
                     }
                     array_push($result,$tempResults);
                 }
                 mysql_free_result($this->_result);
                 return($result);
             }
          }
         /** 返回结果集行数 **/
        function getNumRows() {
            return mysql_num_rows($this->_result);
        }
        /** 释放结果集内存 **/
        function freeResult() {
            mysql_free_result($this->_result);
        }
       /** 返回MySQL操作错误信息 **/
       function getError() {
           return mysql_error($this->_dbHandle);
       }
    }

新建model.class.php,代码如下:

connect(DB_HOST,DB_USER,DB_PASSWORD,DB_NAME);
            $this->_model = get_class($this);
            $this->_table = strtolower($this->_model)."s";
        }
        function __destruct() {
        }
    }

新建视图基类为template.class.php,具体代码如下:

_controller = $controller;
           $this->_action =$action;
       }
       /* 设置变量 */
       function set($name,$value) {
            $this->variables[$name] = $value;
       }
       /* 显示模板 */
       function render() {
           extract($this->variables);
           if (file_exists(ROOT.DS. 'application' .DS. 'views' .DS. $this->_controller .DS. 'header.php')) {
               include(ROOT.DS. 'application' .DS. 'views' .DS. $this->_controller .DS. 'header.php');
           } else {
               include(ROOT.DS. 'application' .DS. 'views' .DS. 'header.php');
           }
           include (ROOT.DS. 'application' .DS. 'views' .DS. $this->_controller .DS. $this->_action . '.php');
           if (file_exists(ROOT.DS. 'application' .DS. 'views' .DS. $this->_controller .DS. 'footer.php')) {
               include (ROOT.DS. 'application' .DS. 'views' .DS. $this->_controller .DS. 'footer.php');
           } else {
               include (ROOT.DS. 'application' .DS. 'views' .DS. 'footer.php');
           }
        }
    }

做完了以上这么多操作,基本上整个MVC框架已经出来了,下面就该制作我们的站点了。我们要做的站点其实很简单,一个ToDo程序。

首先是在我们的/application/controller/ 目录下面新建一个站点控制器类为ItemsController,命名为itemscontroller.php,内容为:

set('title',$name.' - My Todo List App');
           $this->set('todo',$this->Item->select($id));
       }
       function viewall() {
           $this->set('title','All Items - My Todo List App');
           $this->set('todo',$this->Item->selectAll());
       }
       function add() {
           $todo = $_POST['todo'];
           $this->set('title','Success - My Todo List App');
           $this->set('todo',$this->Item->query('insert into items (item_name) values (\''.mysql_real_escape_string($todo).'\')'));
       }
       function delete($id) {
           $this->set('title','Success - My Todo List App');
           $this->set('todo',$this->Item->query('delete from items where id = \''.mysql_real_escape_string($id).'\''));
       }
    }

接下来就是先建站点的模型,在我们的/application/model/ 目录下面先建一个站点模型类为Item,内容直接继承Model,代码如下:

最后一步是设置我们站点的视图部分,我们现在/application/views/目录下新建一个items的文件夹,再在items文件夹下建立与控制器重Action相同的文件,分别为view.php,viewall.php,add.php,delete.php,考虑到这么页面中可能需要共用页首和页尾,所以再新建两个文件,命名为header.php,footer.php,每个文件的代码如下:

view.php文件:查看单条待处理事务

Delete this item

viewall.php文件:查看所有待处理事务



">

add.php文件:添加待处理事务

Todo successfully added. Click here to go back.

delete.php文件:删除事务

Todo successfully deleted. Click here to go back.

header.php:页首文件

<?php echo $title?>

My Todo-List App

footer.php:页尾文件

当然还有一个必不可少的操作就是在数据中中建立一张表,具体代码如下:

CREATE TABLE IF NOT EXISTS `items` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `item_name` varchar(255) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=17 ;

至此一个使用MVC开发的网站就开发完成了,你现在可以通过访问http://localhost/todo/items/viewall 查看新建的站点。

更多相关文章
  • 1. 前言         在 JavaWeb之搭建自己的MVC框架(一) 中我们完成了URL到JAVA后台方法的最基本跳转.但是实际操作中会发现有一个不方便的地方,现在在com.mvc.controller包中只有一个SayController类,如果我们想增加一个新的***Controller类 ...
  • 1. 前言         在前两节的内容中,我们完成了一个基本的框架搭建.但是如果我们在前端请求中增加参数,我们要怎么传递到后台方法呢?接下来我们就来研讨这部分内容. 2. 实现         (1)首先我们增加一个新的注解ParamMapping,用来给方法的参数标注其对应的前端参数名称. p ...
  • JavaWeb之搭建自己的MVC框架一
    1. 介绍         MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时, ...
  • 转10个最佳的Node.js的MVC框架
    10 个最佳的 Node.js 的 MVC 框架 oschina 发布于: 2014年02月24日 (33评) 分享到:  收藏 +322 Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台, 用来方便地搭建快速的, 易于扩展的网络应用· Node.js 借助事件驱动 ...
  • 通过RubyOnRails框架来更好的理解MVC框架
    通过Ruby On Rails 框架来更好的理解MVC框架 1.背景 因为我在学习软件工程课程的时候,对于 MVC 框架理解不太深入,只是在理论层面上掌握,但是不知道如何在开发中使用 MVC框架.如今我了解到一款十分优秀的框架 Rails,而且爱不释手,所以推荐给大家,帮助你更好的了解 MVC框架. ...
  • 由于.net MVC 的controller 依赖于HttpContext,而我们在上一篇中的沙箱模式已经把一次http请求转换为反射调用,并且http上下文不支持跨域,所以我们要重造一个controller. 我们在写mvc项目的时候经常会用到ViewBag.ViewData,那我们就先声明这两个 ...
  •      上周五写了一个实现原理篇,在评论中看到有朋友也遇到了我的问题,真的是有种他乡遇知己的感觉,整个系列我一定会坚持写完,并在最后把代码开源到git中.上一篇文章很多人看了以后,都表示不解,觉得不知道我到底要干什么,可能就像隔行如隔山吧,就像做移动端开发的人很少去考虑分布式中的通信一样.大家都知 ...
  • 产品前端重构TypeScript、MVC框架设计
    最近两周完成了对公司某一产品的前端重构,本文记录重构的主要思路及相关的设计内容. 公司期望把某一管理类信息系统从项目代码中抽取.重构为一个可复用的产品.该系统的前端是基于 ExtJs 5 进行构造的,后端是基于 Asp.net MVC 提供的 REST 数据接口.同时,希望通过这次重构,不但能将其本 ...
一周排行
  • DNS(Domain Name System)作用管理主机的 "户籍"-主机名:IP 是一个分布式数据库系统,DNS服务器的起源,最早的主机解析,依靠hosts文件,有NIC(Network In ...
  • 1.汇报工作说结果 不要告诉老板工作过程多艰辛,你多么的不容易.举重若轻的人老板最喜欢,一定要把结果给老板,结果思维是第一思维. 2.请示工作说方案 不要让老板做问答题,而是要让老板做选择题.请示工作至少给老板两个方 ...
  • ubuntu更改用户名密码
    提示:)这里的时候按下ESC键来进入启动菜单:即可进入下列界面:当引导到Recovery ...
  • 详细介绍策略路由和路由策略的区别
    详细介绍策略路由和路由策略的区别 作者: 烟雨楼 出处:百度博客  ( 1 ) 砖  (  ...
  • MySQL外键的作用:保持数据一致性,完整性,主要目的是控制存储在外键表中的数据. 使两张表形成关联,外键只能引用外表中的列的值!是否需要使用外键:外键的确是有很多好处,但现在也流行反外键的操作,比如我的 医药吧网 ...
  • NppAStyle是一个NotePad++的插件,用于格式化Notepad中编辑的c.c++.C#.java代码.NppAStyle在内部使用Astyle来格式化代码,是一个很简单,但很实用的Notepad++插件. ...
  • TheCProgrammingLanguage(K&R)扣细节随记
    各种糟糕,入坑这么久才开始看K&R的The C Programming Langu ...
  • 又是一年一度的七夕节了,早上下了几滴雨,难道天上这二位早上就见面了?不过,这织女的眼泪也太少了吧,才几滴而已,她的泪已经流干了?或者两人见面的次数已经挺多了,不再激动了?呵呵,纯属瞎猜啊!身边的人好像还比较乐忠于过这 ...
  • impala下Impala/fe/src/main/java/com/cloudera/impala/service/Frontend.java  中存在SQL语句解析功能块其中analysisCtx.analyze ...
  • 刚结束一个项目,又进入另一个项目,算是一个新的开端吧.又重复从调研开始,重写一个新的故事吧....................