使用 CodeIgniter 框架快速開發 PHP 應用(七)
CodeIgniter 和對象
這是玩家章節。它講述的是 CodeIgniter 的工做原理,也就是揭開CI頭上'神祕的面紗'。若是你是 CI 的新手,你可能想要跳過它。不過, 早晚, 你可能想要了解CI的幕後在發生什麼 ,爲何不真正的玩轉它呢?
當我剛開始使用 CodeIgniter 的時候,對象使我迷惑。 我是在使用 PHP 4的時候接觸CI的, PHP4並非真正的面向對象的語言。我在一大堆對象和方法、屬性和繼承,還有封裝等數據結構中轉悠,老是被相似的出錯信息包圍 " 調用非對象的成員函數". 我如此頻繁地看到它們,所以我想到要印一件T恤衫,上寫: 神祕,無規律可循, 而我彷彿正穿着它站在一個現代藝術展會的會場上。
這一章的內容包含CI使用對象的方法, 和你OO編程的方法。 順便說一下,術語 '變量/屬性', '方法/函數'是等義的,當 CI 和 PHP 常常會混着使用它們。好比,你在你的控制器中寫一個 '函數', 純 OO 程序員會稱他們是'方法'。你稱之爲類的變量而純OO程序員會叫它們‘屬性’。
面向對象編程
我正在假定你和我同樣有 OOP 的基本知識, 但若是隻是在PHP4中嘗試過可能還不太夠。 PHP 4 不是一種 OO 語言, 雖然具有了一些 OO 的特徵。 PHP 5 會更好一些, 它的引摯已經完全改寫成面向對象的了。
不過基本的OO特徵PHP4也能實現,並且 CI 設法讓你不管是使用PHP4 仍是PHP5,都有同樣的行爲特徵。
重要的是你要記住,當 OO 程序運行時,總會有一個或數個真實的對象存在。對象可能彼此調用,只有當對象處於運行狀態的那一刻你才能夠讀取變量(屬性) 和方法 (函數)。 所以知道和控制哪一個對象當前在運行很重要。當一個類沒有實例化時,你不能對它內部的屬性和方法操做,靜態方法和屬性除外。
PHP,做爲一個過程式編程和OO編程的混合體, 可讓你混合編寫又是過程式又是OO的程序,你能夠在一過程式代碼中實例化一個類,而後使用它的屬性和方法,用完後把它從內存中釋放掉。這一些工做,CI均可覺得你代勞。
CI '超級-對象'的工做原理
CI 生成一個超級大對象: 它把你的整個項目看成一個大的對象。
當你啓用 CI 的時候,一連串複雜的事件開始發生。 若是你設定你的 CI 產生日誌,你將會見到相似下列的記錄:
1 DEBUG - 2006-10-03 08:56:39 --> Config Class Initialized
2 DEBUG - 2006-10-03 08:56:39 --> No URI present. Default controller
set.
3 DEBUG - 2006-10-03 08:56:39 --> Router Class Initialized
4 DEBUG - 2006-10-03 08:56:39 --> Output Class Initialized
5 DEBUG - 2006-10-03 08:56:39 --> Input Class Initialized
6 DEBUG - 2006-10-03 08:56:39 --> Global POST and COOKIE data
sanitized
7 DEBUG - 2006-10-03 08:56:39 --> URI Class Initialized
8 DEBUG - 2006-10-03 08:56:39 --> Language Class Initialized
9 DEBUG - 2006-10-03 08:56:39 --> Loader Class Initialized
10 DEBUG - 2006-10-03 08:56:39 --> Controller Class Initialized
11 DEBUG - 2006-10-03 08:56:39 --> Helpers loaded: security
12 DEBUG - 2006-10-03 08:56:40 --> Scripts loaded: errors
13 DEBUG - 2006-10-03 08:56:40 --> Scripts loaded: boilerplate
14 DEBUG - 2006-10-03 08:56:40 --> Helpers loaded: url
15 DEBUG - 2006-10-03 08:56:40 --> Database Driver Class Initialized
16 DEBUG - 2006-10-03 08:56:40 --> Model Class Initialized
在啓動時-每次一頁經過英特網發出請求-CI 每次啓動都執行相同的程序。你能經過 CI 文件追蹤記錄:
1. index.php 文件收到一個頁請求。 URL可能指出哪個控制器被調用, 若是不, CI 有一個默認值控制器 (第 2 行).Index.php 開始一些基本檢查而後調用 codeigniter.php 文件(\codeigniter\ codeigniter.php).
2. codeigniter.php 文件實例化 Config 、Router、Input,URL(等等)類.(第 1 行, 和 3-9行) 這些被調用的叫作'基礎'類: 你不多直接與它們交互,可是CI作的每件事都與它們有關。
3. codeigniter.php 測試瞭解它正在使用哪個PHP版本,根據版本決定調用base4 仍是base5(/codeigniter/base4.(或base5)php). 這些建立一個 '單一' 實例: 即一個類只能有一個實例。 無論哪一個都有一個&get_instance() 方法。 注意符號 &:, 這是引用實例的符號。 所以若是你調用 &get_instance() 方法, 它產生類的單一實例。換句話說,整個應用中這個實例是惟一的,其中包含許許多多框架中其它類的實例。
4. 在安全檢查以後,codeigniter.php 實例化被請求的控制器、或一個默認控制器 (第 10 行) 。 新的類叫作 $CI 。在URL中(或默認值)中被指定的函數被調用,類被實例化以後,至關於活了,實實在在存在於內存中。 CI 而後將會實例化你須要的任何其餘的類, 幷包含函數庫腳本文件。 所以在日誌中,model類被實例化。(第16 行)'模板文件' 腳本, 也被裝載(第 13 行), 這是我編寫的包含標準代碼的一個文件。 它是一個.php 文件,保存在scripts目錄中,可是它不是一個類: 僅僅是一組函數。 若是你正在寫 '純粹的' PHP代碼,你可能會使用 'include ' 或者 'require'把這個文件放進命名空間,CI會使用它本身的 '裝載' 函數把它放入「超級對象「中。
'namespace' 的概念或範圍在這裏是決定性的。 當你聲明一個變量、數組、對象等等的時候,PHP把變量名稱保存在內存中併爲它們的內容分配一個內存塊。若是你用相同的名字定義二個變量就會出現問題。 (在一個複雜的網站中,容易犯這樣的錯誤。) 由於這個緣由,PHP 有幾條規則。 舉例來講:
。 每一個函數有它本身的namespace 或者範圍, 並且定義在一個函數中的變量通常是一個局部變量。 在函數外面, 它們是看不到的。
。 你能聲明 '全局' 變量, 放在特別的全局 namespace,在整個程序中均可以調用。
。 對象有他們本身的 namespaces:對象內的變量(屬性)是與對象同時存在的,能夠經過對象來引用。
所以 $variable, global variable, 和 $this->variable是三件不一樣的事情。
特別地,在 OO 以前,這可能致使各類混亂: 你可能有太多的變量在同一namespace中(以至於許多衝突的變量名互相覆蓋),也可能發現有些變量在某個位置沒法存取。CI 爲此提供了一個解決辦法。
假如如今你已經鍵入以下URL:
www.mysite.com/index.php/welcome/index
, 你是但願調用welcome控制器的index函數。
若是你想要了解,哪一個類和方法在當前的namespace 中可用, 試着在welcom控制器中插入下列 '檢測' 代碼:
$fred= get_declared_classes();
foreach ($fred as $value) {
$extensions = get_class_methods($value);
print "class is $value, methods are: ";
print_r($extensions);
}
試着運行它,它列出了270個已明的類。 大部分是PHP的。 最後的 11 個來自 CI: 10個是 CI 基礎類 (config 、router等等。) 並且都是個人控制器調用的類。下面列出這11個類,清單隻保留了最後的兩個方法,其它的被省略了:
258: class is CI_Benchmark
259: class is CI_Hooks,
260: class is CI_Config,
261: class is CI_Router,
262: class is CI_Output,
263: class is CI_Input,
264: class is CI_URI,
265: class is CI_Language,
266: class is CI_Loader,
267: class is CI_Base,
268: class is Instance,
269: class is Controller, methods are: Array ( [0] => Controller [1] => _ci_initialize [2] => _ci_load_model [3] => _ci_assign_to_models [4] => _ci_autoload [5] => _ci_assign_core [6] => _ci_init_scaffolding [7] => _ci_init_database [8] => _ci_is_loaded [9] => _ci_scaffolding [10] => CI_Base )
270: class is Welcome, methods are: Array ( [0] => Welcome [1] =>
index [2] => Controller [3] => _ci_initialize [4] => _ci_load_model [5] => _ci_assign_to_models [6] => _ci_autoload [7] => _ci_assign_core [8] => _ci_init_scaffolding [9] => _ci_init_database [10] => _ci_is_loaded [11] => _ci_scaffolding [12] => CI_Base )
注意-看一下Welcome類括號中包含的內容 (270號: 即我正在使用的控制器) ,它列出了Controller類的全部方法 (269 號). 這就是爲何你老是須要從一個控制器類派生子類的緣由-由於你須要你的新控制器保留這些函數。 (並且一樣地,你的models應該老是從model類繼承.) Welcome類有兩個額外的方法: welcome()和index()。 到如今爲止,在 270個類中,我寫的只有這二個函數!
你可能還注意到類的實例-即object。 有一個指向它的變量,注意到那個引用符號了嗎?代表在整個系統中,CI_Input類只有一個實例,能夠用類變量input調用它:
["input"]=>&object(CI_Input)#6(4){[" use_xss_clean"]=> bool(false)[" ip_address"]=> bool(false)[" user_agent"]=> bool(false)[" allow_get_array"]=> bool}(false)
記得咱們什麼時候裝載了input文件並且建立了最初的輸入類? 它包含的屬性是:
use_xss_clean is bool(false)
ip_address is bool(false)
user_agent is bool(false)
allow_get_array is bool(false)
你能夠看到, 他們如今已經所有被包括在實例中,「設計圖紙」變成了房子,不是嗎?
全部其它的 CI 的基礎類(routers, output等等。) 一樣地被包含了。 你不須要調用這些基礎類,可是 CI 自己須要他們使你的代碼工做。
引用複製
剛纔提到,類變量input引用了CI_Input類:(["input"]=>&object(CI_Input)), 加不加引用符號區別在於:加上引用符號,一變俱變,不加引用符號,原始對象的內容不會改變。你可能會對此感到困惑,用一個簡單的例子來講明:
$one = 1;
$two = $one;
echo $two;
顯示 1, 由於 $two是$one的拷貝。 然而,若是你再從新$one賦值:
$one = 1;
$two = $one;
$one = 5;
echo $two;
仍然顯示 1, 由於在對 $one 從新賦值前 $two已經賦爲1了,而$one和$two是兩個不一樣的變量,各自分配有一小塊內存,分別存放它們的值。
若是在$one改變的時候,$two也要相應地改變,咱們就要使用引用了,這個時候,$one和$two其實是指向了同個內存塊,一變俱變:
代碼:
$one = 1;
$two =& $one;
$one = 5;
echo $two;
如今顯示5: 咱們改變變量$one,實際上也同時改變了$two。
把符號「=」 改爲 「=&」 意味着 '引用'. 針對對象來講,若是你要複製一個對象,與原來的對象沒有關聯,用「=」,若是要使用兩個變量指向同一個對象,就使用「&=", 這時候,一個變量做出的任何改變都會影響到別個變量。
在CI'超級對象'中加入你本身的代碼
你能夠爲CI'超級對象'加上你本身的代碼。假定你已經寫了一個名爲Status的model, 它有兩個屬性:$one和 $two, 構造函數分配兩個值給他們:$one = 1 和 $two = 2。 當你裝載這model時,讓咱們來看看會發生什麼。
instance類有一個變量叫作load, 用來引用對象CI_Loader。 所以你在你的控制器中寫的代碼是:
$this->load->model($status);
換句話說,調用當前CI「超級對象」的類變量load的model方法,裝載一個model, 這個model的名稱存放在變量$status中. 讓咱們看一下保存在/system/libraries/loader.php)中的model方法:
function model($model, $name ='') {
if ($model == '') {
return;
}
$obj=& get_instance();
$obj->_ci_load_model($model, $name);
}
(這個函數裏的變量$name是你要裝載的model的一個別名。 我不知道爲何要使用一個別名,也許它會用在其餘的 namespaces 中。)
就象你看到的,model實例是被類變量引用的。 由於 get_instance()是一個單一實例的方法,你老是針對同一個實例進行操做。
若是你再運行控制器, 把咱們的 '檢查' 代碼來顯示類變量, 你如今將會見到這個類實例包含兩個新的屬性,$one的$two:
["status"]=> object(Status)#12(14){["one"]=> int(1)["two"]=> int(2)... (等等)
換句話說, CI'超級對象' 如今包括一個對象叫作$status, 它包含了咱們剛定義的兩個變量,並被賦以咱們給定的值1和2。
所以咱們正在逐漸地建立一個大的 CI'超級對象', 容許你使用它的某些方法和屬性,而不擔憂它們來自哪裏,或處於什麼 namespace 中。
這是須要 CI 箭符號的理由。 爲了要使用一個model中的方法, 你必定先裝載model到你的控制器中:
$this->load->model('Model_name');
這使model被裝載入當前控制器類的實例中,也就是$this->中。你隨後能夠調用控制器中的model對象中的方法, 像這樣:
$this->Model_name->function();
就好了。
CI'超級對象'的問題
當Rick剛開始開發CI時,爲了讓CI在PHP4和PHP5下行爲一致,他必須在Base4文件中使用比較醜陋'的代碼,無論醜不醜,咱們不用關心,只要CI可以在PHP4環境下工做得和PHP5同樣好就好了。
有其餘二個話題值得在這裏提一下:
。 你能夠嘗試開發一個不是現成的對象並讓它參與工做
。 你必須當心地架構成你的網站, 由於你不能從別一個控制器裏調用某個控制器裏的方法
讓咱們一個一個地來分析這二個問題。 你記得我提到的T恤衫那件事嗎?在調用一個成員函數時我一直收到「企圖調用一個非對象的成員函數」的出錯信息,這個出錯信息產生的緣由通常是由於你調用了一個類方法,可是忘了裝載這個類。換句話說,你寫了下列語句:
$this->Model_name->function();
可是忘記在它以前調用:
$this->load->model('Model_name');
還有一些其它的情形,好比,你在類的一個方法中裝載了model, 而後你企圖在另外一個方法裏調用model的成員方法,雖然在同一個對象中,這樣作也不行。因此最好的方法是在類的構造函數中裝載model, 而後能夠在這個類的全部方法中使用。
問題也可能更嚴重。 若是你寫你本身的類, 舉例來講,你可能想要使用這個類存取數據庫, 或在你的 config 文件中讀取信息, 換句話說,讓這個類存取CI超級對象的某些部分。(如何裝入你本身的類和libraries會在第 13 章討論。) 歸納起來,除非你的新類是一個控制器,一個模型或視圖,它不能在CI 超級對象中被構造。 所以你不能在你的新類中寫這樣的代碼:
$this->config->item(base_url);
這不會工做, 由於對你的新類來講, $this-> 意味着它自己, 而不是 CI 超級對象。取而代之地,你必須經過調用Instance類用另外一個變量名(一般是 $obj)把你的新類加入CI超級對象:
$obj =& get_instance();
如今你能象調用CI超級對象同樣地調用它:
$obj->config->item('base_url);
並且此次它可以工做。
所以,當你編寫你的新類時,記得它有它本身的標識符。 讓咱們使用一個較簡短的例子來把這個問題講得更清楚一點。
你想要寫一個library 類, 用向你的服務器發出頁面請求的URL查找它的地理位置。 這個library類有點象netGeo類,你能夠在下列網址找到它:
http://www.phpclasses.org/browse/package/514.html
這個類使用一個switch函數,根據URL的地域分派不一樣的網頁,好比來自英國和美國的URL請求,你就返回一個英語網頁,德國和奧地利的URL請求就返回一個德語網頁等等。如今,完整的URL會分紅二個部分:基本URL(
www.mysite.com/index.php
)和附加的URL部分(mypage/germanversion)。
你須要從 CI 的 config 文件取得基本URL部分。 後半段網址經過你的新類的構造函數中的swith語句生成,若是這個客戶在德國,調用德國頁函數,依次類推。當這個工做在構造函數中作完之後,你須要把結果放到一個類變量中,因此能夠在同一個類的其它函數中使用,這意味着:
。 基本URL從CI config文件中取得,這個只能經過CI超級對象的引用得到,換句話說,你能夠用$obj->config->item('base_url');得到
。 URL的後半部分由你的新類的構造函數生成,並寫到一個類變量中:$base. 這個變量與CI超級對象無關,它屬於你的新類, 被引用爲$this->base
裝載時會用到兩個關鍵詞:$this-> 和 $obj->, 在同一段代碼中被引用,舉例來講:
class my_new_class {
var $base;
My_new_class() {
$obj= & get_instance();
// geolocation code here, returning a value through a
// switch statement
// this value is assigned to $local_url
$this->base =$obj->config->item('base_url');
$this->base .= $local_url;
}
}
若是你不清楚這些概念,就會成爲頻繁出現「調用非對象的成員函數"的緣由. 舉例說,若是你試着調用$obj->base或 $this->config->item()時,這個出錯信息就出現了。
轉向剩餘的問題, 你不能從一個控制器內部調用別一個控制器的成員方法。 你爲何會想要這樣作? 這視狀況而定。 在一個應用中,我在每一個控制器內部寫了一系列自我測試函數, 若是我調用 $this->selftest(), 它完成了各類不一樣的有用測試。 可是在每一個函數中重複代碼彷佛與OO編程的設計原則不符,所以我想在其中一個控制器中寫一個函數,能夠進入到其它的控制器中執行自我測試代碼。當我這樣作了,指望獲得想要的結果。結果,固然不能如我所願,由於在一個控制器內不能調用另外一個控制器的成員方法。
做爲一個準則,若是你有代碼被超過一個控制器調用的話,把它放入一個model或者其它什麼分離的代碼文件中,這樣就可使用了。
這些都是小問題。 正如Rick告訴個人同樣:
"我想要簡化問題,因此我決定建立一個大的控制器對象包含須要的不少對象實例...當一個用戶建立他們本身的控制器時,他們可以輕鬆地訪問任何資源,不用擔憂做用域的問題"。
這樣作至關不錯,絕大多數狀況下,CI超級對象有效率地,徹底處在幕後完成工做。所以我沒必要要去作那件T恤衫了。
摘要
咱們已經看到CI建立的'超級對象' 確保全部的方法和變量能夠自動地獲取而不用擔憂如何管理以及爲「做用域」操心。
CI 用引用實例的方法把一個一個類實例組合成一個超級對象。大多數狀況下,你不須要知道CI超級對象是如何工做的,只須要正確使用「->"符號就好了。
咱們也學習瞭如何編寫本身的類,並使它與CI很好地協同工做。
下一章咱們學習:
使用 CI 測試代碼
歡迎關注本站公眾號,獲取更多信息