本文主要來聊聊框架理論,但不針對任何一款框架,不過任何一款框架都離不開這個理論,首先我們了解下框架的來龍去脈,任何技術的出現都是為了解決某個問題,之前的博客有講過smarty,其存在就是為了html和php更好地分離開來,而所謂的“框架”是一種為了統一書寫格式,訪問方式而進行的自我約束行為.
其實按照這個說法我們每個人基本上都或多或少的使用過自己定義的框架,比如說在沒使用框架之前自己開發一個項目,有時候是一天無法完成的,我們為了防止混亂,為了更好地記憶我們會進行目錄規劃和程序的規劃,潛意識的把程序分類,放到不同的文件夾,于是框架應運而生了.
又比如說我們曾經做了一個CMS系統,如果我們又接手一個類似的項目,你會重復書寫代碼嗎,答案肯定不會,但如果是別人的項目你拿來改其實是一件很蛋疼的事情的,因為你根本不知道他的這個CMS的規則是什么,即使是自己的項目時間久了如果自己沒有固定的規范的話也是很容易忘記的,那么怎么樣才能保證自己每次寫的代碼都可以按照一定的規范呢,把每個項目要用的東西挑出來,目錄結構挑出來,以后無論什么項目都在這個基礎上書寫,那么屬于自己的框架就出來了.
但框架單純這樣是不完善的,那么框架應該有哪些能力呢,與其這樣說我們不妨想想我們平時一定會寫的代碼一定會做的事情是什么呢,首先解決的是什么呢,為了減少路徑問題,目錄結構的安排其實很重要,有時候文件包含再進行文件的移動總是一件很蛋疼的事情,最好的辦法是什么呢,絕對路徑,但同時存在了一個類似E:www 這樣的問題,但這個值我們可以通過預定義變量$_SERVER["DOCUMENT_ROOT"]獲得,我們可以將其定義為常量,define("ROOT_PATH", $_SERVER["DOCUMENT_ROOT"]);include ROOT_PATH . "/lib/mysql.php";類似這樣的文件夾怎么移動都不會有事的,那么就出現了一個固定的寫法.
為了解決路徑問題而存在的寫法,幾乎每一頁都會用到,還有模板的輸出,數據庫的連接,那么我們可以把這些代碼封裝起來,或者分離出來,每一頁包含即可,被分離的這些部分其實就是一個小框架,為什么這么說呢,如果我們包含它們進來,例如smarty模板,肯定是實例化好的對象,那個量名已經固定,可能是$smarty 如果我們包含了這么個文件進來,這個量就不能再次進行賦值使用,這樣下面部分的代碼就無法進行使用了,由于包含的類,如數據類 上傳類 圖像類 分頁類 位置已經寫死在這個公共文件里,為了不改代碼 這個目錄就成了必須存在的了,目錄格式固定,編寫代碼方式的約束,這就形成了框架.
多年來的程序員的總結交流及開發經驗,大家總結了一些優秀的編寫方式,最經典的是單點入口,什么是單點入口呢,之前我們總結了一些幾乎每個程序都要用到的功能,這里依然存在一些問題,例如 在公共文件包含之前我們還是不知道根目錄是多少,如果我們在每一個文件夾下面都放一個公共文件存在代碼重復問題,哪天修改的話需要全部修改,需要找到需要多少個這樣的文件,現在是多個程序包含一個程序,然后用戶訪問n個程序來完成各個功能,于是程序員就想可不可以反過來我用一個程序包含這些不同功能的程序,用戶只訪問這個程序就可以了呢.
于是單點入口模式出現了,在網站首頁的 index.php 寫上每個程序都要用的部份 然后跟據某個量,比如一個 get 量來判斷當前實際要執行的程序是哪一個,由 index.php 把它包含進來運行,這種由一個程序,完成所有功能的方式,稱為 單點入口,于是由這個入口程序和它對應的各個目錄結構成為了一個框架.
出于安全在包含文件的時候往往都會固定目錄,不然就容易出漏洞 所以,往往會在路徑頭尾加個限制,例如如下代碼:
include "./app/".$_GET['url'].".php";
那路徑就只能寫成類似這樣index.php?url=news/list,實際包含的是 /app/news/list.php 當然了,實際的情況,還要檢查一下這個程序文件是否存在之類的.
完整一點的話,我們可以這樣寫這個入口文件,代碼如下:
- <?php
- //這里寫絕對路徑
- //這里寫數據庫連接
- //這里寫模板初始化,配置
- //這里判斷連接變量
- //這里包含文件進來運行
- //這里輸出模板
- //這里關閉數據庫
- ?>
一個面向過程的單點入口框架就完成了,是不是有覺得每次都在地址欄帶一個 get 不方便?那我們可以換一個寫法,例如 tp 框架最喜歡用的 http://localhost/index.php/news/list后面的 /news/list 由程序轉成 php 路徑包含進來就可以了,在 Apache 環境中,這個 /news/list 可以由服務器變量的 PATH_INFO 取得,如果沒有的話。也可以用 REQUEST_URI 取得接近的,IIS 下面,有 HTTP_X_REWRITE_URL 可以取得這個值,自從單點入口模式出現之后,而且oop開發模式從php5開始大行其道,各種oop設計的框架讓我們眼花繚亂,但是萬變不離其宗,依然是什么入口方式,路徑結構是什么樣的,文件名的命名規則,用什么樣的訪問方式,可以運行哪個程序,用oop開發的框架,不外乎就是把主程序改寫成為了一個類.
例如:
- //包含共用文件,實例化各個類啥的
- 頁面->初始化();
- //把用戶發來的網址轉成要包含的路徑
- 頁面->處理網扯();
- //在這里包含程序運行
- 頁面->運行()
- //輸出模板
- 頁面->輸出()
各種各樣的框架只是為我們準備了一個規矩罷了,在我們的開發累計的過程中,我們常常會把一些常用的類封裝成類,例如,數據庫類,文件上傳類,圖片處理類,郵件收發類,遠程訪問類,各種接口類……這個時候,我們就會希望框架能給我們提供一個好一點調用類的方法, 也就是所謂的“擴展性”,比如 TP 框架的 Db 類,如果不用其自帶的類庫只用它們的核心框架,其實幾個文件就夠了.
TP 框架支持三種訪問格式:
/news/list
/index.php/news/list
/index.php?m=news&a=list
第一種需要服務器的 urlrewrite 支持,后面兩種可以直接用,事實上,Zend 框架也差不多文件的包含方式是,以類的形式包含,執行的其實是:/文件夾/對象/方法,這種做法有優勢,因為在同一個功能中,相似的代碼很多,封裝到同一個類里面,可以更高效的重復使用代碼,比如這樣,代碼如下:
- class NewsAction {
- public function head() {
- 在這里處理每一頁頭部
- }
- public function index() {
- $this->head();
- 在這里處理這一頁
- }
- public function show() {
- $this->head();
- 在這里處理這一頁
- }
還可以利用構造函數等,使每一個功能,在剛進來的時候就都做了同一件事情,以上就是簡單框架的理論.
制作應用原理
index.php 主入口文件,代碼如下:
- <?php
- define('ISEXIST',true);
- require "init.php";
- $control = new Controller();
- $control -> Run();
- ?>
init.php 文件,代碼如下:
- <?php
- if(!defined('ISEXIST'))
- exit("請從入口文件運行程序");
- header("Content-Type:text/html;charset=utf-8");
- if(!defined('ROOT_PATH'))
- //這里動態的聲明,''是轉義反斜線,默認''為轉義字符
- define('ROOT_PATH',str_replace('','/',dirname(__FILE__)));
- require ROOT_PATH.'/a/config.php';
- require ROOT_PATH.'/a/controller.class.php';
- require ROOT_PATH.'/a/view.class.php';
- require ROOT_PATH.'/a/model.class.php';
- ?>
config.php 文件,代碼如下:
- <?php
- if(!defined('ISEXIST'))
- exit("請從入口文件運行程序");
- $C = array(
- 'URL_MODE'=>1,//url模式,1為普通模式,2為path_info模式
- 'DEFAULT'=>'welcome',//默認的控制器
- 'DEFAULT_ACTION'=>'index'//默認的方法
- );
- ?>
controller.class.php 文件,代碼如下:
- <?php
- class Controller
- {
- public function Run()
- {
- $this->Analysis();
- //開始解析URL獲得請求的控制器和方法
- $control = $_GET['con'];
- $action = $_GET['act'];
- $action = ucfirst($action);
- //這里構造出控制器文件的路徑
- $controlFile = ROOT_PATH . '/Controllers/' . $control . '.class.php';
- if(!file_exists($controlFile)) //如果文件不存在提示錯誤, 否則引入
- {
- exit("{$control}.class.php控制器不存在<br>". "請檢查: ".$controlFile."是否存在<br>");
- }
- include($controlFile);
- $class = ucfirst($control); //將控制器名稱中的每個單詞首字母大寫,來當作控制器的類名
- if(!class_exists($class)) //判斷類是否存在, 如果不存在提示錯誤
- {
- exit("{$control}.class.php中未定義的控制器類" . $class);
- }
- $instance = new $class(); //否則創建實例
- if(!method_exists($instance, $action)) //判斷實例$instance中是否存在$action方法, 不存在則提示錯誤
- {
- exit("$class類中不存在方法:". $action);
- }
- $instance->$action();
- }
- protected function Analysis()
- {
- //$GLOBALS['C']['URL_MODE'];
- global $C; //包含全局配置數組, 這個數組是在Config.ph文件中定義的,global聲明$C是調用外部的
- if($C['URL_MODE'] == 1)
- //如果URL模式為1 那么就在GET中獲取控制器, 也就是說url地址是這種的 [url=http://localhost/index.php?c]http://localhost /index.php?c[/url]=控制器&a=方法
- {
- $control = !emptyempty($_GET['con']) ? trim($_GET['con']) : '';
- $action = !emptyempty($_GET['act']) ? trim($_GET['act']) : '';
- }
- else if($C['URL_MODE'] == 2) //如果為2 那么就是使用PATH_INFO模式, 也就是url地址是這樣的 [url=http://localhost/index.php/]http://localhost/index.php/[/url]控制器/方法 /其他參數
- {
- if(isset($_SERVER['PATH_INFO']))
- {
- //$_SERVER['PATH_INFO']URL地址中文件名后的路徑信息, 不好理解, 來看看例子
- //比如你現在的URL是 [url=http://www.49028c.com/index.php]http://www.49028c.com/index.php[/url] 那么你的$_SERVER['PATH_INFO']就是空的
- //但是如果URL是 [url=http://www.49028c.com/index.php/abc/123]http://www.49028c.com/index.php/abc/123[/url]
- //現在的$_SERVER['PATH_INFO']的值將會是 index.php文件名稱后的內容 /abc/123/
- $path = trim($_SERVER['PATH_INFO'], '/');
- $paths = explode('/', $path);
- $control = array_shift($paths);
- $action = array_shift($paths);
- }
- }
- //這里判斷控制器的值是否為空, 如果是空的使用默認的
- $_GET['con'] = !emptyempty($control) ? $control : $C['DEFAULT'];
- //和上面一樣
- $_GET['act'] = !emptyempty($action) ? $action : $C['DEFAULT_ACTION'];
- }
- }
- ?>
welcome.class.php 文件,代碼如下:
- <?php
- class Welcome
- {
- function Index()
- {
- echo '歡迎使用此CMS系統';
- }
- function Run()
- {
- echo 'Hello';
- }
- function Show()
- {
- echo '方法名show';
- }
- }
- ?>
新聞熱點
疑難解答