资讯专栏INFORMATION COLUMN

用PHP搭建自己的MVC框架

cyrils / 1892人阅读

摘要:但是模型中数据的变化一般会通过一种刷新机制被公布。它处理事件并作出响应。事件包括用户的行为和数据模型上的改变。三开始开发自己的框架在开始开发前,让我们先来把项目建立好,假设我们建立的项目为,那么接下来的第一步就是把目录结构先设置好。

现如今市面上有许多PHP框架,像 ThinkPHP、YII、Laravel,那么如何自己搭建一个PHP框架呢?这里有一篇博文写的非常好,特意转载过来,供朋友们研究,原文请戳详情。

一、什么是MVC

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

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

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

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

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

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

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

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

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

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

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

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

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

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

application – 存放程序代码
config – 存放程序配置或数据库配置
db – 用来存放数据库备份内容
library – 存放框架代码
public – 存放静态文件
scripts – 存放命令行工具
tmp – 存放临时数据
在目录设置好以后,我们接下来就要来顶一下一些代码的规范:

MySQL的表名需小写并采用复数形式,如items,cars
模块名(Models)需首字母大写,并采用单数模式,如Item,Car
控制器(Controllers)需首字母大写,采用复数形式并在名称中添加“Controller”,如 ItemsController, CarsController
视图(Views)采用复数形式,并在后面添加行为作为文件,如:items/view.php, cars/buy.php

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

第一步将所有的的请求都重定向到 public 目录下,解决方案是在 Frame 文件下添加一个.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]

这么做的主要原因有:

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

可以用来生成利于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 应该怎么写。

的method属性为post的时候应该用$_POST["user_name"]和$_POST["user_pass"]) 
 * 当register_globals=On的时候,下一个程序可以直接使用$user_name和$user_pass来接受值。
 * 顾名思义,register_globals的意思就是注册为全局变量,所以当On的时候,传递过来的值会被直接的注册为全局变量直接使用,而Off的
 * 时候,我们需要到特定的数组里去得到它。所以,碰到上边那些无法得到值的问题的朋友应该首先检查一下你的register_globals的设置和
 * 你获取值的方法是否匹配。
 *
 * 那我们为什么要使用Off呢?
 * 原因有2: 
 * 1、php以后的新版本默认都用Off,虽然你可以设置它为On,但是当你无法控制服务器的时候,你的代码的兼容性就成为一个大问题,所以
 *,你最好从现在就开始用Off的风格开始编程 2、这里有两篇文章介绍为什么要Off而不用On 
 */
function unregisterGlobals()
{
    if(ini_get("register_globals"))
    {
         $array = array("_SESSION","_POST","_GET","_COOKIE","_REQUEST","_SERVER","_ENV","_FILES");
           foreach ($array as $value)
           {
                 // $GLOBALS 可以打印所有的全局变量
               foreach ($GLOBALS[$value] as $key => $var)
               {
                  if ($var === $GLOBALS[$key])
                  {
                      unset($GLOBALS[$key]);
                  }
               }
           }
    }

}

/**
 * 请求方法,主要目的拆分URL请求
 *
 * 相关学习:
 * 调用回调函数,并把一个数组参数作为回调函数的参数
 * call_user_func_array(array($obj, $method_name), $params);
 * call_user_method_array ( string $method_name , object &$obj , array $params )
 */
function callHook()
{
    global $url;

    $urlArray   = array();
    $urlArray   = explode("/", $url);

    // 获取控制器 controller
    $controller = $urlArray[0];

    // 删除第一个元素
    array_shift($urlArray);

    // 获取动作 Action
    $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
    {
        /* 生成错误代码 */
        exit("该控制器不存在:". $controller);
    }
}

/**
 * 自动加载控制器和模型
 */
function __autoload($className)
{
    
    if(file_exists(ROOT . DS . "library" . DS . strtolower($className) . ".class.php"))
    {
        // 加载核心库 library
        require_once(ROOT . DS . "library" . DS . strtolower($className) . ".class.php");
    } 
    else if(file_exists(ROOT . DS . "application" . DS . "controllers" . DS . strtolower($className) . ".php"))
    {
        // 加载控制器 application 目录下
         require_once(ROOT . DS . "application" . DS . "controllers" . DS . strtolower($className) . ".php");
    }
    else if(file_exists(ROOT . DS . "application" . DS . "models" . DS . strtolower($className) . ".php"))
    {
        // 加载控制器 application 目录下
         require_once(ROOT . DS . "application" . DS . "models" . DS . strtolower($className) . ".php");
    }
    else
    {
         /* 生成错误代码 */
        exit("类名不存在:" . $className);
    }

}


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

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

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

_controller =  $controller;
            $this->_action     =  $action;
            $this->_model      =  $model;
            $this->$model      =& new $model;  // php默认为传值传递,这里默认为地址传递,即引用同一个对象
            $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/Frame/items/... 查看新建的站点。

框架核心小结:

1、单一入口原则

在项目Frame 下写.htaccess 文件指定到 pulibc 目录, 然后在 public 目录下重写入口模块 .htaccess 指定到 index.php 文件

这样只要访问该项目,就会指定到 Frame/public/index.php 目录下

2、加载初始配置文件

定义根路径,分隔符,等常量
define("DEBUG", TRUE);

3、封装核心类
sql.php
model.php (该类继承sql.php)
controller.php

4、其他的类都继承核心类


注:本文为转载,原文地址:请戳这里

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/23045.html

相关文章

  • 后端API从入门到放弃指北

    摘要:菜鸟教程框架中文手册入门目标使用搭建通过对数据增删查改没了纯粹占行用的拜 后端API入门学习指北 了解一下一下概念. RESTful API标准] 所有的API都遵循[RESTful API标准]. 建议大家都简单了解一下HTTP协议和RESTful API相关资料. 阮一峰:理解RESTful架构 阮一峰:RESTful API 设计指南 RESTful API指南 依赖注入 D...

    Jeffrrey 评论0 收藏0
  • 后端API从入门到放弃指北

    摘要:菜鸟教程框架中文手册入门目标使用搭建通过对数据增删查改没了纯粹占行用的拜 后端API入门学习指北 了解一下一下概念. RESTful API标准] 所有的API都遵循[RESTful API标准]. 建议大家都简单了解一下HTTP协议和RESTful API相关资料. 阮一峰:理解RESTful架构 阮一峰:RESTful API 设计指南 RESTful API指南 依赖注入 D...

    sf190404 评论0 收藏0
  • 后端API从入门到放弃指北

    摘要:菜鸟教程框架中文手册入门目标使用搭建通过对数据增删查改没了纯粹占行用的拜 后端API入门学习指北 了解一下一下概念. RESTful API标准] 所有的API都遵循[RESTful API标准]. 建议大家都简单了解一下HTTP协议和RESTful API相关资料. 阮一峰:理解RESTful架构 阮一峰:RESTful API 设计指南 RESTful API指南 依赖注入 D...

    Airmusic 评论0 收藏0
  • 合格PHP工程师知识结构

    摘要:不是原创的,主要是看了觉得有些帮助,就分享给大家看看。对于的知识要求我觉得以鸟哥的私房菜这本书作为要求吧。前端大部分使用,对静态资源使用可以加载更快。其实编程使用什么语言不重要,最重要的是思想,编程的思路。 不是原创的,主要是看了觉得有些帮助,就分享给大家看看。 PHP的运行环境 连环境都搞不起来,就是你有多么喜欢PHP,那也是白搭,开始我们大多会使用集成环境软件例如xampp,wam...

    KaltZK 评论0 收藏0
  • Symfony2中创建数据模型

    摘要:今天我在这里给大家分享一下在中如何创建数据模型和基于的搭建。对象与数据库的关系通常在电脑的世界里创建的数据模型会转换为数据库里的一条条记录。 Symfony是一款优秀的基于MVC架构的PHP框架。今天我在这里给大家分享一下在Symfony中如何创建数据模型和基于RESTful api的搭建。重点是如何创建数据模型哦! 本教程使用的当前Symfony的LTS版本(Symfony 2.8...

    rainyang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<