+ -
当前位置:首页 → 问答吧 → 最简单的PHP MVC留言本实例[已修正]

最简单的PHP MVC留言本实例[已修正]

时间:2009-02-25

来源:互联网

本帖最后由 SunShineJia 于 2009-5-9 15:53 编辑

这个帖子发布以后有朋友指出例子中V直接操作M不符合MVC原则,现在对代码做了修改:
在C中调用M中的方法返回数据(如全部留言数据),将M返回的数据传递给V,这样C操作M和V,V不再直接与M联系。
欢迎大家多多拍砖!

写完那篇“写给懂C语言的人的PHP基本语法入门”后一直在学习PHP5的OOP,目的很简单,就是想研究MVC的PHP实现,所以,兴趣很快转移到MVC上面,网上有很多长篇大论,但是看完了我还是不能写出一个最简单的MVC程序,我这个人学东西有个习惯,那就是先要掌握一个最简单的“Hello World”,然后再以此为基础扩展开去,否则心里没底,一头雾水。
    经过一番搜索,找到了一篇翻译文章(作者:Harry Fuecks 翻译:Easy Chen URL:http://www.21ds.net/article/4/453  原文URL:http://www.phppatterns.com/docs/ ... _controller_pattern)作者以商品目录浏览为例,给出了完整的MVC架构代码。仔细一看,发现他的C和V是继承关系,耦合很紧,似乎不是很理想,但马上又看到了作者的第二个版本(http://www.phppatterns.com/doku.php/design/mvc_pattern_version_2),这个版本的C和V分离得比较清楚,仔细研读了这个版本,然后仿照着实现了一个留言板。
    标题上我把这个留言板叫最简单的,其实应该叫最简陋的,因为把全部注意力集中在MVC模式设计和实现上,所以UI方面几乎没有一点修饰。之所以在这里跟大家分享这个东西,是因为我自己通过读该老外的代码并仿照着写留言板对MVC的概念和具体实现有了些认识,希望了解MVC具体实现的朋友可以参考一下。
    首先通俗地说说我对MVC的理解:Model是负责干活的,它干的活主要是从数据库中获取需要的数据以及对获取的数据按照业务逻辑进行加工处理,至于为什么要干某件活,何时干某件活它一概不管,而这正是Controller的职责,Controller像个餐馆招待,接到食客的需求,马上传达给厨房,Model就是大厨。View负责最终把菜端上桌,摆在合适的位置上。比如说客人来了要了个糖醋鲤鱼,接待客人的是Controller,它会通知Model做一道糖醋鲤鱼,做好之后它又会招呼View把菜端上桌,View知道这是主菜,它会把它摆在桌子中央。MVC的最大优势就在于把数据处理、流程控制和UI显示较好地分离开来,便于程序的开发和维护。
    好了,下面看具体实现。
这个小程序一共包含6个文件,其中index.php是程序入口、post.htm是留言表单、在lib文件夹里Model、View 、Controller三个文件分别实现MVC,DataAccess是一个简单的数据库访问类。
[php]
<?php
/**
*  一个用来访问MySQL的类
*  仅仅实现演示所需的基本功能,没有容错等
*  代码未作修改,只是把注释翻译一下,加了点自己的体会
*/
class DataAccess {
   
    var $link_id; //用于存储数据库连接
   
    var $query_id; //用于存储查询源
    //! 构造函数.
    /**
    * 创建一个新的DataAccess对象
    * @param $host 数据库服务器名称
    * @param $user 数据库服务器用户名
    * @param $pass 密码
    * @param $db   数据库名称
    */
    function __construct($host,$user,$pass,$db) {
        $this->link_id=mysql_pconnect($host,$user,$pass); //连接数据库服务器
        mysql_select_db($db,$this->link_id);              //选择所需数据库
                                             
  mysql_query("set names utf8;");
    }
    //! 执行SQL语句
    /**
    * 执行SQL语句,获取一个查询源并存储在数据成员$query中
    * @param $sql  被执行的SQL语句字符串
    * @return void
    */
    function query($sql) {
        $this->query_id=mysql_unbuffered_query($sql,$this->link_id); // Perform query here
        if ($this->query_id) return true;
  else return false;
}
    //! 获取结果集
    /**
    * 以数组形式返回查询结果的所有记录
    * @return mixed
    */
    function fetchRows($sql) {
        $this->query($sql);
  $arr=array();
  $i=0;
  while( $row=mysql_fetch_array($this->query_id,MYSQL_ASSOC) )
                                             //MYSQL_ASSOC参数决定了数组键名用字段名表示
  {   $arr[$i]=$row;
      $i++;
   }
            return $arr;
      
    }
}
?>
[/php]
       下面再来介绍一下Model类。
    这个类也很简单,里面的函数一看就知道,是针对各种数据操作的,它通过DataAccess访问数据库。
[php]
<?php

//! Model类
/**
* 它的主要部分是对应于留言本各种数据操作的函数
* 如:留言数据的显示、插入、删除等
*/
class Model {
   
    var $dao; //DataAccess类的一个实例(对象)
    //! 构造函数
    /**
    * 构造一个新的Model对象
    * @param $dao是一个DataAccess对象
* 该参数以地址传递(&$dao)的形式传给Model
* 并保存在Model的成员变量$this->dao中
* Model通过调用$this->dao的fetch方法执行所需的SQL语句
    */
    function __construct(&$dao) {
        $this->dao=$dao;
    }
    function listNote() {    //获取全部留言
        $notes=$this->dao->fetchRows("SELECT * FROM note ORDER BY timedate DESC");
  
            return $notes;
         
    }

function postNote() {    //插入一条新留言
     
  $name=$_POST['username'];
        $email=$_POST['email'];
        $content=$_POST['content'];
        $timedate=time()+8*3600;
  $sql="INSERT INTO note (name, email, content, timedate) VALUES
             ('".$name."', '".$email."', '".$content."', '".$timedate."' )";
     //echo $sql;  //对于较复杂的合成SQL语句,<br />
                      //调试时用echo输出一下看看是否正确是一种常用的调试技巧
  if ($this->dao->query($sql)) return true;
  else return false;
}

function deleteNote() {   //删除一条留言,$id是该条留言的id
     $sql="DELETE FROM note WHERE id=".$_GET['id'];
  if ($this->dao->query($sql)) return true;
  else return false;
}

  
}
?>
[/php]
       看完这两个类之后你可能会发现这与以前我们写程序差不多,的确现在还闻不到MVC的味道,如果你不懂MVC,在这两个类的基础上你完全可以开始写你以前的程序了。例如要显示全部留言,只需要写入下代码:
[php]<?php
require_once('lib/DataAccess.php');
require_once('lib/Model.php');

$dao=& new DataAccess ('localhost','root','password','test');
$model=& new Model($dao);
$notes=$model->listNote();
……
?>[/php]
       很亲切吧,呵呵。
    有了这个“感情基础”你就不会对MVC望而生畏了,下面我们就要上今天的主菜了,那就是“Controller”闪亮登场!
    先大体浏览一下主要结构,它包括一个Controller类以及派生出的三个子类(listController对应显示留言功能、postController对应发表留言功能以及deleteController对应删除留言功能)。
[php]
<?php
//! Controller
  /**
  * 控制器将$_GET['action']中不同的参数(list、post、delete)
  * 对应于完成该功能控制的相应子类
  */
class Controller {
    var $model;  // Model 对象
    var $view;   // View  对象
    //! 构造函数
    /**
    * 构造一个Model对象存储于成员变量$this->model;
    */
    function __construct (& $dao) {
        $this->model=& new Model($dao);
    }
  
  function getView() {    //获取View函数
                          //返回视图对象view
        //对应特定功能的Controller子类生成对应的View子类的对象
                             //通过该函数返回给外部调用者
    return $this->view;
  }

}
//用于控制显示留言列表的子类
class listController extends Controller{   //extends表示继承  
function __construct (& $dao) {
      parent::__construct($dao);  //继承其父类的构造函数
                               //该行的含义可以简单理解为:
          //将其父类的构造函数代码复制过来
      $notes=$this->model->listNote();
   $this->view=& new listView($notes);
                               //创建相应的View子类的对象来完成显示
         


}
}
//用于控制添加留言的子类
class postController extends Controller{
function __construct (& $dao) {
      parent::__construct($dao);
   if ($this->model->postNote()) $success=1;
   else $success=0;
   $this->view=& new postView($success);
}
}
//用于控制删除留言的子类
class deleteController extends Controller{
function __construct (& $dao) {
      parent::__construct($dao);
      if ($this->model->deleteNote()) $success=1;
   else $success=0;
   $this->view=& new deleteView($success);
}
}

?>

[/php]
       大体浏览之后,你一定打算开始仔细研究它了吧,别急,为了心中有数,我们先从宏观着眼,先看看总入口index.php是如何调用Controller的:
[php]
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PHP MVC留言板</title>
</head>
<body leftmargin="50px">
<a href="notebook.htm">添加新留言</a><br>
<p>
<?php
//!index.php 总入口
/**
* index.php的调用形式为:
* 显示所有留言:index.php?action=list
* 添加留言    :index.php?action=post
* 删除留言    :index.php?action=delete&id=x
*/
require_once('lib/DataAccess.php');
require_once('lib/Model.php');
require_once('lib/View.php');
require_once('lib/Controller.php');
//创建DataAccess对象(请根据你的需要修改参数值)
$dao=& new DataAccess ('localhost','root','your password here','notebook');
//根据$_GET["action"]取值的不同调用不同的控制器子类
$action=$_GET["action"];
switch ($action)
{
   case "post":
      $controller=& new postController($dao); break;
   case "list":
      $controller=& new listController($dao); break;
   case "delete":
      $controller=& new deleteController($dao); break;
   default:
      $controller=& new listController($dao); break; //默认为显示留言
   
}
$view=$controller->getView(); //获取视图对象
$view->display();             //输出HTML
?>
</body>
</html>

[/php]
      看过index.php之后你就更清楚了吧,原来功能是通过$_GET[“action”]指定的,由一个switch结构分发,不同的功能对应不同的Controller子类。现在可以滚上去(滚动页面上去的简称,绝非不洁用语^_^)仔细看看这个Controller代码了。注释应该很细了,不懂的地方就去看看PHP5的OOP语法和概念吧,单纯看这些概念总是越看催眠效果越好,现在带着实际问题去看,应该有所不同吧。不过我还是建议你在完成这个MVC的Hello World知道MVC是怎么回事之后下功夫打好OOP的基础,毕竟那是根本啊。
    怎么样,Controller真是个光说不练的家伙吧,看不到三行它就把你引向View了,那就看看View吧。
View里有对应的子类,负责相应功能的显示。理解了Controller,View的代码就不难看了,难看的话也是因为混杂着HTML的原因,它所做的就是把Controller(Controller是个二道贩子,它的数据来自Model)给它的数据,然后塞到HTML中。
[php]
<?php
//! View 类
/**
* 针对各个功能(list、post、delete)的各种View子类
* 被Controller调用,完成不同功能的网页显示
*/
class View {
   
    var $output; //用于保存输出HTML代码的字符串

function display() {  //输出最终格式化的HTML数据
     echo($this->output);
   
}
}
class listView extends View   //显示所有留言的子类
{
    function __construct($notes)
{
   foreach ($notes as $value)
   {
      $this->output.="<p><strong>访客姓名:</strong>".$value['name']."</p>".
                     "<p><strong>访客邮箱:</strong>".$value['email']."</p>".
                     "<p><strong>访客留言:</strong>".$value['content']."</p>".
                     "<p><strong>来访时间:</strong>".date("y-m-d H:i",$value['timedate'])."</p>".
      "<p align=\"right\"><a href=\"index.php?action=delete&id=".$value['id']."\">删除留言</a>".
                        "<hr />";   
   }
   
   
}
}
class postView extends View  //发表留言的子类
{
    function __construct($success)
{
    if ($success)
    $this->output="留言成功!<br><a href=\"".$_SERVER['PHP_SELF']."?action=list\">查看</a>";
    else
    $this->output="留言保存失败!";
}
}
class deleteView extends View  //删除留言的子类
{
    function __construct($success)
{
    if ($success)
    $this->output="留言删除成功!<br><a href=\"".$_SERVER['PHP_SELF']."?action=list\">查看</a>";
   
}
}
?>

[/php]

     之所以UI方面写得如此简陋,是因为这些工作可以交给Smarty这样的模板去做,而我们这里就像集中精力研究MVC,不想把Smarty扯进来,所以就这样凑合了,以后我们可以再把Smarty结合进来。

    看了这个东西之后不知你是否对MVC的概念和实现更明白了一点。
    我也是个初学者,这是个依葫芦画瓢之作,目的就是想了解一下MVC,如果你是高手,我很想得到你的点评,这样的划分和架构是否符合MVC的理念?还有哪些应该改进之处?
    当然,大家都知道现在很多关于MVC的争论,这很正常,就像关于开发语言的争论一样,永无休止,学术上的争论有助于创新。作为我们学技术、用技术而言,一定要踏实深入学习,掌握了基本用法之后再去讨论,那才是更高层次的发展,在自己都搞不清的情况下在哪里争论只能是浪费时间。
    下面说说我体会到的MVC的好处,它的确给程序的功能扩展带来方便,比如这个例子我们想要增加一个根据用户名查询留言的功能,只需要在Model里增加一个查询函数(突然发现这些函数的用法很像存储过程),Controller和View里增加相应的子类,这种分离带来的好处是程序功能模块可以即插即用,再就是整个程序的逻辑非常清晰。我想,对于需求变动频繁的Web应用来说,这种特性也许是很有价值的。

    下面我们做什么呢?是不是再研究几个优秀的数据库操作库类,加深一下对数据库操作函数以及OOP的理解,然后再把Smarty整合进来?这方面坛子里的feifengxlq版主有很好的帖子http://bbs.phpchina.com/viewthread.php?tid=5687,应该好好学习一下。

new_mvc_notebook.rar (5.55 KB)

下载次数:249

2009-5-9 15:53

作者: SunShineJia   发布时间: 2009-02-25

支持一下

作者: okjoyel   发布时间: 2009-02-25

支持支持

作者: a1727   发布时间: 2009-02-25

学习了,比之前看的说得都清楚。新手学习。。

作者: chen_pei   发布时间: 2009-02-25

mysql_pconnect !=mysql_connect

作者: a1727   发布时间: 2009-02-25

原帖由 a1727 于 2009-2-25 22:47 发表
mysql_pconnect !=mysql_connect
注意到了这个问题,原作者是这么写的,没管它。

作者: SunShineJia   发布时间: 2009-02-25

调动度员(switch)太累了,而且会膨胀。到时候滚上滚下 都找不到要改的地方。期待楼主的续帖。

作者: a1727   发布时间: 2009-02-25

系统规模大了是不是可以考虑多入口,一个主要功能模块一个入口?

作者: SunShineJia   发布时间: 2009-02-26

支持一下

作者: lxylxy888666   发布时间: 2009-02-26

支持下,谢楼主了

作者: 追风1   发布时间: 2009-02-26

新手学习

作者: abcnic1   发布时间: 2009-02-26

是学习的一个很好的例子,赞一句

作者: deeka   发布时间: 2009-02-27

支持,谢谢!

作者: f75h   发布时间: 2009-03-02

作者: theetc   发布时间: 2009-03-03

LZ的讲解很生动

作者: ylcz   发布时间: 2009-03-03

不错,

作者: mailangel123   发布时间: 2009-03-03

/* 该参数以地址传递(&$dao)的形式传给Model  */ function __construct(&$dao) { $this->dao=$dao; }
这块为什么要用地址传递啊,
谁能帮着解释一下吗?

作者: naodai   发布时间: 2009-03-03

谢谢LZ发布分享``

刚开始学习...呵呵``

作者: 愁云惨淡   发布时间: 2009-03-03

继续关注哦

作者: dbsky   发布时间: 2009-03-03

支持 经典啊 向你学习!!!

作者: xupengfeiaaa   发布时间: 2009-03-04

对了,有一个问题 ,就是为什么 调用Model里面的添加留言方法要在view里面调用,不直接在Controller里面调用?

作者: 深蓝色   发布时间: 2009-03-05

支持,好贴,呵!

作者: libailin   发布时间: 2009-03-05

原帖由 naodai 于 2009-3-3 09:37 发表
/* 该参数以地址传递(&$dao)的形式传给Model  */ function __construct(&$dao) { $this->dao=$dao; }
这块为什么要用地址传递啊,
谁能帮着解释一下吗?
应用地址传递可以是把对象的地址传递给实参,而非复制一个对象,这样有助于节约内存,并随时保持对对象变化的跟踪。

作者: SunShineJia   发布时间: 2009-03-08

原帖由 SunShineJia 于 2009-3-8 21:46 发表


View负责的工作是通过Model获取数据并显示出来,对于提交数据,当然就是把页面上的数据交给Model,Controller是负责指挥和调度工作,不具体处理数据。
哈,搞错吧,View只负责输出显示。View层不允许写业务逻辑。你这个V层怪怪的。我搜到的一些MVC模式图,V和M都没直接互通。我接触过一点Rails,那个MVC还算正点了吧,也不是像楼主的这样做。


作者: adriandcb   发布时间: 2009-03-09

不错不错..

作者: 古嗣小井   发布时间: 2009-03-11

想询问一下为什么不能提交中文字符?

作者: jinshuo   发布时间: 2009-03-11

太好了.很想了解mvc 是什么

作者: teri   发布时间: 2009-03-13

学习了

作者: cgh43   发布时间: 2009-03-15

不错!对MVC模式了解也不深···今天终于有点明白的气味了!呵呵

作者: xifangjingji   发布时间: 2009-03-15

我也有这种感觉
原帖由 adriandcb 于 2009-3-9 23:28 发表



哈,搞错吧,View只负责输出显示。View层不允许写业务逻辑。你这个V层怪怪的。我搜到的一些MVC模式图,V和M都没直接互通。我接触过一点Rails,那个MVC还算正点了吧,也不是像楼主的这样做。
http://www.haoxiai.n ...

作者: 深蓝色   发布时间: 2009-03-19

function fetch($sql) {
        $this->query=mysql_unbuffered_query($sql,$this->db); // Perform query here
    }

function postNote($name,$content) {    //插入一条新留言
            $sql = "INSERT INTO `test`.`note`
                       (`id`, `name`, `content`, `ndate`, `add`)
                            VALUES (NULL, '$name', '$content', NULL, NULL);";
            //echo $sql;  //对于较复杂的合成SQL语句,<br />
                      //调试时用echo输出一下看看是否正确是一种常用的调试技巧
                $this->dao->fetch($sql);
        }


我要插入一条数据,如何判断类似面向过程if(mysql_affected_rows() == 1){这样数据插入成功了呢!谢谢!

作者: xyfxh   发布时间: 2009-03-19

感谢LZ分享,收藏了

作者: tawwudi   发布时间: 2009-03-20

看上去不错,我以前写的都是过程式的,HTML里有PHP

作者: shgen   发布时间: 2009-04-19

不错。

作者: levi6717   发布时间: 2009-04-20

哈,搞错吧,View只负责输出显示。View层不允许写业务逻辑。你这个V层怪怪的。我搜到的一些MVC模式图,V和M都没直接互通。我接触过一点Rails,那个MVC还算正点了吧,也不是像楼主的这样做。
http://www.haoxiai.n ...
adriandcb 发表于 2009-3-9 23:28
是的,我刚才看了代码,这个例子写的有点不规范,它用V和M打交道了,有点误导初学者,我看ZF上面,V只是一些html,没有业务处理。

作者: shgen   发布时间: 2009-04-21

支持一下

作者: flyboyxiang   发布时间: 2009-04-21

简洁,精炼!

作者: aries   发布时间: 2009-04-25

php的mvc,支持一下,学习一下

作者: kingkongzhang   发布时间: 2009-04-25

楼主这个例子V层是否应该做模版呢,因为像你这样将V层写层一个类且与M层联系这么紧密的话,还叫MVC么,我觉的应该将C层和V层之间用模版技术比如Smarty,而且V层坚决不和M层发生关系

作者: gjwzjl   发布时间: 2009-04-26

a1727
用重载吧。

作者: it577net   发布时间: 2009-04-26

SunShineJia
正如VC中正是这么处理的,楼主我说的对不?

作者: it577net   发布时间: 2009-04-26

Fatal error: Call to undefined method DataAccess::fetch() in /lib/Model.php on line 12
        function listNote()
        {
                $this->dao->fetch("select * from note;");//指的就是这句没法调用
        }

求楼主解释

作者: it577net   发布时间: 2009-04-26

很好的实例教程,学习了

作者: 城市贫民   发布时间: 2009-04-27

我怎么用了一个,出问题了呢!~

作者: f19861211   发布时间: 2009-04-28

f19861211
你遇到的是什么问题

作者: it577net   发布时间: 2009-04-28

好东西,我也想用mvc写个留言本练练手,就拿你的例子学习了。

作者: bwt009   发布时间: 2009-04-28

本帖最后由 SunShineJia 于 2009-5-9 15:55 编辑
是的,我刚才看了代码,这个例子写的有点不规范,它用V和M打交道了,有点误导初学者,我看ZF上面,V只是一些html,没有业务处理。
shgen 发表于 2009-4-21 14:59
多谢指教!我已经对代码做了修正,请看一楼。

作者: SunShineJia   发布时间: 2009-05-08

7# a1727
用重载吧。
it577net 发表于 2009-4-26 19:56
愿闻其详。

作者: SunShineJia   发布时间: 2009-05-09

adriandcb

多谢指教,我已修正代码!

作者: SunShineJia   发布时间: 2009-05-09

本帖最后由 lssxlh 于 2009-5-11 18:06 编辑

非常棒的例子,如果我想加如分页功能改怎么实现?请楼主说说

作者: lssxlh   发布时间: 2009-05-11

lssxlh

在Model里面增加分页方法。
获取分页数据可以通过在SQL语句中加Limit
分页方法关键是生成分页导航信息。

作者: SunShineJia   发布时间: 2009-05-12

本帖最后由 lssxlh 于 2009-5-12 12:08 编辑

如果我有个现成的分页类或者登陆类,能不能像插件一样集成到里面?我是菜鸟,楼主能不能详细的说说!非常感谢你的这个帖子,让我对mvc有的初步的了解!

还有我把引用(&)全去掉了,程序也能运行,这队程序也没什么影响吧

作者: lssxlh   发布时间: 2009-05-12

支持了

作者: lc0633   发布时间: 2009-05-18

php 5  实例化会自动引用地址 不需要& 这个了吧

作者: pxllong   发布时间: 2009-05-18

pxllong


正解!
我也是初学者,写这个例子只把精力集中在MVC模式的理解上了,没有考虑太多其他。

作者: SunShineJia   发布时间: 2009-05-19

lssxlh

这个例子仅仅是用来演示MVC的还没有考虑那么多,现在的框架都可以提供更丰富的功能。

作者: SunShineJia   发布时间: 2009-05-19

很好的MVC学习资料,支持楼主出续集:)

作者: hotwow   发布时间: 2009-05-24

系统规模大了是不是可以考虑多入口,一个主要功能模块一个入口?
SunShineJia 发表于 2009-2-26 09:55
怎么实现多入口?

作者: hotwow   发布时间: 2009-05-24

支持下

作者: strongability   发布时间: 2009-05-25

看看,学习下。。

作者: yleo77   发布时间: 2009-06-14

这个版本的V好多了,分工也清晰多了

作者: zuiw   发布时间: 2009-06-26

支持你

作者: niceup   发布时间: 2009-06-26

看看。。。。。。。。。。。。。

作者: Cr@zyTreE   发布时间: 2009-07-28

先收下了!!!

作者: wengbin22   发布时间: 2009-07-30

支持一下

作者: ksheng   发布时间: 2009-07-31

很好很强大....

作者: wzwen   发布时间: 2009-08-01

很不错啊.   顶起来

作者: ImFantasy   发布时间: 2009-08-11

支持下。

作者: vsxp   发布时间: 2009-09-02

支持LZ。

作者: 我为ぁ琴狂   发布时间: 2009-09-12

讲解真的很明白啊,虽然看你的代码还是不是很明白,但是mvc的理解没以前那么模糊了。

作者: 东哥小白龙   发布时间: 2009-11-22

很好很强大

作者: alexru   发布时间: 2009-11-24

慢慢看下、

作者: Niger   发布时间: 2009-11-24

支持一下
不错,一直在学习

作者: leilei111111   发布时间: 2009-11-26

很详细!

作者: 杨贺   发布时间: 2009-11-27

看看先

作者: shulinqian   发布时间: 2009-12-10

看看先

作者: beston   发布时间: 2009-12-10

支持

作者: 邹梨明   发布时间: 2009-12-13

刚开始学习,仔细看下。

作者: wto-ugg   发布时间: 2010-09-21

热门下载

更多