PHP中使用MVC

PHP中使用MVC
--《細說PHP閱讀筆記》javascript

爲了解決一類共同問題總結出來的一套可複用的解決方案,這是軟件設計模式產生的初衷。不論是客戶端仍是移動端,MVC的分層設計模式解決了軟件開發中的可複用、單一職責、解耦的問題,PHP語言中的MVC設置模式也是如此。php

本文經過PHP語言來講明MVC模式如何在PHP中應用,內容包括:css

  • MVC 的工做原理
  • PHP開發框架
    • 開發框架的優點
    • 使用框架進行模塊劃分
  • 一個簡單MVC框架的分析
    • URL訪問方式(URL Parser)
    • 控制器(Controller)
    • 視圖(View)
    • 模型(Model)
    • 運行時(Runtime)

MVC 的工做原理

MVC框架圖:
html

MVC框架圖
MVC框架圖

視圖View

表明用戶交互的頁面、能夠包含HTML界面、Smarty模板等和界面相關的元素。MVC設計模式對於視圖的處理僅限於視圖上數據的採集和處理,以及用戶的點擊、拖動等事件的處理,而不包括在視圖上的業務流程處理。業務流程會交給模型層(Model)處理。java

模型Model

模型層是對業務流程、狀態的處理以及業務規則的指定。業務流程的處理過程對其餘層來講是黑箱操做,模型接受視圖的請求處理數據,返回最終的處理結果。業務模型還有一個很重要的模型--數據模型,數據模型主要指實體對象的數據保存(持久化)。好比將一張訂單保存到數據庫,從數據庫獲取訂單,全部和數據庫相關的操做限定在該模型中。mysql

控制器Controller

控制層是View層和Model層之間的一個橋樑,接收到用戶的請求,將模型和視圖匹配在一塊兒,共同完成用戶的請求。好比,用戶點擊一個連接,控制層接收到請求後,把信息傳遞給模型層,模型層處理完成以後返回視圖給用戶。git

PHP開發框架

開發框架的優點

  • 框架提升開發效率和質量
  • 框架處理了許多基礎性工做
  • 框架處理細節工做(事務處理、安全、數據流控制)
  • 框架結構性好、擴張性好
  • 框架劃分子問題,易於控制、易於延展、易於分配資源

使用框架進行模塊劃分

一個典型的後臺應用模塊的劃分web

  • 平臺操做管理
    • 登陸管理
    • 操做界面管理
  • 系統管理頻道
    • 常規管理
    • 公告管理
    • 友情連接掛你
  • 內容管理頻道
    • 圖片管理
    • 欄目管理
    • 文章管理
    • 幻燈片管理
  • 用戶管理頻道
    • 用戶組管理
    • 用戶管理

模塊設置操做sql

  • 每一個模塊能夠設置查看、添加、修改、刪除、搜索等操做
  • 模塊太大應該劃分子模塊,適合的模塊數量爲8~12個

一個簡單MVC框架的分析

分析基於《細說PHP》書中提供的框架BroPHP,從如下五個方面來分析數據庫

  • URL訪問方式(URL Parser)
  • 控制器(Controller)
  • 視圖(View)
  • 模型(Model)
  • 運行時(Runtime)

URL訪問方式

URL使用PATHINFO模式(index.php/index/index/),應用的訪問方式都是採用單一入口的訪問方式,全部訪問一個應用中的具體模塊及模塊中的某個操做,都須要在URL中經過入口文件後的參數來訪問和執行,全部訪問都會變成由URL的參數來統一解析和調度,格式以下:

不帶參數的URL

http://example.com/index.php/user/add  
複製代碼

帶有參數的URL

http://example.com/index.php/user/add/cid/5  
http://example.com/index.php/user/add/cid/5/page/6  
複製代碼

這種採用單一入口和PATHINFO模式的URL訪問是MVC實現的基礎,做爲單一入口的框架的入口brophp.php文件則負責處理基本的信息,包括了

  • 路徑信息:BroPHP框架的路徑,用戶項目的應用路徑,項目的根路徑
  • 包含框架中的函數庫文件
  • 包含全局的函數庫文件,用戶能夠本身定義函數在這個文件中
  • __autoload()自動加載類
  • 頁面緩存配置
  • 初使化時,建立項目的目錄結構
  • 解析處理URL

路徑信息處理

路徑信息會保存在$GLOBALS全局數組中,後面的頁面須要使用到直接從$GLOBALS中獲取便可

//模板文件中全部要的路徑,html\css\javascript\image\link等中用到的路徑,從WEB服務器的文檔根開始
$spath = dirname($_SERVER["SCRIPT_NAME"]);
if ($spath == "/" || $spath == "\\")
    $spath = "";
$GLOBALS["root"] = $spath . '/'; //Web服務器根到項目的根
$GLOBALS["app"] = $_SERVER["SCRIPT_NAME"] . '/';            //當前應用腳本文件
$GLOBALS["url"] = $GLOBALS["app"] . $_GET["m"] . '/';       //訪問到當前模塊
$GLOBALS["public"] = $GLOBALS["root"] . 'public/';        //項目的全局資源目錄
$GLOBALS["res"] = $GLOBALS["root"] . ltrim(APP_PATH, './') . "views/" . TPLSTYLE . "/resource/"; //當前應用模板的資源
複製代碼

包含框架中的函數庫文件

函數庫文件主要是一些經常使用的工具方法的集合,框架自帶的functions.inc.php方法庫包含了數據模型建立操做的一些列工具方法,能夠開箱即用。此外用戶也能夠自定義函數庫文件保存在對應模塊目錄下的commons/functions.inc.php位置,框架會自動引入。

//包含框架中的函數庫文件
include BROPHP_PATH . 'commons/functions.inc.php';


// 包含全局的函數庫文件,用戶能夠本身定義函數在這個文件中
$funfile = PROJECT_PATH . "commons/functions.inc.php";
if (file_exists($funfile))
    include $funfile;

複製代碼

設置包含目錄(類所在的所有目錄)

這個步驟是__autoload()自動加載類的基礎,__autoload()方法中include會自動從這些目錄中尋找要包含的類

//設置包含目錄(類所在的所有目錄), PATH_SEPARATOR 分隔符號 Linux(:) Windows(;)
$include_path = get_include_path();                         //原基目錄
$include_path .= PATH_SEPARATOR . BROPHP_PATH . "bases/";       //框架中基類所在的目錄
$include_path .= PATH_SEPARATOR . BROPHP_PATH . "classes/";    //框架中擴展類的目錄
$include_path .= PATH_SEPARATOR . BROPHP_PATH . "libs/";       //模板Smarty所在的目錄
$include_path .= PATH_SEPARATOR . PROJECT_PATH . "classes/";    //項目中用的到的工具類
$controlerpath = PROJECT_PATH . "runtime/controls/" . TMPPATH;  //生成控制器所在的路徑
$include_path .= PATH_SEPARATOR . $controlerpath;             //當前應用的控制類所在的目錄

//設置include包含文件所在的全部目錄
set_include_path($include_path);

複製代碼

__autoload()自動加載類

__autoload()魔術方法是在用戶建立一個沒有包含的類的對象以前會調用,因此重寫這個方法,在這個方法中處理類文件的包含,省去了類文件包含的工做,固然類名須要符合必定的規則才能使用自動包含,框架定義了類名的規則爲「首字母大小的類名.clsss.php」

//自動加載類
function __autoload($className) {
    if ($className == "memcache") {        //若是是系統的Memcache類則不包含
        return;
    } else if ($className == "Smarty") {    //若是類名是Smarty類,則直接包含
        include "Smarty.class.php";
    } else {                             //若是是其餘類,將類名轉爲小寫
        include strtolower($className) . ".class.php";
    }
    Debug::addmsg("<b> $className </b>類", 1);  //在debug中顯示自動包含的類
}

複製代碼

解析處理URL

解析處理URL步驟調用的是Prourl::parseUrl();

/** * URL路由,轉爲PATHINFO的格式 */
static function parseUrl() {
    if (isset($_SERVER['PATH_INFO'])) {
        //獲取 pathinfo
        $pathinfo = explode('/', trim($_SERVER['PATH_INFO'], "/"));

        // 獲取 control
        $_GET['m'] = (!empty($pathinfo[0]) ? $pathinfo[0] : 'index');

        array_shift($pathinfo); //將數組開頭的單元移出數組 

        // 獲取 action
        $_GET['a'] = (!empty($pathinfo[0]) ? $pathinfo[0] : 'index');
        array_shift($pathinfo); //再將將數組開頭的單元移出數組 

        for ($i = 0; $i < count($pathinfo); $i += 2) {
            $_GET[$pathinfo[$i]] = $pathinfo[$i + 1];
        }

    } else {
        $_GET["m"] = (!empty($_GET['m']) ? $_GET['m'] : 'index');    //默認是index模塊
        $_GET["a"] = (!empty($_GET['a']) ? $_GET['a'] : 'index');   //默認是index動做

        if ($_SERVER["QUERY_STRING"]) {
            $m = $_GET["m"];
            unset($_GET["m"]);  //去除數組中的m
            $a = $_GET["a"];
            unset($_GET["a"]);  //去除數組中的a
            $query = http_build_query($_GET);   //造成0=foo&1=bar&2=baz&3=boom&cow=milk格式
            //組成新的URL
            $url = $_SERVER["SCRIPT_NAME"] . "/{$m}/{$a}/" . str_replace(array("&", "="), "/", $query);
            header("Location:" . $url);
        }
    }
}
複製代碼

訪問login/index,解析保存在全局的GET數組中的信息以下:

GET數組
GET數組

 m -> control 表示控制器
a -> action 表示操做

有了這些信息,動態建立控制器,發起對應的流程

$className = ucfirst($_GET["m"]) . "Action";
$controler = new $className();
$controler->run();
複製代碼

控制器(Controller)

控制器的聲明

功能模塊的控制器類保存在controls目錄中,類名和模塊名相同,下面是登陸模塊,定義一個Login類(類的首字母須要大寫)保存的文件名爲login.class.php

class Login extends Action {
    function __construct() {
        parent::__construct();
    }

    function index() {//登錄頁面
        $GLOBALS['debug'] = 0;
        $this->display();
    }

    function islogin() {
        if ($_POST['user_username'] == null && $_POST['user_password'] == null) {//若是用戶名爲空
            $this->error('用戶名和密碼不能爲空', 1, '');
        }
        $_POST['user_password'] = md5($_POST['user_password']);
        $_POST['user_repassword'] = md5($_POST['user_repassword']);
        if ($_POST['user_repassword'] != $_POST['user_password']) {//若是用戶輸入的兩次密碼不一致
            $this->error('兩次密碼不一致', 1, '');
        }
        $user = D('user');
        $date = $user->field('uid,user_password')->where(array('user_username' => $_POST['user_username']))->find();
        $_POST['uid'] = $date['uid'];
        if ($_POST['user_password'] != $date['user_password']) {//若是輸入的密碼與數據庫密碼不匹配
            $this->error('密碼不正確', 1, '');
        }
        if (strtoupper($_POST['code']) != $_SESSION['code']) {//若是輸入的驗證碼不正確
            $this->error('驗證碼輸入不正確', 1, '');
        }
        $_SESSION = $_POST;//把posts全部的數據壓入session
        $date = $user->query('SELECT free_user_group.group_muser,free_user_group.group_mweb,free_user_group.group_marticle,free_user_group.group_sendarticle,free_user_group.group_mimage,free_user_group.group_sendcomment,free_user_group.group_sendmessage,free_user.user_lock FROM free_user,free_user_group WHERE free_user.uid=' . $_SESSION['uid'] . ' AND free_user.gid=free_user_group.gid', 'select');
        if ($date[0]['user_lock']) {
            $this->error('您的賬號已被鎖定,請與管理員聯繫後再登陸', 3, 'index/index');
        } else {
            if ($date[0]['group_muser'] || $date[0]['group_marticle'] || $date[0]['group_mweb'] || $date[0]['group_mimage']) {
                //查詢數據庫中是否開啓自動記錄操做
                $opernote = D('foreground');
                //$_SESSION['oper']=D('OperDate');
                $isOpenNote = $opernote->where(array('fid' => '1'))->field('operateNotes')->find();
                //$_SESSION['operAuthor']=$operAuthior->where(array('id'=>'1'))->find();
                $_SESSION['isOpenNotes'] = $isOpenNote['operateNotes'];
                $_SESSION['islogin'] = true;
                $_SESSION = array_merge($date[0], $_SESSION);
                $user->where($_SESSION['uid'])->update('user_onlinestatus=user_onlinestatus+1');
                $this->success('登錄成功', 1, 'index/index');
            } else {
                $this->error('您的權限不夠沒法進入後臺', 1, '');
            }
        }
    }

    function logout() {//退出時銷燬session
        $user = D('user');
        $_SESSION['islogin'] = false;
        $_SESSION = array();
        if (isset($_COOKIE[session_name()])) {
            setCookie(session_name(), '', time() - 3600, '/');
        }
        session_destroy();
        $this->redirect('index');
    }

    function code() {//顯示驗證碼
        echo new Vcode();
    }

}
複製代碼

common.class.php 類

class Common extends Action {
    function init() {
        if (!(isset($_SESSION['islogin']) && $_SESSION['islogin'] == true)) {
            $this->redirect("login/index");
        }
        $this->assign('session', $_SESSION);
    }
}
複製代碼

操做的聲明

每一個操做對應的是控制器中的一個方法,好比在上面的Login控制器中

  • code()是一個獲取驗證碼的操做,能夠經過 yourhost:port/.../Login/code 這種方式訪問該操做
  • logout()是一個退出登陸的操做,能夠經過yourhost:port/.../Login/logout這種方式訪問該操做

視圖(View)

視圖的顯示是基於Smarty模板引擎的,繼承了Smarty類,而且重寫了__constructdisplayis_cachedclear_cache 方法。

<?php
class Mytpl extends Smarty {
    /** * 構造方法,用於初使化Smarty對象中的成員屬性 * */
    function __construct() {
        $this->template_dir = APP_PATH . "views/" . TPLSTYLE;  //模板目錄
        $this->compile_dir = PROJECT_PATH . "runtime/comps/" . TPLSTYLE . "/" . TMPPATH;    //裏的文件是自動生成的,合成的文件
        $this->caching = CSTART;     //設置緩存開啓
        $this->cache_dir = PROJECT_PATH . "runtime/cache/" . TPLSTYLE;  //設置緩存的目錄
        $this->cache_lifetime = CTIME;  //設置緩存的時間
        $this->left_delimiter = "<{";   //模板文件中使用的「左」分隔符號
        $this->right_delimiter = "}>";   //模板文件中使用的「右」分隔符號
        parent::__construct(); //調用父類被覆蓋的構造方法
    }

    /* * 重載父類Smarty類中的方法 * @param string $resource_name 模板的位置 * @param mixed $cache_id 緩存的ID */
    function display($resource_name = null, $cache_id = null, $compile_id = null) {

        //將部分全局變量直接分配到模板中使用
        $this->assign("root", rtrim($GLOBALS["root"], "/"));
        $this->assign("app", rtrim($GLOBALS["app"], "/"));
        $this->assign("url", rtrim($GLOBALS["url"], "/"));
        $this->assign("public", rtrim($GLOBALS["public"], "/"));
        $this->assign("res", rtrim($GLOBALS["res"], "/"));

        if (is_null($resource_name)) {
            $resource_name = "{$_GET["m"]}/{$_GET["a"]}." . TPLPREFIX;
        } else if (strstr($resource_name, "/")) {
            $resource_name = $resource_name . "." . TPLPREFIX;
        } else {
            $resource_name = $_GET["m"] . "/" . $resource_name . "." . TPLPREFIX;
        }
        Debug::addmsg("使用模板 <b> $resource_name </b>");
        parent::display($resource_name, $cache_id, $compile_id);
    }

    /* * 重載父類的Smarty類中的方法 * @param string $tpl_file 模板文件 * @param mixed $cache_id 緩存的ID */
    function is_cached($tpl_file = null, $cache_id = null, $compile_id = null) {
        if (is_null($tpl_file)) {
            $tpl_file = "{$_GET["m"]}/{$_GET["a"]}." . TPLPREFIX;
        } else if (strstr($tpl_file, "/")) {
            $tpl_file = $tpl_file . "." . TPLPREFIX;
        } else {
            $tpl_file = $_GET["m"] . "/" . $tpl_file . "." . TPLPREFIX;
        }
        return parent::is_cached($tpl_file, $cache_id, $compile_id);
    }

    /* * 重載父類的Smarty類中的方法 * @param string $tpl_file 模板文件 * @param mixed $cache_id 緩存的ID */

    function clear_cache($tpl_file = null, $cache_id = null, $compile_id = null, $exp_time = null) {
        if (is_null($tpl_file)) {
            $tpl_file = "{$_GET["m"]}/{$_GET["a"]}." . TPLPREFIX;
        } else if (strstr($tpl_file, "/")) {
            $tpl_file = $tpl_file . "." . TPLPREFIX;
        } else {
            $tpl_file = $_GET["m"] . "/" . $tpl_file . "." . TPLPREFIX;
        }
        return parent::clear_cache($tpl_file = null, $cache_id = null, $compile_id = null, $exp_time = null);
    }
}
複製代碼

好比訪問登陸頁面

function index() {//登錄頁面
    $GLOBALS['debug'] = 0;
    $this->display();
}
複製代碼

Mytpl的構造方法會自動初始化Smarty的模板目錄、編譯目錄、緩存目錄等Smarty模板引擎須要的內容

$this->template_dir = APP_PATH . "views/" . TPLSTYLE;  //模板目錄
$this->compile_dir = PROJECT_PATH . "runtime/comps/" . TPLSTYLE . "/" . TMPPATH;    //裏的文件是自動生成的,合成的文件
$this->caching = CSTART;     //設置緩存開啓
$this->cache_dir = PROJECT_PATH . "runtime/cache/" . TPLSTYLE;  //設置緩存的目錄
複製代碼

主要內容以下:

template_dir = "./admin/views/default"
compile_dir = "./runtime/comps/default/admin_php/"
cache_dir = "./runtime/cache/default"
cache_lifetime = 604800
複製代碼

Login控制器中調用無參的$this->display();方法,會自動從$this->template_dir文件夾下面查找模板文件,模板文件的是保存在_GET["m"]子文件夾下的名稱爲_GET["a"]的文件,好比,Login控制器對應的index模板位於以下位置:

模板位置
模板位置

最後使用Smarty模板引擎完成頁面內容的渲染工做,最終把編譯後的模板文件保存在$this->compile_dir目錄下面,以下所示:

模板編譯位置
模板編譯位置

模型(Model)

模型層分爲業務模型和數據模型,業務模型用於處理業務流程中的數據驗證、數據處理、結果輸出等等步驟;數據模型處理數據的持久化(增刪改查等操做),數據模型承擔了重要的責任,因此會圍繞數據模型的底層處理展開來講。

  • insert
  • delete
  • update

模型層的基類是抽象的DB類,有如下幾個重要的公有屬性

protected $tabName = "";  //表名,自動獲取
protected $fieldList = array();  //表字段結構,自動獲取
protected $auto;
//SQL的初使化
protected $sql = array("field" => "", "where" => "", "order" => "", "limit" => "", "group" => "", "having" => "");
複製代碼

$sql變量保存瞭如下信息

  • field 表字段
  • where where字句
  • order order by 字句
  • limit limit 字句
  • group group 字句
  • having having 字句

調用field() where() order() limit() group() having()方法,會把對應的參數值保存在$sql關聯數key對應的value中,這些方法在實際中不存在,而是經過重寫__call方法實現了

/** *連貫操做調用field() where() order() limit() group() having()方法,組合SQL語句 */
function __call($methodName, $args) {
    $methodName = strtolower($methodName);
    if (array_key_exists($methodName, $this->sql)) {
        if (empty($args[0]) || (is_string($args[0]) && trim($args[0]) === '')) {
            $this->sql[$methodName] = "";
        } else {
            $this->sql[$methodName] = $args;
        }

        if ($methodName == "limit") {
            if ($args[0] == "0")
                $this->sql[$methodName] = $args;
        }
    } else {
        Debug::addmsg("<font color='red'>調用類" . get_class($this) . "中的方法{$methodName}()不存在!</font>");
    }
    return $this;
}
複製代碼

好比執行下面的代碼:

$date=$user->field('uid,user_password')->where(array('user_username'=>$_POST['user_username']))->find();
複製代碼

最終$sql中保存的數據以下:

arra (
"field" => 'uid,user_password',
"where" => array('user_username'=>"xxxxx")
)
複製代碼

表名稱和表字段結構 protected $fieldList = array();$tabName,在dpdp類setTable方法中自動獲取

/** * 自動獲取表結構 */
function setTable($tabName) {
    $cachefile = PROJECT_PATH . "runtime/data/" . $tabName . ".php";
    $this->tabName = TABPREFIX . $tabName; //加前綴的表名

    if (!file_exists($cachefile)) {
        try {
            $pdo = self::connect();
            $stmt = $pdo->prepare("desc {$this->tabName}");
            $stmt->execute();
            $auto = "yno";
            $fields = array();
            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                if ($row["Key"] == "PRI") {
                    $fields["pri"] = strtolower($row["Field"]);
                } else {
                    $fields[] = strtolower($row["Field"]);
                }
                if ($row["Extra"] == "auto_increment")
                    $auto = "yes";
            }
            //若是表中沒有主鍵,則將第一列看成主鍵
            if (!array_key_exists("pri", $fields)) {
                $fields["pri"] = array_shift($fields);
            }
            if (!DEBUG)
                file_put_contents($cachefile, "<?php " . json_encode($fields) . $auto);
            $this->fieldList = $fields;
            $this->auto = $auto;
        } catch (PDOException $e) {
            Debug::addmsg("<font color='red'>異常:" . $e->getMessage() . '</font>');
        }
    } else {
        $json = ltrim(file_get_contents($cachefile), "<?ph ");
        $this->auto = substr($json, -3);
        $json = substr($json, 0, -3);
        $this->fieldList = (array)json_decode($json, true);
    }
    Debug::addmsg("表<b>{$this->tabName}</b>結構:" . implode(",", $this->fieldList), 2); //debug
}
複製代碼

運行時(Runtime)

  • 處理模塊類的隱式集成common類,處理統一的業務,好比用戶驗證
  • 處理運行時生成對應數據庫驅動的數據模型

運行時數據模型

以一個簡單的數據庫表查詢做爲例子,

function test_query() {
    $article = D("article");
    $data = $article->query("SELECT * FROM article", "select");
    print_r($data);
}
複製代碼

總體步驟流程:

總體步驟流程:
D("article") 	-> Structure::model($className, $app);
			-> $model->setTable($className);
$article->query("SELECT * FROM article", "select");
複製代碼

一、 D("article")中D方法的職責以下:

  • 運行時生成數據模型
  • 獲取數據模型對應的數據庫表的字段以及其餘表信息
/**
 * 建立Models中的數據庫操做對象
 * @param    string $className 類名或表名
 * @param    string $app 應用名,訪問其餘應用的Model
 * @return    object    數據庫鏈接對象
 */
function D($className = null, $app = "")
{
    $db = null;
    //若是沒有傳表名或類名,則直接建立DB對象,但不能對錶進行操做
    if (is_null($className)) {
        $class = "D" . DRIVER;

        $db = new $class;
    } else {
        $className = strtolower($className);
        $model = Structure::model($className, $app);
        $model = new $model();

        //若是表結構不存在,則獲取表結構
        $model->setTable($className);


        $db = $model;
    }
    if ($app == "")
        $db->path = APP_PATH;
    else
        $db->path = PROJECT_PATH . strtolower($app) . '/';
    return $db;
}

複製代碼

1.一、 運行時數據模型
運行時生成數據模型由Structure::model這個方法處理

static function model($className, $app) {
    //父類名,使用PDO連接對應的父類名爲Dpdo
    $driver = "D" . DRIVER;
    $rumtimeModelPath = PROJECT_PATH . "runtime/models/" . TMPPATH;
    if ($app == "") {
        // 數據模型類源碼的位置:eg ./test/models/article.class.php,用戶能夠自定義數據模型保存在該位置
        $src = APP_PATH . "models/" . strtolower($className) . ".class.php";
        // 數據模型父類源碼的位置(___表示佔位符,後面會有替換步驟) eg ./test/models/___.class.php
        $psrc = APP_PATH . "models/___.class.php";
        // 運行時數據模型類名稱,規則爲原始類名添加"Model"後綴
        $className = ucfirst($className) . 'Model';
        // 運行時數據模型父類名稱(___表示佔位符,後面會有替換步驟)
        $parentClass = '___model';
        // 運行時保存的數據模型類位置 /Users/aron/git-repo/PhpLearning/Foundation/26-brophp/runtime/models/Foundation_26-brophp_test_php/articlemodel.class.php
        $to = $rumtimeModelPath . strtolower($className) . ".class.php";
        // 運行時保存的數據模型父類位置 eg /Users/aron/git-repo/PhpLearning/Foundation/26-brophp/runtime/models/Foundation_26-brophp_test_php/___model.class.php
        $pto = $rumtimeModelPath . $parentClass . ".class.php";

    } else {
        $src = PROJECT_PATH . $app . "/models/" . strtolower($className) . ".class.php";
        $psrc = PROJECT_PATH . $app . "/models/___.class.php";
        $className = ucfirst($app) . ucfirst($className) . 'Model';
        $parentClass = ucfirst($app) . '___model';
        $to = $rumtimeModelPath . strtolower($className) . ".class.php";
        $pto = $rumtimeModelPath . $parentClass . ".class.php";
    }


    // 若是有原model存在,用戶自定義了數據模型類
    if (file_exists($src)) {
        $classContent = file_get_contents($src);
        $super = '/extends\s+(.+?)\s*{/i';
        // 若是已經有父類
        if (preg_match($super, $classContent, $arr)) {
            $psrc = str_replace("___", strtolower($arr[1]), $psrc);
            $pto = str_replace("___", strtolower($arr[1]), $pto);

            if (file_exists($psrc)) {
                if (!file_exists($pto) || filemtime($psrc) > filemtime($pto)) {
                    $pclassContent = file_get_contents($psrc);
                    $pclassContent = preg_replace('/class\s+(.+?)\s*{/i', 'class ' . $arr[1] . 'Model extends ' . $driver . ' {', $pclassContent, 1);

                    file_put_contents($pto, $pclassContent);
                }
            } else {
                Debug::addmsg("<font color='red'>文件{$psrc}不存在!</font>");
            }
            $driver = $arr[1] . "Model";
            include_once $pto;
        }
        if (!file_exists($to) || filemtime($src) > filemtime($to)) {
            $classContent = preg_replace('/class\s+(.+?)\s*{/i', 'class ' . $className . ' extends ' . $driver . ' {', $classContent, 1);
            // 生成model
            file_put_contents($to, $classContent);
        }
    } else {
        // 數據模型不存在,用戶沒有定義對應的數據模型,若是沒有生成,則生成該數據模型
        if (!file_exists($to)) {
            // 繼承Driver對應的父類,PDO的父類爲Dpdo,mysqli的父類爲Dmysqli
            $classContent = "<?php\n\tclass {$className} extends {$driver}{\n\t}";
            // 運行時生成model
            file_put_contents($to, $classContent);
        }
    }

    // 包含數據模型類,返回數據模型的類名
    include_once $to;
    return $className;
}
複製代碼

1.二、 獲取數據庫結構信息

獲取數據模型對應的數據庫表的字段以及其餘表信息由setTable該方法處理,不一樣的數據驅動程序處理數據庫操做的方法是不一樣的,因此對應的數據庫驅動類須要重寫該方法,下面是PDO驅動對應的setTable方法

/** * 自動獲取表結構 */
function setTable($tabName) {
    $cachefile = PROJECT_PATH . "runtime/data/" . $tabName . ".php";
    $this->tabName = TABPREFIX . $tabName; //加前綴的表名

    if (!file_exists($cachefile)) {
        try {
            $pdo = self::connect();
            $stmt = $pdo->prepare("desc {$this->tabName}");
            $stmt->execute();
            $auto = "yno";
            $fields = array();
            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                if ($row["Key"] == "PRI") {
                    $fields["pri"] = strtolower($row["Field"]);
                } else {
                    $fields[] = strtolower($row["Field"]);
                }
                if ($row["Extra"] == "auto_increment")
                    $auto = "yes";
            }
            //若是表中沒有主鍵,則將第一列看成主鍵
            if (!array_key_exists("pri", $fields)) {
                $fields["pri"] = array_shift($fields);
            }
            if (!DEBUG)
                file_put_contents($cachefile, "<?php " . json_encode($fields) . $auto);
            $this->fieldList = $fields;
            $this->auto = $auto;
        } catch (PDOException $e) {
            Debug::addmsg("<font color='red'>異常:" . $e->getMessage() . '</font>');
        }
    } else {
        $json = ltrim(file_get_contents($cachefile), "<?ph ");
        $this->auto = substr($json, -3);
        $json = substr($json, 0, -3);
        $this->fieldList = (array)json_decode($json, true);
    }
    Debug::addmsg("表<b>{$this->tabName}</b>結構:" . implode(",", $this->fieldList), 2); //debug
}
複製代碼

二、 查詢

$article->query("SELECT * FROM article", "select");代碼執行的是查詢的功能,查詢方法是最基礎的方法,上層的total()、select()、find()、insert()、update()、delete() 等數據庫操做的方法都依賴於該方法的處理,不一樣的數據驅動程序處理數據庫操做的方法是不一樣的,因此對應的數據庫驅動類須要重寫該方法,下面是PDO驅動對應的query方法

/** * 執行SQL語句的方法 * @param string $sql 用戶查詢的SQL語句 * @param string $method SQL語句的類型(select,find,total,insert,update,other) * @param array $data 爲prepare方法中的?參數綁定值 * @return mixed 根據不一樣的SQL語句返回值 */
function query($sql, $method, $data = array()) {
    $startTime = microtime(true);
    $this->setNull(); //初使化sql

    $value = $this->escape_string_array($data);
    $marr = explode("::", $method);
    $method = strtolower(array_pop($marr));
    if (strtolower($method) == trim("total")) {
        $sql = preg_replace('/select.*?from/i', 'SELECT count(*) as count FROM', $sql);
    }
    $addcache = false;
    $memkey = $this->sql($sql, $value);
    if (defined("USEMEM")) {
        global $mem;
        if ($method == "select" || $method == "find" || $method == "total") {
            $data = $mem->getCache($memkey);
            if ($data) {
                return $data; //直接從memserver中取,再也不向下執行
            } else {
                $addcache = true;
            }
        }
    }

    try {
        $return = null;
        $pdo = self::connect();
        $stmt = $pdo->prepare($sql);  //準備好一個語句
        $result = $stmt->execute($value);   //執行一個準備好的語句

        //若是使用mem,而且不是查找語句
        if (isset($mem) && !$addcache) {
            if ($stmt->rowCount() > 0) {
                $mem->delCache($this->tabName);     //清除緩存
                Debug::addmsg("清除表<b>{$this->tabName}</b>在Memcache中全部緩存!"); //debug
            }
        }

        switch ($method) {
            case "select":  //查全部知足條件的
                $data = $stmt->fetchAll(PDO::FETCH_ASSOC);

                if ($addcache) {
                    $mem->addCache($this->tabName, $memkey, $data);
                }
                $return = $data;
                break;
            case "find":    //只要一條記錄的
                $data = $stmt->fetch(PDO::FETCH_ASSOC);

                if ($addcache) {
                    $mem->addCache($this->tabName, $memkey, $data);
                }
                $return = $data;
                break;

            case "total":  //返回總記錄數
                $row = $stmt->fetch(PDO::FETCH_NUM);

                if ($addcache) {
                    $mem->addCache($this->tabName, $memkey, $row[0]);
                }

                $return = $row[0];
                break;
            case "insert":  //插入數據 返回最後插入的ID
                if ($this->auto == "yes")
                    $return = $pdo->lastInsertId();
                else
                    $return = $result;
                break;
            case "delete":
            case "update":        //update
                $return = $stmt->rowCount();
                break;
            default:
                $return = $result;
        }
        $stopTime = microtime(true);
        $ys = round(($stopTime - $startTime), 4);
        Debug::addmsg('[用時<font color="red">' . $ys . '</font>秒] - ' . $memkey, 2); //debug
        return $return;
    } catch (PDOException $e) {
        Debug::addmsg("<font color='red'>SQL error: " . $e->getMessage() . '</font>');
        Debug::addmsg("請查看:<font color='#005500'>" . $memkey . '</font>'); //debug
    }
}
複製代碼
相關文章
相關標籤/搜索