1、MVC
MVC模式(Model-View-Controller)是軟件工程中的一種軟件架構(gòu)模式,把軟件系統(tǒng)分為三個(gè)基本部分:模型(Model)、視圖(View)和控制器(Controller)。
PHP中MVC模式也稱Web MVC,從上世紀(jì)70年代進(jìn)化而來。MVC的目的是實(shí)現(xiàn)一種動(dòng)態(tài)的程序設(shè)計(jì),便于后續(xù)對程序的修改和擴(kuò)展簡化,并且使程序某一部分的重復(fù)利用成為可能。除此之外,此模式通過對復(fù)雜度的簡化,使程序結(jié)構(gòu)更加直觀。軟件系統(tǒng)通過對自身基本部份分離的同時(shí),也賦予了各個(gè)基本部分應(yīng)有的功能。
MVC各部分的職能:
模型Model?– 管理大部分的業(yè)務(wù)邏輯和所有的數(shù)據(jù)庫邏輯。模型提供了連接和操作數(shù)據(jù)庫的抽象層。
控制器Controller?- 負(fù)責(zé)響應(yīng)用戶請求、準(zhǔn)備數(shù)據(jù),以及決定如何展示數(shù)據(jù)。
視圖View?– 負(fù)責(zé)渲染數(shù)據(jù),通過HTML方式呈現(xiàn)給用戶。
一個(gè)典型的Web MVC流程:
Controller截獲用戶發(fā)出的請求;
Controller調(diào)用Model完成狀態(tài)的讀寫操作;
Controller把數(shù)據(jù)傳遞給View;
View渲染最終結(jié)果并呈獻(xiàn)給用戶。
2、為什么自己開發(fā)MVC框架
網(wǎng)絡(luò)上有大量優(yōu)秀的MVC框架可供使用,本教程并不是為了開發(fā)一個(gè)全面的、終極的MVC框架解決方案,而是將它看作是一個(gè)很好的從內(nèi)部學(xué)習(xí)PHP的機(jī)會(huì),在此過程中,你將學(xué)習(xí)面向?qū)ο缶幊毯蚆VC設(shè)計(jì)模式,并學(xué)習(xí)到開發(fā)中的一些注意事項(xiàng)。
更重要的是,你可以完全控制你的框架,并將你的想法融入到你開發(fā)的框架中。雖然不一定是做好的,但是你可以按照你的方式去開發(fā)功能和模塊。
3、準(zhǔn)備開發(fā)自己的MVC框架
3.1目錄準(zhǔn)備
在開始開發(fā)前,讓我們先來把項(xiàng)目建立好,假設(shè)我們建立的項(xiàng)目為 myphp-frame,MVC的框架可以命名為 MyPHP,那么接下來的第一步就是把目錄結(jié)構(gòu)先設(shè)置好。
雖然在這個(gè)教程中不會(huì)使用到上面的所有的目錄,但是為了以后程序的可拓展性,在一開始就把程序目錄設(shè)置好使非常必要的。下面就具體說說每個(gè)目錄的作用:
application?– 應(yīng)用代碼
config?– 程序配置或數(shù)據(jù)庫配置
myphp?- 框架核心目錄
public?– 靜態(tài)文件
runtime?- 臨時(shí)數(shù)據(jù)目錄
3.2代碼規(guī)范
在目錄設(shè)置好以后,我們接下來就要來規(guī)定一下代碼的規(guī)范:
MySQL的表名需小寫,如:item,car
模塊名(Models)需首字母大寫,,并在名稱后添加“Model”,如:ItemModel,CarModel
控制器(Controllers)需首字母大寫,,并在名稱中添加“Controller”,如:ItemController,CarController
視圖(Views)部署結(jié)構(gòu)為“控制器名/行為名”,如:item/view.php,car/buy.php
上述的一些規(guī)則是為了能在程序中更好的進(jìn)行互相的調(diào)用。接下來就開始真正的PHP MVC編程了
3.3重定向
將所有的數(shù)據(jù)請求都重定向 index.php 文件,在 myphp-frame 目錄下新建一個(gè)?.htaccess?文件,文件內(nèi)容為:
<IfModule mod_rewrite.c> RewriteEngine On # 確保請求路徑不是一個(gè)文件名或目錄 RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # 重定向所有請求到 index.php?url=PATHNAME RewriteRule ^(.*)$ index.php?url=$1 [PT,L] </IfModule>
這樣做的主要原因有:
程序有一個(gè)單一的入口;
除靜態(tài)程序,其他所有程序都重定向到 index.php 上;
可以用來生成利于SEO的URL,想要更好的配置URL,后期可能會(huì)需要URL路由,這里先不做介紹了。
3.4入口文件
做完上面的操作,就應(yīng)該知道我們需要做什么了,沒錯(cuò)!在 myphp-frame目錄下添加 index.php 文件,文件內(nèi)容為:
<?php // 應(yīng)用目錄為當(dāng)前目錄 define('APP_PATH', __DIR__.'/'); // 開啟調(diào)試模式 define('APP_DEBUG', true); // 網(wǎng)站根URL define('APP_URL', 'http://localhost/myphp'); // 加載框架 require './myphp/MyPHP.php';
注意,上面的PHP代碼中,并沒有添加PHP結(jié)束符號(hào)”?>”,這么做的主要原因是,對于只有 PHP 代碼的文件,結(jié)束標(biāo)志(“?>”)最好不存在,PHP自身并不需要結(jié)束符號(hào),不添加結(jié)束符號(hào)可以很大程度上防止末尾被添加額外的注入內(nèi)容,讓程序更加安全。
3.5配置文件和主請求
在 index.php 中,我們對 myphp 文件夾下的 MyPHP.php 發(fā)起了請求,那么 MyPHP.php 這個(gè)啟動(dòng)文件中到底會(huì)包含哪些內(nèi)容呢?
<?php // 初始化常量 defined('FRAME_PATH') or define('FRAME_PATH', __DIR__.'/'); defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']).'/'); defined('APP_DEBUG') or define('APP_DEBUG', false); defined('CONFIG_PATH') or define('CONFIG_PATH', APP_PATH.'config/'); defined('RUNTIME_PATH') or define('RUNTIME_PATH', APP_PATH.'runtime/'); // 包含配置文件 require APP_PATH . 'config/config.php'; //包含核心框架類 require FRAME_PATH . 'Core.php'; // 實(shí)例化核心類 $fast = new Core; $fast->run();
以上文件都其實(shí)可以直接在 index.php 文件中包含,常量也可以直接在 index.php 中定義,我們這么做的原因是為了在后期管理和拓展中更加的方便,所以把需要在一開始的時(shí)候就加載運(yùn)行的程序統(tǒng)一放到一個(gè)單獨(dú)的文件中引用。
先來看看config文件下的 config .php 文件,該文件的主要作用是設(shè)置一些程序的配置項(xiàng)及數(shù)據(jù)庫連接等,主要內(nèi)容為:
<?php /** 變量配置 **/ define('DB_NAME', mydb); define('DB_USER', 'root'); define('DB_PASSWORD', 'root'); define('DB_HOST', 'localhost');
應(yīng)該說 config.php 涉及到的內(nèi)容并不多,不過是一些基礎(chǔ)數(shù)據(jù)庫的設(shè)置,再來看看 myphp下的共用框架入口文件 Core.php 應(yīng)該怎么寫。
<?php /** * MyPHP核心框架 */ class Core { // 運(yùn)行程序 function run() { spl_autoload_register(array($this, 'loadClass')); $this->setReporting(); $this->removeMagicQuotes(); $this->unregisterGlobals(); $this->Route(); } // 路由處理 function Route() { $controllerName = 'Index'; $action = 'index'; if (!empty($_GET['url'])) { $url = $_GET['url']; $urlArray = explode('/', $url); // 獲取控制器名 $controllerName = ucfirst($urlArray[0]); // 獲取動(dòng)作名 array_shift($urlArray); $action = empty($urlArray[0]) ? 'index' : $urlArray[0]; //獲取URL參數(shù) array_shift($urlArray); $queryString = empty($urlArray) ? array() : $urlArray; } // 數(shù)據(jù)為空的處理 $queryString = empty($queryString) ? array() : $queryString; // 實(shí)例化控制器 $controller = $controllerName . 'Controller'; $dispatch = new $controller($controllerName, $action); // 如果控制器存和動(dòng)作存在,這調(diào)用并傳入U(xiǎn)RL參數(shù) if ((int)method_exists($controller, $action)) { call_user_func_array(array($dispatch, $action), $queryString); } else { exit($controller . "控制器不存在"); } } // 檢測開發(fā)環(huán)境 function setReporting() { if (APP_DEBUG === 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', RUNTIME_PATH. 'logs/error.log'); } } // 刪除敏感字符 function stripSlashesDeep($value) { $value = is_array($value) ? array_map('stripSlashesDeep', $value) : stripslashes($value); return $value; } // 檢測敏感字符并刪除 function removeMagicQuotes() { if ( get_magic_quotes_gpc()) { $_GET = stripSlashesDeep($_GET ); $_POST = stripSlashesDeep($_POST ); $_COOKIE = stripSlashesDeep($_COOKIE); $_SESSION = stripSlashesDeep($_SESSION); } } // 檢測自定義全局變量(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]); } } } } } // 自動(dòng)加載控制器和模型類 static function loadClass($class) { $frameworks = FRAME_PATH . $class . '.class.php'; $controllers = APP_PATH . 'application/controllers/' . $class . '.class.php'; $models = APP_PATH . 'application/models/' . $class . '.class.php'; if (file_exists($frameworks)) { // 加載框架核心類 include $frameworks; } elseif (file_exists($controllers)) { // 加載應(yīng)用控制器類 include $controllers; } elseif (file_exists($models)) { //加載應(yīng)用模型類 include $models; } else { /* 錯(cuò)誤代碼 */ } } }
下面重點(diǎn)講解主請求方法 callHook(),首先我們想看看我們的 URL 會(huì)這樣:
yoursite.com/controllerName/actionName/queryString
callHook()的作用就是,從全局變量 $_GET['url']變量中獲取 URL,并將其分割成三部分:$controller、$action 和 $queryString。
例如,URL鏈接為:myphp.com/item/view/1/first-item,那么
$controller 就是:item
$action 就是:view
查詢字符串Query String就是:array(1, first-item)
分割完成后,會(huì)實(shí)例化一個(gè)新的控制器:$controller.'Controller'(其中“.”是連字符),并調(diào)用其方法 $action。
3.6控制器/Controller基類
接下來的操作就是在 myphp 中建立程序所需的基類,包括控制器、模型和視圖的基類。
新建控制器基類為 Controller.class.php,控制器的主要功能就是總調(diào)度,具體具體內(nèi)容如下:
<?php /** * 控制器基類 */ class Controller { protected $_controller; protected $_action; protected $_view; // 構(gòu)造函數(shù),初始化屬性,并實(shí)例化對應(yīng)模型 function __construct($controller, $action) { $this->_controller = $controller; $this->_action = $action; $this->_view = new View($controller, $action); } // 分配變量 function assign($name, $value) { $this->_view->assign($name, $value); } // 渲染視圖 function __destruct() { $this->_view->render(); } }
Controller 類實(shí)現(xiàn)所有控制器、模型和視圖(View類)的通信。在執(zhí)行析構(gòu)函數(shù)時(shí),我們可以調(diào)用 render() 來顯示視圖(view)文件。
3.7模型Model基類
新建模型基類為 Model.class.php,模型基類 Model.class.php 代碼如下:
<?php class Model extends Sql { protected $_model; protected $_table; function __construct() { // 連接數(shù)據(jù)庫 $this->connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME); // 獲取模型名稱 $this->_model = get_class($this); $this->_model = rtrim($this->_model, 'Model'); // 數(shù)據(jù)庫表名與類名一致 $this->_table = strtolower($this->_model); } function __destruct() { } }
考慮到模型需要對數(shù)據(jù)庫進(jìn)行處理,所以單獨(dú)建立一個(gè)數(shù)據(jù)庫基類 Sql.class.php,模型基類繼承 Sql.class.php,代碼如下:
<?php class Sql { protected $_dbHandle; protected $_result; // 連接數(shù)據(jù)庫 public function connect($host, $user, $pass, $dbname) { try { $dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8", $host, $dbname); $this->_dbHandle = new PDO($dsn, $user, $pass, array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC)); } catch (PDOException $e) { exit('錯(cuò)誤: ' . $e->getMessage()); } } // 查詢所有 public function selectAll() { $sql = sprintf("select * from `%s`", $this->_table); $sth = $this->_dbHandle->prepare($sql); $sth->execute(); return $sth->fetchAll(); } // 根據(jù)條件 (id) 查詢 public function select($id) { $sql = sprintf("select * from `%s` where `id` = '%s'", $this->_table, $id); $sth = $this->_dbHandle->prepare($sql); $sth->execute(); return $sth->fetch(); } // 根據(jù)條件 (id) 刪除 public function delete($id) { $sql = sprintf("delete from `%s` where `id` = '%s'", $this->_table, $id); $sth = $this->_dbHandle->prepare($sql); $sth->execute(); return $sth->rowCount(); } // 自定義SQL查詢,返回影響的行數(shù) public function query($sql) { $sth = $this->_dbHandle->prepare($sql); $sth->execute(); return $sth->rowCount(); } // 新增數(shù)據(jù) public function add($data) { $sql = sprintf("insert into `%s` %s", $this->_table, $this->formatInsert($data)); return $this->query($sql); } // 修改數(shù)據(jù) public function update($id, $data) { $sql = sprintf("update `%s` set %s where `id` = '%s'", $this->_table, $this->formatUpdate($data), $id); return $this->query($sql); } // 將數(shù)組轉(zhuǎn)換成插入格式的sql語句 private function formatInsert($data) { $fields = array(); $values = array(); foreach ($data as $key => $value) { $fields[] = sprintf("`%s`", $key); $values[] = sprintf("'%s'", $value); } $field = implode(',', $fields); $value = implode(',', $values); return sprintf("(%s) values (%s)", $field, $value); } // 將數(shù)組轉(zhuǎn)換成更新格式的sql語句 private function formatUpdate($data) { $fields = array(); foreach ($data as $key => $value) { $fields[] = sprintf("`%s` = '%s'", $key, $value); } return implode(',', $fields); } }
應(yīng)該說,Sql.class.php 是框架的核心部分。為什么?因?yàn)橥ㄟ^它,我們創(chuàng)建了一個(gè) SQL 抽象層,可以大大減少了數(shù)據(jù)庫的編程工作。雖然 PDO 接口本來已經(jīng)很簡潔,但是抽象之后框架的可靈活性更高。
3.8視圖View類
視圖類 View.class.php 內(nèi)容如下:
<?php /** * 視圖基類 */ class View { protected $variables = array(); protected $_controller; protected $_action; function __construct($controller, $action) { $this->_controller = $controller; $this->_action = $action; } /** 分配變量 **/ function assign($name, $value) { $this->variables[$name] = $value; } /** 渲染顯示 **/ function render() { extract($this->variables); $defaultHeader = APP_PATH . 'application/views/header.php'; $defaultFooter = APP_PATH . 'application/views/footer.php'; $controllerHeader = APP_PATH . 'application/views/' . $this->_controller . '/header.php'; $controllerFooter = APP_PATH . 'application/views/' . $this->_controller . '/footer.php'; // 頁頭文件 if (file_exists($controllerHeader)) { include ($controllerHeader); } else { include ($defaultHeader); } // 頁內(nèi)容文件 include (APP_PATH . 'application/views/' . $this->_controller . '/' . $this->_action . '.php'); // 頁腳文件 if (file_exists($controllerFooter)) { include ($controllerFooter); } else { include ($defaultFooter); } } }
這樣我們的核心的PHP MVC框架就編寫完成了,下面我們開始編寫應(yīng)用來測試框架功能。
4、應(yīng)用
4.1數(shù)據(jù)庫部署
在 SQL 中新建一個(gè) mydb 數(shù)據(jù)庫,使用下面的語句增加 item 數(shù)據(jù)表并插入2條記錄:
CREATE DATABASE ` mydb ` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE ` mydb `;
CREATE TABLE `item` (
`id` int(11) NOT NULL auto_increment,
`item_name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `item` VALUES(1, 'Hello World.');
INSERT INTO `item` VALUES(2, 'Lets go!');
4.2部署模型
然后,我們還需要在 models 目錄中創(chuàng)建一個(gè) ItemModel.php 模型,內(nèi)容如下:
<?php class ItemModel extends Model { /* 業(yè)務(wù)邏輯層實(shí)現(xiàn) */ }
模型內(nèi)容為空。因?yàn)?Item 模型繼承了 Model,所以它擁有 Model 的所有功能。
4.3部署控制器
在 controllers 目錄下創(chuàng)建一個(gè) ItemController.php 控制器,內(nèi)容如下:
<?php class ItemController extends Controller { // 首頁方法,測試框架自定義DB查詢 public function index() { $items = (new ItemModel)->selectAll(); $this->assign('title', '全部條目'); $this->assign('items', $items); } // 添加記錄,測試框架DB記錄創(chuàng)建(Create) public function add() { $data['item_name'] = $_POST['value']; $count = (new ItemModel)->add($data); $this->assign('title', '添加成功'); $this->assign('count', $count); } // 查看記錄,測試框架DB記錄讀?。≧ead) public function view($id = null) { $item = (new ItemModel)->select($id); $this->assign('title', '正在查看' . $item['item_name']); $this->assign('item', $item); } // 更新記錄,測試框架DB記錄更新(Update) public function update() { $data = array('id' => $_POST['id'], 'item_name' => $_POST['value']); $count = (new ItemModel)->update($data['id'], $data); $this->assign('title', '修改成功'); $this->assign('count', $count); } // 刪除記錄,測試框架DB記錄刪除(Delete) public function delete($id = null) { $count = (new ItemModel)->delete($id); $this->assign('title', '刪除成功'); $this->assign('count', $count); } }
4.4部署視圖
在 views 目錄下新建 header.php 和 footer.php 兩個(gè)頁頭頁腳模板,內(nèi)容如下。
header.php,內(nèi)容:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <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:blue; font-family:georgia,times; font-size:20px; font-weight:normal; line-height:1.2em; text-decoration:none; } a:hover { text-decoration:underline; } 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><?php echo $title ?></h1> footer.php,內(nèi)容: </body> </html>
然后,在 views/item 創(chuàng)建以下幾個(gè)視圖文件。
index.php,瀏覽數(shù)據(jù)庫內(nèi) item 表的所有記錄,內(nèi)容:
<form action="<?php echo APP_URL ?>/item/add" method="post"> <input type="text" value="點(diǎn)擊添加" onclick="this.value=''" name="value"> <input type="submit" value="添加"> </form> <br/><br/> <?php $number = 0?> <?php foreach ($items as $item): ?> <a class="big" href="<?php echo APP_URL ?>/item/view/<?php echo $item['id'] ?>" title="點(diǎn)擊修改"> <span class="item"> <?php echo ++$number ?> <?php echo $item['item_name'] ?> </span> </a> ---- <a class="big" href="<?php echo APP_URL ?>/item/delete/<?php echo $item['id']?>">刪除</a> <br/> <?php endforeach ?>
add.php,添加記錄,內(nèi)容:
<a class="big" href="<?php echo APP_URL ?>/item/index">成功添加<?php echo $count ?>條記錄,點(diǎn)擊返回</a>
view.php,查看單條記錄,內(nèi)容:
<form action="<?php echo APP_URL ?>/item/update" method="post"> <input type="text" name="value" value="<?php echo $item['item_name'] ?>"> <input type="hidden" name="id" value="<?php echo $item['id'] ?>"> <input type="submit" value="修改"> </form> <a class="big" href="<?php echo APP_URL ?>/item/index">返回</a>
update.php,更改記錄,內(nèi)容:
<a class="big" href="<?php echo APP_URL ?>/item/index">成功修改<?php echo $count ?>項(xiàng),點(diǎn)擊返回</a>
delete.php,刪除記錄,內(nèi)容:
<a href="<?php echo APP_URL ?>/item/index">成功刪除<?php echo $count ?>項(xiàng),點(diǎn)擊返回</a>
4.5應(yīng)用測試
這樣,在瀏覽器中訪問?myphp程序:http://localhost/myphp/item/index/,就可以看到效果了。

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)
