欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

开发自己的PHP MVC框架

程序员文章站 2023-12-25 14:28:39
...
这个教程可以使大家掌握用mvc模式开发php应用的基本概念。此教程分为三个部分,现在这篇是第一部分。

现在市面上有很多流行的框架供大家使用,但是我们也可以自己动手开发一个mvc框架,采用mvc模式可以大大减少我们开发应用的时间,并且能够更好的组织项目源代码,而且其中的某些模块还可在其它项目中使用。现在我要教大家写一个简单的mvc框架。由于这个项目很简单,轻量,所以可能并不是最佳实践,也不具备安全性,还需要大家在实际应用中完善。

所用技术:php,面向对象开发方法。

开始

首先在网站根目录下建立三个文件夹

  • models
  • views
  • controllers
  • 然后在根目录下新建一个文件:

  • index.php
  • 现在项目结构应该像这样

    § 网站根目录

    § index.php

    § models/

    § views/

    § controllers/


    index.php是整个web应用的入口点,所有的用户请求都会经过它。我们会写一些代码来把用户请求分派到相应的控制器中,这些控制器存放在controllers文件夹里。之后,我们就可以用下面的方式来实现页面跳转:

  • http://你的域名.com/index.php?page1
  • http://你的域名.com/index.php?page2
  • http://你的域名.com/index.php?page3
  • 设置前端控制器index.php

    首先在index.php中定义网站根目录和网站域名,以便在整个应用中访问。

    1. //应用的根目录就是index.php的父目录
    2. define("SERVER_ROOT", dirname(__FILE__));
    3. //你的域名.comm 是你的服务器域名
    4. define('SITE_ROOT' , 'http://你的域名.com');

    定义了网站根目录后,在任何php文件中,都能很方便的引用其它目录的php文件,因为index.php是入口文件,这样就能够在整个应用中访问在它之中定义的这些变量。

    设置路由器router.php(转发用户请求到相应控制器)

    在controllers目录下新建一个文件,名字为“router.php",这个文件用来处理所有页面请求。想像一下你家里的路由器,它负责把internet路由到家中的每个电脑。router.php文件将会获取传入到index.php的页面请求,然后把请求分派给不同的控制器(controllers)。

    route.php中的代码:

    1. //获取所有请求
    2. $request = $_SERVER['QUERY_STRING'];

    这句代码会获取传入到应用中的请求参数。QUERY_STRING就是”?“后面的所有字符串。

  • http://你的域名.com/index.php?page1
  • 上面的地址会在代码中得到”page1&action=login“,为了把page1和后面的参数分开,我们需要在route.php中继续加入下列代码:

    1. //解析$request变量,得到用户请求的页面(page1)和其它GET变量(&分隔的变量)如一个请求http://你的域名.com/index.php?page1&article=buildawebsite,则被解析为array("page1", "article=buildawebsite")
    2. $parsed = explode('&' , $request);
    3. //用户请求的页面,如上面的page1,为$parsed第一个变量,shift之后,数组为array("article=buildawebsite")
    4. $page = array_shift($parsed);
    5. //剩下的为GET变量,把它们解析出来
    6. $getVars = array();
    7. foreach ($parsed as $argument)
    8. {
    9. //用"="分隔字符串,左边为变量,右边为值
    10. list($variable , $value) = split('=' , $argument);
    11. $getVars[$variable] = $value;
    12. }
    13. //这是测试语句,一会儿会删除
    14. print "The page your requested is '$page'";
    15. print '
      ';
    16. $vars = print_r($getVars, TRUE);
    17. print "The following GET vars were passed to the page:
      ".$vars."
      ";
    现在我们需要在index.php中引入route.php

    1. /**
    2. * 定义文档路径
    3. */
    4. define("SERVER_ROOT", dirname(__FILE__));
    5. define('SITE_ROOT' , 'http://你的域名.com');
    6. /**
    7. * 引入router.php
    8. */
    9. require_once(SERVER_ROOT . '/controllers/' . 'router.php');
    10. ?>
    如果顺利的话,你可以打开浏览器输入:


  • http://你的域名.com/index.php?news&article=howtobuildaframework
  • 我们会看到如下输出

    1. The page you requested is 'news'
    2. The following GET vars were passed to the page:
    3. Array
    4. (
    5. [article] => howtobuildaframework
    6. )

    如果没有上述输出,请检查你的服务器配置是否正确,并检查代码是否有错误。

    现在来让我们添加一个页面到我们的网站里,这样就可以让router.php来产生一个页面,而不是直接输出上面的信息。


    创建一个控制器(controller)

    在controllers文件夹里新建一个文件名为“news.php",定义如下的类:

    1. /**
    2. * 这个文件处理文章的查询,并提供文章
    3. */
    4. class News_Controller
    5. {
    6. /**
    7. * $template变量会保存与此控制器相关的"view(视图)"的文件名,不包括.php后缀
    8. */
    9. public $template = 'news';
    10. /**
    11. * 此方法为route.php默认调用
    12. *
    13. * @param array $getVars 传入到index.php的GET变量数组
    14. */
    15. public function main(array $getVars)
    16. {
    17. //测试代码,以后会删除
    18. print "We are in news!";
    19. print '
      ';
    20. $vars = print_r($getVars, TRUE);
    21. print
    22. (
    23. "The following GET vars were passed to this controller:" .
    24. "
      ".$vars."
      "
    25. );
    26. }
    27. }

    注意我们把route.php中的测试代码复制过来了,并做了一些修改,我们把它放置在main函数里。现在让我们来修改route.php中的代码:

    1. /**
    2. * 此文件会把所有的传入参数分派到相应的控制器中
    3. */
    4. //获取请求参数
    5. $request = $_SERVER['QUERY_STRING'];
    6. //解析请求页面和其它GET变量
    7. $parsed = explode('&' , $request);
    8. //页面是第一个元素
    9. $page = array_shift($parsed);
    10. //剩余的为GET变量,也把它们解析出来
    11. $getVars = array();
    12. foreach ($parsed as $argument)
    13. {
    14. //split GET vars along '=' symbol to separate variable, values
    15. list($variable , $value) = split('=' , $argument);
    16. $getVars[$variable] = $value;
    17. }
    18. //构成控制器文件路径
    19. $target = SERVER_ROOT . '/controllers/' . $page . '.php';
    20. //引入目标文件
    21. if (file_exists($target))
    22. {
    23. include_once($target);
    24. //修改page变量,以符合命名规范(如$page="news",我们的约定是首字母大写,控制器的话就在后面加上“_Controller”,即News_Controller)
    25. $class = ucfirst($page) . '_Controller';
    26. //初始化对应的类
    27. if (class_exists($class))
    28. {
    29. $controller = new $class;
    30. }
    31. else
    32. {
    33. //类的命名正确吗?
    34. die('class does not exist!');
    35. }
    36. }
    37. else
    38. {
    39. //不能在controllers找到此文件
    40. die('page does not exist!');
    41. }
    42. //一但初始化了控制器,就调用它的默认函数main();
    43. //把get变量传给它
    44. $controller->main($getVars);?>

    再次访问http://你的域名.com/index.php?news&article=howtobuildaframework,你将会看到从News_Controller打印出来的信息。注意,我们现在用die()来处理错误,我们可以用其它更好的错误处理来规制它,但现在使用die()足够了,试试访问其它页面如http://你的域名.com/index.php?books,你会看到"page does not exist!"错误。创建一个Model(模型)完善News_Controller。假设我们有一些新闻片段来供读者阅读,那么就需要News_Controller这个控制器去调用一个模型来抓取相关的新闻片段,无论它们是存储在数据库还是文件里。在models文件夹里新建一个文件,“news.php”,代码如下:

    1. /**
    2. * 新闻模型为新闻控制器做复杂的后台操作
    3. */
    4. class News_Model
    5. {
    6. public function __construct()
    7. {
    8. print 'I am the news model';
    9. }
    10. }

    现在,我们需要对新闻控制器稍做一些更改,打开controllers里的news.php,把News_Controller类的main函数的代码改为如下,这样,我们就会在“News_Model”初始化时,看到打印在屏幕上的信息:

    1. public function main(array $getVars)
    2. {
    3. $newsModel = new News_Model;
    4. }
    现在刷新页面,你会看到:

    1. Fatal error: Class 'News_Model' not found in /var/www/mvc/controllers/news.php on line xx
    等一下,这不是我们想要的结果!我们正试图去加载一个不存在的类。那么原因就是我们并没有引入 /models/news.php文件。为了解决这个问题,让们重新来看一下router.php,然后在它的顶部添加一些代码:

    1. //当类初始化时,自动引入相关文件
    2. function __autoload($className)
    3. {
    4. //解析文件名,得到文件的存放路径,如News_Model表示存放在models文件夹里的news.php(这里是作者的命名约定)
    5. list($filename , $suffix) = split('_' , $className);
    6. //构成文件路径
    7. $file = SERVER_ROOT . '/models/' . strtolower($filename) . '.php';
    8. //获取文件
    9. if (file_exists($file))
    10. {
    11. //引入文件
    12. include_once($file);
    13. }
    14. else
    15. {
    16. //文件不存在
    17. die("File '$filename' containing class '$className' not found.");
    18. }
    19. }
    这个函数重载了PHP内置的autoload函数。当我们试图去初始化一个不存在的类时,这个‘魔术方法’允许我们拦截php所执行的动作。通过使用__autoload函数,我们能够告诉php寻找包含此类的文件的位置。假设你遵循了这篇文章中类和文件名的命名约定,那么每当你初始化一个类时,你就不必手动去引入包含此类的文件了!

    保存route.php,再刷新一次浏览器,你会看到:

    1. I am the news model

    让我们在新闻模型类里定义一些函数来提供文章。现在,我们只简单的定义了一个数组,并保存一些文章,然后提供一个函数,让控制器从中根据标题获取一篇文章。修改models/news.php:


    1. /**
    2. * 新闻模型为新闻控制器做复杂的后台操作
    3. *
    4. */
    5. class News_Model
    6. {
    7. /**
    8. * 文章数组. key为文章标题, 值为相应的
    9. * 文章。
    10. */
    11. private $articles = array
    12. (
    13. //文章1
    14. 'new' => array
    15. (
    16. 'title' => 'New Website' ,
    17. 'content' => 'Welcome to the site! We are glad to have you here.'
    18. )
    19. ,
    20. //2
    21. 'mvc' => array
    22. (
    23. 'title' => 'PHP MVC Frameworks are Awesome!' ,
    24. 'content' => 'It really is very easy. Take it from us!'
    25. )
    26. ,
    27. //3
    28. 'test' => array
    29. (
    30. 'title' => 'Testing' ,
    31. 'content' => 'This is just a measly test article.'
    32. )
    33. );
    34. public function __construct()
    35. {
    36. }
    37. /**
    38. * 根据标题获取文章
    39. *
    40. * @param string $articleName
    41. *
    42. * @return array $article
    43. */
    44. public function get_article($articleName)
    45. {
    46. //从数组中获取文章
    47. $article = $this->articles[$articleName];
    48. return $article;
    49. }
    50. }?>
    现在修改controllers/news.php中的main函数:

    1. public function main(array $getVars)
    2. {
    3. $newsModel = new News_Model;
    4. //获取一篇文章
    5. $article = $newsModel->get_article('test');
    6. print_r($article);
    7. }
    现在我们并没有考虑过滤用户输入的问题,因为我们现在只是为了尽快让大家掌握PHP MVC的基本内容,所以我们现在不必太关心这些。

    如果访问如下网址:

    § http://yourdomain.com/mvc/index.php?news&article=test

    你会看到如下输出:

    1. Array ( [title] => Testing [content] => This is just a measly test article. )

    创建视图(VIEW

    现在我们已经有控制器和模型了,只差一个视图。视图是表现层,它是你的应用中,与用户接触最频繁的部分。之前我提到过,视图是提供与业务逻辑分离的用户接口,有很多方法可以做到这个。你可以使用模板引擎Smarty或其它类似的。你也可以写一个自己的模板引擎,但那肯定是相当艰巨的任务。最后,你可以使用原生php视图。

    对于目前来说,php视图足够了。这个就像以前php与html代码混合编程一样,但是有一点不同是,我们的业务逻辑已经和视图分离了。看一下如下代码:

    1. Welcome to Our Website!


    2. News

    3. =$data['title'];?>

    4. =$data['content'];?>

    注意,嵌入的php标签利用了PHP 快捷操作符。这样就能够把我们的内容直接输出到HTML里面了。在views文件夹里新建一个文件“news.php”,把上述代码拷贝进来。现在我们有了视图文件,但是我们需要一个与视图交互的方法。在models文件夹里新建一个文件“view.php”,添加如下代码:


    1. /**
    2. * 在我们的MVC框架中,处理视图的功能
    3. */
    4. class View_Model
    5. {
    6. /**
    7. * 保存赋给视图模板的变量
    8. */
    9. private $data = array();
    10. /**
    11. * 保存视图渲染状态
    12. */
    13. private $render = FALSE;
    14. /**
    15. * 加载一个视图模板
    16. */
    17. public function __construct($template)
    18. {
    19. //构成完整文件路径
    20. $file = SERVER_ROOT . '/views/' . strtolower($template) . '.php';
    21. if (file_exists($file))
    22. {
    23. /**
    24. * 当模型对象销毁时才能渲染视图
    25. * 如果现在就渲染视图,那么我们就不能给视图模板赋予变量
    26. * 所以此处先保存要渲染的视图文件路径
    27. */
    28. $this->render = $file;
    29. }
    30. }
    31. /**
    32. * 接受从控制器赋予的变量,并保存在data数组中
    33. *
    34. * @param $variable
    35. * @param $value
    36. */
    37. public function assign($variable , $value)
    38. {
    39. $this->data[$variable] = $value;
    40. }
    41. public function __destruct()
    42. {
    43. //把类中的data数组变为该函数的局部变量,以方便在视图模板中使用
    44. $data = $this->data;
    45. //渲染视图
    46. include($this->render);
    47. }
    48. }

    现在,最后一件要做的事就是从News_Controller里加载视图。修改controllers/news.php:

    1. /*
    2. *这个文件处理文章的查询,并产生新闻文章*
    3. */
    4. class News_Controller{
    5. /**
    6. * $template变量会保存与此控制器相关的"view(视图)"的文件名,不包括.php后缀
    7. *
    8. */
    9. public $template = 'news';
    10. /**
    11. * 此方法为route.php默认调用
    12. *
    13. * @param array $getVars 传入到index.php的GET变量数组
    14. */
    15. public function main(array $getVars) {
    16. $newsModel = new News_Model;
    17. //获取一片文章
    18. $article = $newsModel->get_article($getVars['article']);
    19. //创建一个视图,并传入该控制器的template变量
    20. $view = new View_Model($this->template);
    21. //把文章数据赋给视图模板
    22. $view->assign('title' , $article['title']);
    23. $view->assign('content' , $article['content']);
    24. }
    25. }
    26. ?>

    上一篇:

    下一篇: