教程:使用PHP开发自己的MVC框架一、什么是MVCMVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。
除此之外,此模式通过对复杂度的简化,使程序结构更加直观。
软件系统通过对自身基本部份分离的同时也赋予了各个基本部分应有的功能。
专业人员可以通过自身的专长分组:∙(控制器Controller)- 负责转发请求,对请求进行处理。
∙(视图View)–界面设计人员进行图形界面设计。
∙(模型Model)–程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
模型(Model)“数据模型”(Model)用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。
“模型”有对数据直接访问的权力,例如对数据库的访问。
“模型”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。
但是模型中数据的变化一般会通过一种刷新机制被公布。
为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解在数据模型上发生的改变。
视图(View)视图层能够实现数据有目的的显示(理论上,这不是必需的)。
在视图中一般没有程序上的逻辑。
为了实现视图上的刷新功能,视图需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册。
控制器(Controller)控制器起到不同层面间的组织作用,用于控制应用程序的流程。
它处理事件并作出响应。
“事件”包括用户的行为和数据模型上的改变。
二、为什么要自己开发MVC框架网络上有大量优秀的MVC框架可供使用,本教程并不是为了开发一个全面的、终极的MVC框架解决方案,而是将它看作是一个很好的从内部学习PHP的机会,在此过程中,你将学习面向对象编程和设计模式,并学习到开放中的一些注意事项。
更重要的是,你可以完全控制你的框架,并将你的想法融入到你开发的框架中。
虽然不一定是做好的,但是你可以按照你的方式去开发功能和模块。
三、开始开发自己的MVC框架在开始开发前,让我们先来把项目建立好,假设我们建立的项目为todo,那么接下来的第一步就是把目录结构先设置好。
虽然在这个教程中不会使用到上面的所有的目录,但是为了以后程序的可拓展性,在一开始就把程序目录设置好使非常必要的。
下面就具体说说每个目录的作用:∙application –存放程序代码∙config –存放程序配置或数据库配置∙db –用来存放数据库备份内容∙library –存放框架代码∙public –存放静态文件∙scripts –存放命令行工具∙tmp –存放临时数据在目录设置好以后,我们接下来就要来顶一下一些代码的规范:1.MySQL的表名需小写并采用复数形式,如items,cars2.模块名(Models)需首字母大写,并采用单数模式,如Item,Car3.控制器(Controllers)需首字母大写,采用复数形式并在名称中添加“Controller”,如ItemsController, CarsController4.视图(Views)采用复数形式,并在后面添加行为作为文件,如:items/view.php,cars/buy.php1 2 3 4 5 <IfModule mod_rewrite.c> RewriteEngine on RewriteRule ^$ public/ [L] RewriteRule (.*) public/$1 [L] </IfModule>1 2 3 4 5 6 7 8 9 <IfModule mod_rewrite.c>RewriteEngine On#如果文件存在就直接访问目录不进行RewriteRule RewriteCond %{REQUEST_FILENAME} !-f#如果目录存在就直接访问目录不进行RewriteRule RewriteCond %{REQUEST_FILENAME} !-d#将所有其他URL重写到index.php/URL RewriteRule ^(.*)$ index.php?url=$1 [PT,L]</IfModule>这么做的主要原因有:1.可以使程序有一个单一的入口,将所有除静态程序以外的程序都重定向到index.php上;2.可以用来生成利于SEO的URL,想要更好的配置URL,后期可能会需要URL路由,这里先不做介绍了。
1 2 3 4 5 <?phpdefine('DS',DIRECTORY_SEPARATOR);define('ROOT',dirname(dirname(__FILE__)));$url = $_GET['url'];require_once(ROOT.DS.'library'.DS.'bootstrap.php');注意上面的PHP代码中,并没有添加PHP结束符号”?>”,这么做的主要原因是:对于只包含PHP代码的文件,结束标志(“?>”)最好不存在,PHP自身并不需要结束符号,不添加结束符号可以很大程度上防止末尾被添加额外的注入内容,让程序更加安全。
在index.php中,我们对library文件夹下的bootstrap.php发起了请求,那么bootstrap.php这个启动文件中到底会包含哪些内容呢?1 2 3 <?phprequire_once(ROOT.DS.'config'.DS .'config.php');require_once(ROOT.DS.'library'.DS .'shared.php');1 2 3 4 5 6 7 8 <?php# 设置是否为开发状态define('DEVELOPMENT_ENVIRONMENT',true);# 设置数据库连接所需数据define('DB_HOST','localhost');define('DB_NAME','todo');define('DB_USER','root');define('DB_PASSWORD','root');1 2 3 4 5 6 7 8 9 <?php/* 检查是否为开发环境并设置是否记录错误日志 */function setReporting(){if (DEVELOPMENT_ENVIRONMENT == true) {error_reporting(E_ALL);ini_set('display_errors','On');} else {error_reporting(E_ALL);ini_set('display_errors','Off');1011121314151617181920212223242526272829303132333435363738394041424344454647484950515253 ini_set('log_errors','On');ini_set('error_log',ROOT.DS. 'tmp' .DS. 'logs' .DS. 'error.log');}}/* 检测敏感字符转义(Magic Quotes)并移除他们*/function stripSlashDeep($value){$value = is_array($value) ? array_map('stripSlashDeep',$value) : stripslashes($value);return $value;}function removeMagicQuotes(){if (get_magic_quotes_gpc()) {$_GET = stripSlashDeep($_GET);$_POST = stripSlashDeep($_POST);$_COOKIE = stripSlashDeep($_COOKIE);}}/* 检测全局变量设置(register globals)并移除他们*/function unregisterGlobals(){if (ini_get('register_globals')) {$array = array('_SESSION','_POST','_GET','_COOKIE','_REQUEST','_SERVER','_ENV','_FILES'); foreach ($array as $value) {foreach ($GLOBALS[$value] as $key => $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);545556575859606162636465666768697071727374757677787980 $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();1 2 3 4 5 6 <?phpclass Controller { protected $_model; protected $_controller; protected $_action; protected $_template;7 8 91011121314151617181920 function __construct($model, $controller,$action) {$this->_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();}}1 2 3 4 5 6 7 8 9101112131415161718 <?phpclass SQLQuery {protected $_dbHandle;protected $_result;/** 连接数据库**/function connect($address, $account, $pwd, $name) {$this->_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;}1920212223242526272829303132333435363738394041424344454647484950515253545556575859606162}/** 中断数据库连接**/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);63646566676869707172737475767778return($result);}}/** 返回结果集行数**/function getNumRows() {return mysql_num_rows($this->_result); }/** 释放结果集内存**/function freeResult() {mysql_free_result($this->_result);}/** 返回MySQL操作错误信息**/ function getError() {return mysql_error($this->_dbHandle); }}1 2 3 4 5 6 7 8 91011 <?phpclass Model extends SQLQuery{protected $_model;function __construct() {$this->connect(DB_HOST,DB_USER,DB_PASSWORD,DB_NAME); $this->_model = get_class($this);$this->_table = strtolower($this->_model)."s";}function __destruct() {}}1 2 3 4 <?phpclass Template {protected $variables = array(); protected $_controller;5 6 7 8 91011121314151617181920212223242526272829protected $_action;function __construct($controller,$action) {$this->_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');}}}1 2 3 4 5 6 7 <?phpclass ItemsController extends Controller { function view($id = null,$name = null) {$this->set('title',$name.' - My Todo List App'); $this->set('todo',$this->Item->select($id)); }function viewall() {8 91011121314151617181920$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).'\''));}}1 2 3 <?phpclass Item extends Model { }1 <h2><?php echo $todo['Item']['item_name']?></h2>2 3 4 <a href="../../../items/delete/<?php echo $todo['Item']['id']?>"> <span>Delete this item</span></a>1 2 3 4 5 6 7 8 910111213 <form action="../items/add" method="post"><input type="text" value="I have to..." onclick="this.value=''" name="todo"> <input type="submit" value="add"></form><br/><br/><?php $number = 0?><?php foreach ($todo as $todoitem):?><a href="../items/view/<?php echo $todoitem['Item']['id']?>/<?php echo strtolower(str_replace(" ","-",$todoitem['Item']['item_name']))?>"><span><?php echo ++$number?><?php echo $todoitem['Item']['item_name']?></span></a><br/><?php endforeach?>1 <a href="../items/viewall">Todo successfully added. Click here to go back.</a><br/>1 <a href="../../items/viewall">Todo successfully deleted. Click here to go back.</a><br/> header.php:页首文件1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 1 3 1 4 <html><head><title><?php echo $title?></title><style>.item {width:400px;}input{color:#222222;font-family:georgia,times;font-size:24px;font-weight:normal;line-height:1.2em;color:black;} a{color:#222222;font-family:georgia,times;font-size:24px;font-weight:normal;line-height:1.2em;color:black;text -decoration:none;}a:hover {background-color:#BCFC3D;}h1{color:#000000;font-size:41px;letter-spacing:-2px;line-height:1em;font-family:helvetica,arial,sans-serif;border-bottom:1px dotted #cccccc;}h2 {color:#000000;font-size:34px;letter-spacing:-2px;line-height:1em;font-family:helvetica,arial,sans-serif;} </style></head><body><h1>My Todo-List App</h1>12</body></html>1 2 3 4 5 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 查看新建的站点。