MVC模式(Model-View-Controller)是軟體工程中的一種軟體架構模式,把軟體系統分為三個基本部分:模型(Model)、檢視(View)和控制器(Controller)。下面是小編為大家帶來的如何使用PHP開發自己的MVC框架的知識,歡迎閱讀。
一、什麼是MVC
MVC模式(Model-View-Controller)是軟體工程中的一種軟體架構模式,把軟體系統分為三個基本部分:模型(Model)、檢視(View)和控制器(Controller)。
MVC模式的目的是實現一種動態的程式設計,使後續對程式的修改和擴充套件簡化,並且使程式某一部分的重複利用成為可能。除此之外,此模式通過對複雜度的簡化,使程式結構更加直觀。軟體系統通過對自身基本部份分離的同時也賦予了各個基本部分應有的功能。專業人員可以通過自身的專長分組:
(控制器Controller)- 負責轉發請求,對請求進行處理。
(檢視View) – 介面設計人員進行圖形介面設計。
(模型Model) – 程式設計師編寫程式應有的功能(實現演算法等等)、資料庫專家進行資料管理和資料庫設計(可以實現具體的功能)。
模型(Model) “資料模型”(Model)用於封裝與應用程式的業務邏輯相關的資料以及對資料的處理方法。“模型”有對資料直接訪問的權力,例如對資料庫的訪問。“模型”不依賴“檢視”和“控制器”,也就是說,模型不關心它會被如何顯示或是如何作。但是模型中資料的變化一般會通過一種重新整理機制被公佈。為了實現這種機制,那些用於監視此模型的檢視必須事先在此模型上註冊,從而,檢視可以瞭解在資料模型上發生的改變。
檢視(View) 檢視層能夠實現資料有目的的顯示(理論上,這不是必需的)。在檢視中一般沒有程式上的邏輯。為了實現檢視上的重新整理功能,檢視需要訪問它監視的資料模型(Model),因此應該事先在被它監視的資料那裡註冊。
控制器(Controller) 控制器起到不同層面間的組織作用,用於控制應用程式的流程。它處理事件並作出響應。“事件”包括使用者的行為和資料模型上的改變。
二、為什麼要自己開發MVC框架
網路上有大量優秀的MVC框架可供使用,本教程並不是為了開發一個全面的、終極的MVC框架解決方案,而是將它看作是一個很好的從內部學習PHP的機會,在此過程中,你將學習面向物件程式設計和設計模式,並學習到開放中的一些注意事項。
更重要的是,你可以完全控制你的框架,並將你的想法融入到你開發的框架中。雖然不一定是做好的,但是你可以按照你的方式去開發功能和模組。
三、開始開發自己的MVC框架
在開始開發前,讓我們先來把專案建立好,假設我們建立的專案為todo,那麼接下來的第一步就是把目錄結構先設定好。
todo
雖然在這個教程中不會使用到上面的所有的目錄,但是為了以後程式的可拓展性,在一開始就把程式目錄設定好使非常必要的。下面就具體說說每個目錄的作用:
application – 存放程式程式碼
config – 存放程式配置或資料庫配置
db – 用來存放資料庫備份內容
library – 存放框架程式碼
public – 存放靜態檔案
scripts – 存放命令列工具
tmp – 存放臨時資料
在目錄設定好以後,我們接下來就要來頂一下一些程式碼的規範:
MySQL的表名需小寫並採用複數形式,如items,cars
模組名(Models)需首字母大寫,並採用單數模式,如Item,Car
控制器(Controllers)需首字母大寫,採用複數形式並在名稱中新增“Controller”,如ItemsController, CarsController
檢視(Views)採用複數形式,並在後面新增行為作為檔案,如:items/, cars/
上述的一些規則是為了能在程式鍾更好的進行互相的呼叫。接下來就開始真正的編碼了。
第一步將所有的的請求都重定向到public目錄下,解決方案是在todo檔案下新增一個cesss檔案,檔案內容為:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ^$ public/ [L]
RewriteRule (.*) public/$1 [L]
</IfModule>
在我們把所有的請求都重定向到public目錄下以後,我們就需要將所有的資料請求都再重定向到public下的檔案,於是就需要在public資料夾下也新建一個cess檔案,檔案內容為:
<IfModule mod_rewrite.c>
RewriteEngine On
#如果檔案存在就直接訪問目錄不進行RewriteRule
RewriteCond %{REQUEST_FILENAME} !-f
#如果目錄存在就直接訪問目錄不進行RewriteRule
RewriteCond %{REQUEST_FILENAME} !-d
#將所有其他URL重寫到
RewriteRule ^(.*)$ $1 [PT,L]
</IfModule>
這麼做的主要原因有:
可以使程式有一個單一的入口,將所有除靜態程式以外的程式都重定向到上;
可以用來生成利於SEO的URL,想要更好的配置URL,後期可能會需要URL路由,這裡先不做介紹了。
做完上面的操作,就應該知道我們需要做什麼了,沒錯!在public目錄下新增檔案,檔案內容為:
<?php
define('DS',DIRECTORY_SEPARATOR);
define('ROOT',dirname(dirname(__FILE__)));
$url = $_GET['url'];
require_once(.'library'.'');
注意上面的PHP程式碼中,並沒有新增PHP結束符號”?>”,這麼做的主要原因是:對於只包含PHP程式碼的檔案,結束標誌(“?>”)最好不存在,PHP自身並不需要結束符號,不新增結束符號可以很大程度上防止末尾被新增額外的注入內容,讓程式更加安全。
在中,我們對library資料夾下的發起了請求,那麼這個啟動檔案中到底會包含哪些內容呢?
<?php
require_once(.'config' .'');
require_once(.'library' .'');
以上檔案都可以直接在檔案中引用,我們這麼做的原因是為了在後期管理和拓展中更加的方便,所以把需要在一開始的時候就載入執行的.程式統一放到一個單獨的檔案中引用。
先來看看config檔案下的config 檔案,該檔案的主要作用是設定一些程式的配置項及資料庫連線等,主要內容為:
<?php
# 設定是否為開發狀態
define('DEVELOPMENT_ENVIRONMENT',true);
# 設定資料庫連線所需資料
define('DB_HOST','localhost');
define('DB_NAME','todo');
define('DB_USER','root');
define('DB_PASSWORD','root');
應該說涉及到的內容並不多,不過是一些基礎資料的一些設定,再來看看library下的共用檔案應該怎麼寫。
<?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');
ini_set('log_errors','On');
ini_set('error_log',. 'tmp' . 'logs' . '');
}
}
/* 檢測敏感字元轉義(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);
$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) . '')) {
require_once(ROOT . DS . 'library' . DS . strtolower($className) . '');
} else if (file_exists(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '')) {
require_once(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '');
} else if (file_exists(ROOT . DS . 'application' . DS . 'models' . DS . strtolower($className) . '')) {
require_once(ROOT . DS . 'application' . DS . 'models' . DS . strtolower($className) . '');
} else {
/* 生成錯誤程式碼 */
}
}
setReporting();
removeMagicQuotes();
unregisterGlobals();
callHook();
接下來的操作就是在library中建立程式所需要的基類,包括控制器、模型和檢視的基類。
新建控制器基類為,控制器的主要功能就是總排程,具體具體內容如下:
<?php
class Controller {
protected $_model;
protected $_controller;
protected $_action;
protected $_template;
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();
}
}
新建控制器基類為,考慮到模型需要對資料庫進行處理,所以可以新建一個數據庫基類,模型去繼承。
新建,程式碼如下:
<?php
class 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;
}
}
/** 中斷資料庫連線 **/
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` = ''l_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);
}
}
新建,程式碼如下:
<?php
class 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() {
}
}
新建檢視基類為,具體程式碼如下:
<?php
class Template {
protected $variables = array();
protected $_controller;
protected $_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(. 'application' . 'views' . $this->_controller . '')) {
include(. 'application' . 'views' . $this->_controller . '');
} else {
include(. 'application' . 'views' . '');
}
include (. 'application' . 'views' . $this->_controller . $this->_action . '');
if (file_exists(. 'application' . 'views' . $this->_controller . '')) {
include (. 'application' . 'views' . $this->_controller . '');
} else {
include (. 'application' . 'views' . '');
}
}
}
做完了以上這麼多操作,基本上整個MVC框架已經出來了,下面就該製作我們的站點了。我們要做的站點其實很簡單,一個ToDo程式。
首先是在我們的/application/controller/ 目錄下面新建一個站點控制器類為ItemsController,命名為,內容為:
<?php
class 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() {
$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(' into items (item_name) values (''l_real_escape_string($todo).'')'));
}
function ($id) {
$this->set('title','Success - My Todo List App');
$this->set('todo',$this->Item->query(' from items where id = ''l_real_escape_string($id).'''));
}
}
接下來就是先建站點的模型,在我們的/application/model/ 目錄下面先建一個站點模型類為Item,內容直接繼承Model,程式碼如下:
<?php
class Item extends Model {
}
最後一步是設定我們站點的檢視部分,我們現在/application/views/目錄下新建一個items的資料夾,再在items資料夾下建立與控制器重Action相同的檔案,分別為,,,,考慮到這麼頁面中可能需要共用頁首和頁尾,所以再新建兩個檔案,命名為,,每個檔案的程式碼如下:
檔案:檢視單條待處理事務
<h2><?php echo $todo['Item']['item_name']?></h2>
<a href="../../../items//<?php echo $todo['Item']['id']?>">
<span>Delete this item</span>
</a>
檔案:檢視所有待處理事務
<form action="../items/add" method="post">
<input type="text" value="I have to..." onclick="e=''" 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 d. Click here to go back.</a><br/>
:頁首檔案
<html>
<head>
<title><?php echo $title?></title>
<style>
{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>
:頁尾檔案
1
2
</body>
</html>
當然還有一個必不可少的操作就是在資料中中建立一張表,具體程式碼如下:
CREATE TABLE IF NOT EXISTS `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item_name` var255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=17 ;
至此一個使用MVC開發的網站就開發完成了。