CodeIgniter 框架分析

入口文件 入口文件主要完成下列工做: 1)   指定 CodeIgniter 框架所在目錄; 2)   定義 APPPATH 常量,指示應用程序文件根目錄; 3)   載入 codeigniter/CodeIgniter.php 文件,啓動框架。 codeigniter/CodeIgniter.php 文件 這個文件是 CodeIgniter 的基本文件,主要完成初始化 CodeIgniter 框架和啓動應用程序兩項工做。 1)   實例化 CI_Benchmark,這個類用於標記應用程序執行消耗的時間; 2)   載入應用程序的配置文件 require(APPPATH.'config/config'.EXT); 3)   實例化 CI_Config,這個類用於將數組封裝爲能夠操做的配置服務; 4)   實例化 CI_Router,這個類用於分析 URL 請求,肯定要執行的控制器和動做; 5)   實例化 CI_Output,這個類提供輸出內容的緩存和檢查服務; 6)   經過 $OUT->_display_cache($CFG, $RTR) 嘗試輸出緩存內容,若是成功,則結束程序運行; 7)   判斷控制器類定義文件是否存在。若是不存在則經過 show_404() 顯示錯誤信息; 8)   實例化 CI_Input,這個類提供對 $_GET、$_POST 的訪問手段,並封裝了一些過濾方法; 9)   實例化 CI_URI,這個類提供對 URL 的分析、構造服務; 10)   實例化 CI_Language,這個類提供多語言字符串映射服務; 11)   載入 codeigniter/Base4.php 或者 codeigniter/Base5.php; 12)   載入 libraries/Controller.php; 13)   載入控制器類定義文件; 14)   實例化控制器類; 15)   若是控制器使用了 scaffolding 功能,則調用控制器的 _ci_scaffolding() 方法,不然調用控制器動做方法; 16)   經過 $OUT->_display(); 輸出內容($OUT 是 CI_Output 的實例)。 CI_Benchmark 這個類很簡單,就是用 microtime() 函數記錄時間點,並提供 elapsed_time() 方法來計算兩個時間點之間消耗的時間。這個類功能很少,可是很實用。CodeIgniter 中大部分類都是這種設計思想,值得稱讚! CI_Config 這個類實際上是在內部維護了一個數組,用來記錄應用程序的設置(相似 Windows 註冊表)。這種簡單的封裝能夠強制應用程序按照固定的規範訪問設置,同時又不將設置保存爲全局變量,避免無心中遭到破壞或篡改。 CI_Router CI_Router 功能很單一。CI_Router 首先分析出應用程序當前使用的 URL 模式:PATHINFO 或普通模式。接下來從 URL 地址中分析出控制器名字、動做名以及參數名和參數值。分析的結果保存爲 CI_Router 對象實例的成員變量。 這裏比較有特色的是,CI_Router 能夠根據開發者在應用程序設置裏面定義的模式來分析 URL,而不是使用某種固定的模式。 CI_Output CI_Output 有兩個主要功能:得到應用程序執行的全部輸出內容和輸出緩存服務。 應用程序執行的輸出結果都會保存爲 CI_Output 的成員變量。而後根據應用程序設置,CI_Output::_display() 方法會調用 CI_Output::_write_cache() 方法將輸出內容緩存起來。下一次當使用 CI_Output::_display_cache() 時若是緩存已經創建了,而且沒有過時,則會直接輸出緩存內容。 在 CI_Output::_write_cache() 中,是根據 URL 地址和 URL 參數來肯定緩存 ID 的。所以即使是同一個控制器和動做,只要使用不一樣的 URL 參數,也會緩存不一樣的內容。 這個類的功能很簡單,所以在許多動態頁面是沒法使用的。例如用戶登陸前和登陸後,訪問同一個控制器和動做並使用相同的 URL 參數,頁面內容也有多是不一樣的。這時,CI_Output 的緩存就不能使用。 由於從本質上來講,CI_Output 提供的緩存是在應用程序以外的,因此應用程序沒法根據當前狀態來決定是否緩存頁面。當一個頁面被緩存後,對該頁面的訪問實際上根本就不會執行應用程序代碼,而是由 CI_Output 取出緩存內容直接就輸出了。 CI_Input CI_Input 是相似過濾器,而且提供了對 $_GET、$_POST 的封裝服務。例如用 CI_Input::post() 方法來訪問 $_POST。因爲多了這層封裝,CI_Input 能夠在 post() 方法中對數據進行更多的過濾。 這種封裝從出發點上看,是很不錯的。可是這也會形成一些問題。例如 CI_Input 只有在調用 post() 方法時才能進行過濾。若是應用程序使用 $_POST 直接獲取數據,那麼實際上就繞過了安全屏障。若是應用程序使用了第三方庫,那麼這種風險更大,由於第三方庫極可能會直接使用 $_POST 等全局變量。 所以有些開發者認爲過濾應該是全局的,即在框架初始化時,就對全部輸入數據進行過濾。但初始化時的全局過濾靈活性不好,要麼全過濾,要麼都不過濾,無法作到對個別數據的單獨過濾。 CI_Input 的另外一個問題,就是沒有處理 magic_quotes。無論 magic_quotes 設置爲何,CI_Input 都沒有對數據進行相關的處理。這樣一來,若是服務器的 magic_quotes 設置不一樣,那麼應用程序獲得的數據也是不一致的。後來查看數據庫驅動的代碼,發現 CI_Input 將對 magic_quotes 的處理放到了數據庫驅動中。 這種設計是有很大缺陷的!若是應用程序取得數據後,並非存入數據庫(例如直接顯示或存入文件),那麼就必須自行判斷 magic_quotes 的狀態。這種判斷不但煩瑣,並且容易遺忘。因此框架有責任將全部數據庫整理爲一致的格式,要麼是應用 addslashes() 轉義事後的數據,要麼是沒有轉義的數據。 奇怪的是 CI_Input 卻對輸入數據的字段名進行了 magic_quotes 檢查,並應用了 addslashes()。這是爲了讓數據庫字段名不會成爲 SQL 注入攻擊的根源。甚至,CI_Input 還會將 \n\r\n\r 替換爲 \n。這種隨意篡改原始數據的作法,很是不可取。 總之,我我的認爲 CodeIgniter 在這部分的設計是很糟糕的。不過要改善也很簡單,幾行代碼就能夠了。而後修改一下數據庫驅動。可是因爲已經有許多采用 CodeIgniter 開發的應用程序,因此這樣的升級改動,影響是很是大的。 CI_URI 因爲 CodeIgniter 容許應用程序定義 URL 映射模式,因此須要專門的工具來生成 URL 地址。CI_URI 就是完成這些工做的。 CI_Language 這個類能夠載入不一樣的語言文件。而後應用程序就能夠用 CI_Language::line() 方法取出某個項目的對應翻譯。每一個語言文件就是一個名值對數組。因此 CI_Language::line() 以項目名作爲鍵名,就能夠查詢到對應的翻譯。 codeigniter/Base codeigniter/Base4.php 和 codeigniter/Base5.php 功能同樣,只不過度別適用於 PHP4 和 PHP5 而已。其中定義了 CI_Base 類和一個很是重要的 get_instance() 函數。 get_instance() 函數返回一個 CI_Base 類在整個應用程序中的惟一實例。 這裏有一個有趣的發現。Base4.php 和 Base5.php 中的 CI_Base 和 get_instance() 有這徹底不一樣的實現。 在 Base4.php(對應 PHP4)中,CI_Base 直接繼承自 CI_Loader。CI_Base 實例化時,將 自身的引用保存到了 CI_Base::$load 中。也就是說 CI_Base 實例的 $load 實際上指向本身。而後 $load 被複制到一個名爲 $OBJ 的全局變量。 在 PHP4 版的 get_instance() 函數中,若是檢查到 $CI(這是 CI_Base 的實例,也就是控制器的實例)存在,就返回 $CI,不然返回全局變量 $OBJ->load。但因爲在 PHP4 中,$OBJ->load 實際上就是一個 CI_Base 的實例。因此。。。。因此。。。。。。仍是返回了一個 CI_Base 的實例。真搞不懂做者爲何這樣寫,簡直要讓人發瘋。 無論怎麼樣,應用程序其餘地方調用 get_instance() 都會得到一個 CI_Base 的實例。 在 Base5.php(對應 PHP5)中,用一個 singleton 模式來解決了這個問題。所以 CI_Base 也再也不須要從 CI_Loader 繼承了。不過這也留下了隱患(CI_Loader 實例要何時獲取呢?),因此在 CI_Base 的繼承類 Controller 中,只好經過判斷是不是運行 PHP5 來決定是否是要實例化一個 CI_Loader。 真的很無語啊,這種設計雖然能夠用,可是很糟糕。在 PHP4 種,CI_Loader 的方法和成員變量暴露在了 CI_Base 中。若是應用程序不當心調用了這些方法或使用了這些成員變量。那麼應用程序在 PHP5 中運行就會出錯。 Controller Controller 類是全部控制器的基礎類。Controller 實例化時會將 CI_Input、CI_Benchmark、CI_Config、CI_URI、CI_Output、CI_Language 的實例複製到 Controller 實例的成員變量中。而後根據應用程序設置,自動載入文件。 可是這裏做者顯然沒有處理好,因此不得不用 `global $IN, $BM, $CFG, $URI, $LANG, $OUT;` 這樣的全局變量來傳遞幾個重要的對象實例。 Controller 自己並沒提供 model、helper 的載入服務。這些都由 CI_Loader 來提供。可是,CI_Loader 的各類載入服務,卻又用 get_instance() 獲取控制器的實例,而後調用 Controller(控制器都是 Controller 的繼承類哦)的 _ci_initialize()、_ci_init_database() 等方法來作初始化。 神啊!救救我吧!這種錯綜複雜的關係,真的要人命啊! Controller 的 $ci_is_loaded 成員變量用於保存已經載入的對象實例。因此每次用 Controller::_ci_load_model() 載入模塊後,都要將該模塊登記到 $ci_is_loaded,以免重複載入。 Controller 裏面大部分是一些初始化各類服務的方法,例如初始化數據庫、Model 的方法。還有就是用 _ci_scaffolding() 調用 CodeIgniter 的「腳手架」功能。 對 Controller 的設計,沒什麼好說的,一個字:爛! CI_Loader CI_Loader 提供各類載入服務,例如載入 Model、Helper、View 等。可是(我真的很痛恨「可是」這個詞),CI_Loader 卻須要 Controller 來完成初始化。那麼又是誰來調用 CI_Loader 呢?答案是 Controller。 這種緊密的耦合,徹底是沒有必要的! 控制器開始執行 分析到這裏,終於進入應用程序的代碼了。應用程序控制器中,能夠用 $this->load 來載入各類服務,而後就能夠調用這些載入的服務了。 雖然 CodeIgniter 在 CI_Base、Controller 和 CI_Loader 上設計很糟糕,但開發者若是不在意這些,那麼開發過程仍是很愉快的。 下面咱們再來看看 CodeIgniter 主要服務的特色。 數據庫訪問 與大部分框架不一樣,CodeIgniter 的 Model 類沒有提供數據庫訪問功能。全部數據庫操做都是經過數據庫驅動程序來進行的。 全部數據庫驅動均繼承自 CI_DB 類。等等,我怎麼找不到 CI_DB 類的定義呢?由於 CI_DB 類是在 Controller 中用 `eval('class CI_DB extends CI_DB_driver { }');` 這行代碼來定義的。定義這樣一個空殼,估計是做者爲之後擴充數據庫驅動留下的伏筆。 CodeIgniter 的數據庫驅動,功能都很簡單,和 AdoDB Lite 相似,可是缺少 AdoDB Lite 那麼多的擴展庫。我我的認爲反倒不如用 AdoDB Lite 來替換這部分。固然了,CodeIgniter 目前已經有很多數據庫驅動了,因此替換成 AdoDB Lite 好處很少。 CodeIgniter 也提供了一個 ActiveRecord 實現,不過這個 ActiveRecord 可沒有一點半點的「ORM」能力。可是 CodeIgniter 的 ActiveRecord 不須要爲每個數據表都構造一個實例。一般一個實例就能夠處理多個數據表的操做。例如 `$query = $this->db->get('mytable');` 和 `$query = $this->db->get('mytable2');` 就能夠分別取得 mytable 和 mytable2 的數據。 說實話,做者可能用錯了名字。CodeIgniter 中的「ActiveRecord」其實是表數據入口模式——TableDataGateway。 CodeIgniter 中的 ActiveRecord 基本上只是一個對數據表進行 CRUD 操做的公共接口。沒有提供 RoR、CakePHP、FleaPHP 等框架具備的數據表關聯自動處理能力。和本身寫 SQL 相比,沒什麼優點。惟一的好處就是做者所說的可讓 ActiveRecord 來生成這些簡單的 SQL 語句,而不用本身寫,提升應用程序在不一樣數據庫之間移植的能力。 「腳手架」功能 CodeIgniter 中提供了基本的「腳手架」功能,能夠用幾行代碼即實現一個對某個數據表進行 CRUD 的界面。這和 phpMyAdmin 中的數據瀏覽、編輯頁面相似,固然功能要簡單得多。 「腳手架」有什麼實用價值,衆說紛紜。但廣泛認同的一點就是「腳手架」功能爲處於開發初期的應用程序提供了管理數據的界面。開發者能夠在後期替換掉「腳手架」的界面。 可是,CodeIgniter 也太簡單了,就只有 CRUD 操做,還不如 phpMyAdmin 好用。 其餘 CodeIgniter 還有許多其餘的類和助手。這些類基本上都屬於提供各類輔助服務的範疇。有些類很不錯,像圖片操做。但大部分類和助手實在太簡單,缺少實用價值。像數據驗證助手,只能作很基本的驗證,在絕大多數應用程序裏面都不能知足要求。 總結 咳——咳——,總結時間到了。 再次鄭重申明:本文全部文字均爲做者我的理解和感想。做者儘可能作到客觀,但人非聖賢,不免參雜我的好惡在其中。因此若是你看到不爽的文字,請自動無視,謝謝合做!  CodeIgniter 是一個:簡單不簡潔、好用但可能不夠用的工具。 幾個步驟就可讓你的應用程序跑起來,因此簡單。由於簡單,因此好用。但糟糕的設計增長了複雜度,簡單的表面下是錯綜複雜的對象關係。由於過於簡單,因此可能不夠用。 若是你只是開發很簡單的應用程序,那麼 CodeIgniter 徹底能夠知足你的需求。並且你也會得到愉快的體驗。 但若是應用程序具備必定的複雜度,CodeIgniter 就可能起到副作用。由於 CodeIgniter 在幾個主要類上的糟糕設計,你的應用程序最終也會受到牽連。並且 CodeIgniter 缺少許多必須的服務,例如訪問控制、用戶管理、自動化的數據表關聯處理、複雜緩存等。 這些服務對於一個較爲複雜的應用程序來講都是必須的。若是用 CodeIgniter 做爲應用程序框架,那麼這些服務都須要本身實現。這時 CodeIgniter 帶來的好處就不多了。
相關文章
相關標籤/搜索