使用PHP搭建本身的MVC框架

1、什麼是MVC

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

MVC模式的目的是實現一種動態的程序設計,使後續對程序的修改和擴展簡化,而且使程序某一部分的重複利用成爲可能。除此以外,此模式經過對複雜度的簡化,使程序結構更加直觀。軟件系統經過對自身基本部份分離的同時也賦予了各個基本部分應有的功能。專業人員能夠經過自身的專長分組:html

  • (控制器Controller)- 負責轉發請求,對請求進行處理。
  • (視圖View) – 界面設計人員進行圖形界面設計。
  • (模型Model) – 程序員編寫程序應有的功能(實現算法等等)、數據庫專家進行數據管理和數據庫設計(能夠實現具體的功能)。

mvc
模型(Model) 「數據模型」(Model)用於封裝與應用程序的業務邏輯相關的數據以及對數據的處理方法。「模 型」有對數據直接訪問的權力,例如對數據庫的訪問。「模型」不依賴「視圖」和「控制器」,也就是說,模型不關心它會被如何顯示或是如何被操做。可是模型中 數據的變化通常會經過一種刷新機制被公佈。爲了實現這種機制,那些用於監視此模型的視圖必須事先在此模型上註冊,從而,視圖能夠了解在數據模型上發生的改 變。mysql

視圖(View) 視圖層可以實現數據有目的的顯示(理論上,這不是必需的)。在視圖中通常沒有程序上的邏輯。爲了實現視圖上的刷新功能,視圖須要訪問它監視的數據模型(Model),所以應該事先在被它監視的數據那裏註冊。程序員

控制器(Controller) 控制器起到不一樣層面間的組織做用,用於控制應用程序的流程。它處理事件並做出響應。「事件」包括用戶的行爲和數據模型上的改變。算法

2、爲何要本身開發MVC框架

網絡上有大量優秀的MVC框架可供使用,本教程並非爲了開發一個全面的、終極的MVC框架解決方案,而是將它看做是一個很好的從內部學習PHP的機會,在此過程當中,你將學習面向對象編程和設計模式,並學習到開放中的一些注意事項。sql

更重要的是,你能夠徹底控制你的框架,並將你的想法融入到你開發的框架中。雖然不必定是作好的,可是你能夠按照你的方式去開發功能和模塊。數據庫

3、開始開發本身的MVC框架

在開始開發前,讓咱們先來把項目創建好,假設咱們創建的項目爲todo,那麼接下來的第一步就是把目錄結構先設置好。編程

todo

雖然在這個教程中不會使用到上面的全部的目錄,可是爲了之後程序的可拓展性,在一開始就把程序目錄設置好使很是必要的。下面就具體說說每一個目錄的做用:bootstrap

  • application – 存放程序代碼
  • config – 存放程序配置或數據庫配置
  • db – 用來存放數據庫備分內容
  • library – 存放框架代碼
  • public – 存放靜態文件
  • scripts – 存放命令行工具
  • tmp – 存放臨時數據

在目錄設置好之後,咱們接下來就要來頂一下一些代碼的規範:設計模式

  1. MySQL的表名需小寫並採用複數形式,如items,cars
  2. 模塊名(Models)需首字母大寫,並採用單數模式,如Item,Car
  3. 控制器(Controllers)需首字母大寫,採用複數形式並在名稱中添加「Controller」,如ItemsController, CarsController
  4. 視圖(Views)採用複數形式,並在後面添加行爲做爲文件,如:items/view.php, cars/buy.php

上述的一些規則是爲了能在程序鍾更好的進行互相的調用。接下來就開始真正的編碼了。

第一步將全部的的請求都重定向到public目錄下,解決方案是在todo文件下添加一個.htaccesss文件,文件內容爲:

<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule    ^$    public/    [L]
RewriteRule    (.*) public/$1    [L]
</IfModule>

在咱們把全部的請求都重定向到public目錄下之後,咱們就須要將全部的數據請求都再重定向到public下的index.php文件,因而就須要在public文件夾下也新建一個.htaccess文件,文件內容爲:

<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路由,這裏先不作介紹了。

作完上面的操做,就應該知道咱們須要作什麼了,沒錯!在public目錄下添加index.php文件,文件內容爲:

<?php

    define('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這個啓動文件中到底會包含哪些內容呢?

<?php
    require_once(ROOT.DS.'config'.DS .'config.php');
    require_once(ROOT.DS.'library'.DS .'shared.php');

以上文件均可以直接在index.php文件中引用,咱們這麼作的緣由是爲了在後期管理和拓展中更加的方便,因此把須要在一開始的時候就加載運行的程序統一放到一個單獨的文件中引用。

先來看看config文件下的config .php文件,該文件的主要做用是設置一些程序的配置項及數據庫鏈接等,主要內容爲:

<?php
    # 設置是否爲開發狀態
    define('DEVELOPMENT_ENVIRONMENT',true);
    # 設置數據庫鏈接所需數據
    define('DB_HOST','localhost');
    define('DB_NAME','todo');
    define('DB_USER','root');
    define('DB_PASSWORD','root');

應該說config.php涉及到的內容並很少,不過是一些基礎數據的一些設置,再來看看library下的共用文件shared.php應該怎麼寫。

<?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',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);
        $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();

接下來的操做就是在library中創建程序所須要的基類,包括控制器、模型和視圖的基類。

新建控制器基類爲controller.class.php,控制器的主要功能就是總調度,具體具體內容以下:

<?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();
        }
    }

新建控制器基類爲model.class.php,考慮到模型須要對數據庫進行處理,因此能夠新建一個數據庫基類sqlquery.class.php,模型去繼承sqlquery.class.php。

新建sqlquery.class.php,代碼以下:

<?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` = \''.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);
                 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);
       }
    }

新建model.class.php,代碼以下:

<?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() {
        }
    }

新建視圖基類爲template.class.php,具體代碼以下:

<?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(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');
           }
        }
    }

作完了以上這麼多操做,基本上整個MVC框架已經出來了,下面就該製做咱們的站點了。咱們要作的站點其實很簡單,一個ToDo程序。

首先是在咱們的/application/controller/ 目錄下面新建一個站點控制器類爲ItemsController,命名爲itemscontroller.php,內容爲:

<?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('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).'\''));
       }
    }

接下來就是先建站點的模型,在咱們的/application/model/ 目錄下面先建一個站點模型類爲Item,內容直接繼承Model,代碼以下:

<?php
class Item extends Model {
}

最後一步是設置咱們站點的視圖部分,咱們如今/application/views/目錄下新建一個items的文件夾,再在items文件夾下建 立與控制器重Action相同的文件,分別爲view.php,viewall.php,add.php,delete.php,考慮到這麼頁面中可能需 要共用頁首和頁尾,因此再新建兩個文件,命名爲header.php,footer.php,每一個文件的代碼以下:

view.php文件:查看單條待處理事務

<h2><?php echo $todo['Item']['item_name']?></h2>
<a href="../../../items/delete/<?php echo $todo['Item']['id']?>">
<span>Delete this item</span>
</a>

viewall.php文件:查看全部待處理事務

<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?>

add.php文件:添加待處理事務

<a href="../items/viewall">Todo successfully added. Click here to go back.</a><br/>

delete.php文件:刪除事務

<a href="../../items/viewall">Todo successfully deleted. Click here to go back.</a><br/>

header.php:頁首文件

<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>

footer.php:頁尾文件

</body>
</html>

固然還有一個必不可少的操做就是在數據中中創建一張表,具體代碼以下:

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 查看新建的站點。 

 

摘自:http://nonfu.me/p/5284.html

相關文章
相關標籤/搜索