手把手編寫自己的PHPMVC框架實例教程

2021-03-02 php開發案例

MVC模式(Model-View-Controller)是軟體工程中的一種軟體架構模式,把軟體系統分為三個基本部分:模型(Model)、視圖(View)和控制器(Controller)。

1 什麼是MVC

MVC模式(Model-View-Controller)是軟體工程中的一種軟體架構模式,把軟體系統分為三個基本部分:模型(Model)、視圖(View)和控制器(Controller)。
MVC模式的目的是實現一種動態的程序設計,使後續對程序的修改和擴展簡化,並且使程序某一部分的重複利用成為可能。除此之外,此模式通過對複雜度的簡化,使程序結構更加直觀。軟體系統通過對自身基本部份分離的同時也賦予了各個基本部分應有的功能。
簡而言之,
模型Model – 管理所有資料庫相關的邏輯。模型提供了連接和操作資料庫的抽象層。
控制器Controller - 負責所有的業務邏輯,比如 if/else 邏輯。
視圖View – 負責界面顯示,如HMTL/XML/JSON顯示。
PHP MVC教程

2 為什麼要自己開發MVC框架

網絡上有大量優秀的MVC框架可供使用,本教程並不是為了開發一個全面的、終極的MVC框架解決方案,而是將它看作是一個很好的從內部學習PHP的機會,在此過程中,你將學習面向對象編程和MVC設計模式,並學習到開發中的一些注意事項。
更重要的是,你可以完全控制你的框架,並將你的想法融入到你開發的框架中。雖然不一定是做好的,但是你可以按照你的方式去開發功能和模塊。
3 開始開發自己的MVC框架


3.1 目錄準備

在開始開發前,讓我們先來把項目建立好,假設我們建立的項目為 todo,MVC的框架可以命名為 FastPHP,那麼接下來的第一步就是把目錄結構先設置好。
PHP MVC簡單目錄
雖然在這個教程中不會使用到上面的所有的目錄,但是為了以後程序的可拓展性,在一開始就把程序目錄設置好使非常必要的。下面就具體說說每個目錄的作用:

application – 應用代碼
config – 程序配置或資料庫配置
fastphp - 框架核心目錄
public – 靜態文件
runtime - 臨時數據目錄
scripts – 命令行工具

3.2 代碼規範

在目錄設置好以後,我們接下來就要來規定一下代碼的規範:

MySQL的表名需小寫,如:item,car
模塊名(Models)需首字母大寫,,並在名稱後添加「Model」,如:ItemModel,CarModel
控制器(Controllers)需首字母大寫,,並在名稱中添加「Controller」,如:ItemsController,CarsController
視圖(Views)部署結構為「控制器名/行為名」,如:item/view.php,car/buy.php
上述的一些規則是為了能在程序中更好的進行互相的調用。接下來就開始真正的PHP MVC編程了。

3.3 重定向


將所有的數據請求都重定向 index.php 文件,在 todo 目錄下新建一個 .htaccess 文件,文件內容為:
 RewriteEngine On # 確保請求路徑不是一個文件名或目錄 RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # 重定向所有請求到 index.php?url=PATHNAME RewriteRule ^(.*)$ index.php?url=$1 [PT,L] 
這樣做的主要原因有:
程序有一個單一的入口;
除靜態程序,其他所有程序都重定向到 index.php 上;
可以用來生成利於SEO的URL,想要更好的配置URL,後期可能會需要URL路由,這裡先不做介紹了。


3.4 入口文件


做完上面的操作,就應該知道我們需要做什麼了,沒錯!在 public 目錄下添加 index.php 文件,文件內容為:
 // 應用目錄為當前目錄 define('APP_PATH', __DIR__.'/'); // 開啟調試模式 define('APP_DEBUG', true); // 加載框架 require './fastphp/FastPHP.php';
注意,上面的PHP代碼中,並沒有添加PHP結束符號」?>」,這麼做的主要原因是,對於只有 PHP 代碼的文件,結束標誌(「?>」)最好不存在,PHP自身並不需要結束符號,不添加結束符號可以很大程度上防止末尾被添加額外的注入內容,讓程序更加安全。


3.5 配置文件和主請求


在 index.php 中,我們對 fastphp  文件夾下的 FastPHP.php 發起了請求,那麼 FastPHP.php 這個啟動文件中到底會包含哪些內容呢?

 // 初始化常量 defined('ROOT') or define('ROOT', __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/');

// 類文件擴展名

const EXT = '.class.php';

// 包含配置文件

require APP_PATH . 'config/config.php';

// 包含核心框架類

require ROOT . 'Core.php';

// 實例化核心類

$fast = new Fast;
$fast->run();


 以上文件都其實可以直接在 index.php 文件中包含,常量也可以直接在 index.php 中定義,我們這麼做的原因是為了在後期管理和拓展中更加的方便,所以把需要在一開始的時候就加載運行的程序統一放到一個單獨的文件中引用。
先來看看config文件下的 config .php 文件,該文件的主要作用是設置一些程序的配置項及資料庫連接等,主要內容為:

 /** 變量配置 **/

define('DB_NAME', 'todo');
define('DB_USER', 'root');
define('DB_PASSWORD', 'root');
define('DB_HOST', 'localhost');


應該說 config.php 涉及到的內容並不多,不過是一些基礎資料庫的設置,再來看看 fastphp下的共用框架入口文件 Core.php 應該怎麼寫。
 /**
 * FastPHP核心框架
 */

class Fast {

// 運行程序

function run() {

spl_autoload_register(array($this, 'loadClass'));

$this->setReporting();

$this->removeMagicQuotes();

$this->unregisterGlobals();

$this->callHook();
    }

// 主請求方法,主要目的是拆分URL請求

function callHook() {

if (!empty($_GET['url'])){
            $url = $_GET['url'];
            $urlArray = explode("/",$url);

// 獲取控制器名

$controllerName = ucfirst(empty($urlArray[0]) ? 'Index' : $urlArray[0]);
 $controller = $controllerName . 'Controller';

// 獲取動作名

array_shift($urlArray);
$action = empty($urlArray[0]) ? 'index' : $urlArray[0];

//獲取URL參數

array_shift($urlArray);
 $queryString = empty($urlArray) ? array() : $urlArray;
        }

// 數據為空的處理

$action = empty($action) ? 'index' : $action;
 $queryString  = empty($queryString) ? array() : $queryString;

// 實例化控制器

$int = new $controller($controllerName, $action);

// 如果控制器存和動作存在,這調用並傳入URL參數

if ((int)method_exists($controller, $action)) {
            call_user_func_array(array($int, $action), $queryString);
        } else {

exit($controller . "控制器不存在");
        }
    }

// 檢測開發環境

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]);
                    }
                }
            }
        }
    }

//自動加載控制器和模型類 

static function loadClass($class) {
        $frameworks = ROOT . $class . EXT;
        $controllers = APP_PATH . 'application/controllers/' . $class . EXT;
        $models = APP_PATH . 'application/models/' . $class . EXT;

if (file_exists($frameworks)) {

// 加載框架核心類

include $frameworks;
        } elseif (file_exists($controllers)) {

// 加載應用控制器類

include $controllers;
        } elseif (file_exists($models)) {

//加載應用模型類

include $models;
        } else { /* 錯誤代碼 */ }
    }


}
下面重點講解主請求方法 callHook(),首先我們想看看我們的 URL 會這樣:
yoursite.com/controllerName/actionName/queryString
callHook()的作用就是,從全局變量 $_GET['url']變量中獲取 URL,並將其分割成三部分:$controller、$action 和 $queryString。
例如,URL連結為:todo.com/item/view/1/first-item,那麼
$controller 就是:items
$action 就是:view
查詢字符串Query String就是:array(1, first-item)
分割完成後,會實例化一個新的控制器:$controller.』Controller』(其中「.」是連字符),並調用其方法 $action。


3.6 控制器/Controller基類

接下來的操作就是在 fastphp 中建立程序所需的基類,包括控制器、模型和視圖的基類。
新建控制器基類為 Controller.class.php,控制器的主要功能就是總調度,具體具體內容如下:
 /**
 * 控制器基類
 */

class Controller {

protected $_controller;

protected $_action;

protected $_view;

// 構造函數,初始化屬性,並實例化對應模型

function __construct($controller, $action) {

$this->_controller = $controller;

$this->_action = $action;

$this->_view = new View($controller, $action);
    }

function set($name, $value) {

$this->_view->set($name, $value);
    }

function __destruct() {

$this->_view->render();
    }


}
Controller 類實現所有控制器、模型和視圖(View類)的通信。在執行析構函數時,我們可以調用 render() 來顯示視圖(view)文件。


3.7 模型Model基類


新建模型基類為 Model.class.php,模型基類 Model.class.php 代碼如下:
 class Model extends Sql {

protected $_model;

protected $_table;

function __construct() {

// 連接資料庫

$this->connect(DB_HOST,DB_USER,DB_PASSWORD,DB_NAME);

// 轉換模型+Model為模型名稱 // 獲取對象所屬類的名稱

$this->_model = get_class($this);

$this->_model = rtrim($this->_model, 'Model');

// 資料庫表名與類名一致

$this->_table = strtolower($this->_model);
    } function __destruct() {
    }
}
考慮到模型需要對資料庫進行處理,所以單獨建立一個資料庫基類 Sql.class.php,模型基類繼承 Sql.class.php,代碼如下:
 class Sql { 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);
    }

/** 根據條件 (id) 查詢 **/

function select($id) {
        $query = 'select * from `'.$this->_table.'` where `id` = \''.mysql_real_escape_string($id).'\''; return $this->query($query, 1);
    }

/** 根據條件 (id) 刪除 **/

function delete($id) {
        $query = 'delete from `'.$this->_table.'` where `id` = \''.mysql_real_escape_string($id).'\''; return $this->query($query); 
    }

/** 自定義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] = ucfirst($table[$i]); $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);
    }

/** 獲取錯誤信息 **/

function getError() {

return mysql_error($this->_dbHandle);
    }


}
應該說,Sql.class.php 是框架的核心部分。為什麼?因為通過它,我們創建了一個 SQL 抽象層,可以大大減少了資料庫的編程工作。connect() 和 disconnect() 方法比較簡單,不多做說明,重點講講 Query查詢。假設我們有如下的一段 SQL 查詢語句:
SELECT table1.field1, table1.field2, table2.field3, table2.field4 FROM table1,table2 WHERE …
如果使用上面的 SQL 基類,首先要做的工作是選出要輸出的欄位以及相對應的數據表,然後把它們放到數組中,其中,$field 和 $table 使用相同的索引值。在上面的例子中,它們是這樣的:
$field = array(field1,field2,field3,field4);
$table = array(table1,table1,table2,table2);
腳本會展開所有的數據行,並將數據錶轉換成一個模型名(如去除複數和首字母大寫)。查詢結果最終保存在一個多維數組中,然後返回,格式類似於:$var['modelName']['fieldName']。這樣輸出方式可以非常便於在視圖中使用這些元素。


3.8 視圖View類


視圖類 View.class.php 內容如下:
 /**
 * 視圖基類
 */

class View {

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);
        $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);
        }

// 頁內容文件

include (APP_PATH . 'application/views/' . $this->_controller . '/' . $this->_action . '.php');

// 頁腳文件

if (file_exists($controllerFooter)) { i

nclude ($controllerFooter);
        } else {

include ($defaultFooter);
        }
    }


}
這樣我們的核心的PHP MVC框架就編寫完成了,下面我們開始編寫應用來測試框架功能。


4 應用


4.1 資料庫部署


在 SQL 中新建一個 todo 資料庫,使用下面的語句增加 item 數據表並插入2條記錄:
CREATE TABLE `items` (
    `id` int(11) NOT NULL auto_increment,
    `item_name` varchar(255) NOT NULL,
    PRIMARY KEY (`id`)
);

INSERT INTO `items` VALUES(1, 'Hello World.');

INSERT INTO `items` VALUES(2, 'Lets go!');
4.2 部署模型


然後,我們還需要在 models 目錄中創建一個 ItemModel.php 模型,內容如下:
 class ItemModel extends Model {

/** 新增數據 **/

function add($value){ 
        $query = 'insert into `'.$this->_table.'` (item_name) values (\''.mysql_real_escape_string($value).'\')';

return $this->query($query);
    }

/** 新增數據 **/

function update($id, $value){
        $query = 'update `'.$this->_table.'` set item_name = \''.mysql_real_escape_string($value).'\' where `id` = \''.mysql_real_escape_string($id).'\''; return $this->query($query);
    }    
}
模型內容為空。因為 Item 模型繼承了 Model,所以它擁有 Model 的所有功能。


4.3 部署控制器


在 controllers 目錄下創建一個 ItemsController.php 控制器,內容如下:
 class ItemController extends Controller { // 首頁方法,測試框架自定義DB查詢 function index() {
        $item = new ItemModel; $this->set('title', '全部條目'); $this->set('todo', $item->query('select * from item'));
    } // 添加記錄,測試框架DB記錄創建(Create) function add() {
        $value = $_POST['value'];
        $item = new ItemModel; $this->set('title', '添加成功'); $this->set('todo', $item->add($value));
    } // 查看記錄,測試框架DB記錄讀取(Read) function view($id = null,$name = null) {
        $item = new ItemModel; $this->set('title', '正在查看'. $name); $this->set('todo', $item->select($id));
    } // 更新記錄,測試框架DB記錄更新(Update) function update() {
        $id = $_POST['id'];
        $value = $_POST['value'];
        $item = new ItemModel; $this->set('title', '修改成功'); $this->set('todo', $item->update($id, $value));
    } // 刪除記錄,測試框架DB記錄刪除(Delete) function delete($id = null) {
        $item = new ItemModel; $this->set('title','刪除成功'); $this->set('todo',$item->delete($id));
    }


}


4.4 部署視圖


在 views 目錄下新建 header.php 和 footer.php 兩個頁頭頁腳模板

剩下的內容留給大家思考,歡迎留言

相關焦點

  • 手把手編寫自己的 PHP MVC 框架實例教程
    網絡上有大量優秀的MVC框架可供使用,本教程並不是為了開發一個全面的、終極的MVC框架解決方案,而是將它看作是一個很好的從內部學習PHP的機會,在此過程中,你將學習面向對象編程和MVC設計模式,並學習到開發中的一些注意事項。
  • 自學MVC看這裡——全網最全ASP.NET MVC 教程匯總
    ) - Part.2ASP.NET MVC中使用View Model分離領域模型探秘ASP.NET MVC框架傳遞加載過程3.How: 如何使用Asp.net MVC 框架進行開發, Asp.net MVC 入門教程及實例開發七天學會ASP.NET MVC 5系列教程,該系列入門教程由淺至深,介紹了MVC5的使用,涉及了一些安全方面的功能(授權認證,角色管理
  • 簡述PHP網站開發的MVC模式
    為了提高開發時候的代碼重用和開發速度,php
  • PHP學習資料推薦(書籍篇)
    根據我自己對php後端認知,以及現在市場需求了解到的,推薦下學習資料,分書籍,視頻和公眾號,博客四類,這次主要推薦書籍,方便大家後面學習提高。                           (2)php框架部分主要是看各種開源框架的官方手冊,建議一定去看下,laravel的手冊和源碼,代表了目前框架設計最前沿的思想,關注性能的框架建議去看下yaf(百度系,新浪系的人喜歡                                         用),再就是phalcon都是c擴展開發的框架。
  • SSM框架整合完整教程
    ,既然框架各有優點,那麼我們是否可以將這幾個框架進行整合?下面就開始手把手教你搭建SSM框架,在這之前我們先總結一下這三個框架MyBatis是一個優秀的基於java的持久層框架,它內部封裝了jdbc,使開發者只需要關注sql語句本身,而不需要花費精力去處理加載驅動、創建連接、創建statement等繁雜的過程。
  • 使用PHP框架的十大好處
    不會因為你新建了一個/inc的文件夾,然後在裡面寫function.php文件,就能說明你的代碼有組織了。
  • Spring MVC框架的理解
    SpringMVC框架理解1.Spring MVC概述:Spring MVC是一個Java框架,用於構建Web應用程式。
  • 手把手教你搭建SSH框架(Eclipse版)
    在開始教程之前,先來了解SSH框架的基本概念:在文章《手把手教你搭建SSM框架(Eclipse版)》中已經對Spring、SpringMVC做了詳細介紹,若需了解請點擊連結查看,這裡只對Hibernate做介紹。
  • 常見的25種php框架
    「php是世界上最好的語言」我不敢肯定,但是至少php為我們的web開發做了很大的貢獻,毋庸置疑,php是我們web界的一大功臣,
  • 【教程資源】asp.net視頻教程 零基礎/網站實戰/項目實例開發/mvc編程在線課程
    asp.net視頻教程 零基礎/網站實戰/項目實例開發/mvc編程在線課程《需要的可以找我,你就可以擁有這個課程》微信:nhy336
  • 【Vue.js入門到實戰教程】12-在 Laravel 項目中編寫單文件 Vue 組件
    我們在《【Vue.js入門到實戰教程】11-Vue Loader(下)| 編寫一個單文件 Vue 組件》中演示了如何在 Vue CLI 原型項目中編寫單文件
  • 淺談框架模式 MVC、MVP 和 MVVM
    一種框架模式往往使用了多種設計模式,切不要把他們的關係搞混。cs文件是他的相關邏輯處理文件 事件驅動模式下,框架幫我們完成了基本的事件類型,我們要做的是在事件下完成相關業務邏輯。在前端中,也可以找到二代目大人的影分身。。
  • Minecraft:基於GameTest框架編寫模組
    、應用及使用教程的綜述,主要分為四個章節。      第一章為緒論部分,主要綜述了GameTest框架是什麼,包含什麼內容,及其發展現狀。      第二章為使用GameTest框架編寫模組的簡易入門教程,它可以幫助您快速上手。      第三章為本文的總結及對GameTest框架未來發展的展望,個人拙見,望輕噴。
  • 14個編寫Spring MVC控制器的實用小技巧
    Spring MVC框架的控制器(controller)的基礎技巧和最佳操作。在Spring MVC框架中,編寫控制器類通常是為了處理用戶提出的請求。編寫完成後,控制器會調用一個業務類來處理業務相關任務,進而重定向客戶到邏輯視圖名。Springdispatcher servlet會對邏輯視圖名進行解析,並渲染結果或輸出。這就是一個典型的「請求—響應」的完整流程。
  • 十分鐘就能快速上手的PHP爬蟲框架
    是一個爬蟲開發框架。使用該框架,您不用了解爬蟲的堆疊技術實現,爬蟲被網站屏蔽,有些網站需要登錄或驗證碼識別才能爬取等問題。簡單幾行PHP代碼,就可以創建自己的爬蟲,利用框架封裝的多進程Worker類庫,代碼更簡潔,執行效率更高的速度轉換但是,該框架只能在命令行下運行。
  • PHP 命名空間與類自動加載實現
    phprequire_once 'bootstrap.php';// 新增一個 IoC 容器,通過依賴注入獲取對象實例$container = Container::getInstance();bootApp($container);...
  • PHP 實例 - AJAX 實時搜索
    這篇文章給大家講述了PHP 實例 - AJAX 實時搜索的教程。
  • 用PHP自己寫個留言板-10(進入資料庫的大門 ——php+mysql的聯姻)
    php和資料庫交互最多的就是mysql,php對mysql有非常好的支持,在php裡面使用資料庫可以用mysqli、pdo等實例化連接資料庫。
  • mzphp:支持 scss 語法和 css sprite 的 PHP 框架
    mzphp 介紹PHP 開發框架 mzphp,擁有特點:性能,高性能極致加載
  • PHP代碼審計實戰思路淺析
    原因在於,基於mvc模式開發出來的cms不像面向過程化開發的cms那樣,一個目錄就是一個功能模塊,一個php文件就是一個功能點。基於mvc模式開發的cms,會有一個統一的入口,所有的請求都從該入口進入,然後由框架統一進行調度,且程序中的一些可重用的操作,例如資料庫操作、緩存、安全過濾等都會被封裝成框架的核心類庫,需要時再去調框架封裝好的方法。