現在讓我們更深入的了解一下prado框架。這一章我們會介紹一些框架的基本概念和如何使用現有的prado組件來開發一個prado應用。
2.1 組件
根據clemens szyperski的說法, ``軟件組件是一個具有特定接口的組合單元,可以獨立存在,參與組成其它組建。''
在prado中, 組件是 tcomponent 或者其子類的實例。prado框架中已經包括了 tcomponent
類,它主要實現了組件的屬性和事件機制。
一個完整的組件類定義包括兩個文件:
注意:prado 是區分大小寫的,組件類型,屬性名稱,事件名稱,文件名等等這些都是區分大小寫的。
2.1.1 控件
控件是定義了用戶界面的prado 組件。每一個控件都有一個父控件和一些子控件(注意,這里的父,子這些關系和面向對象中的父類子類的概念是完全不一樣的。一個控件是另外一個控件的父控件只是說明了前者擁有后者,這個擁有關系可以理解為一個目錄下包含了一個子目錄;而絕不是后者是前者的子類、是從前者繼承而來的這個概念)。頁面是最高級別的控件,它沒有父控件(當然你也可以認為application是它的父控件)。一個prado的應用是一個頁面的集合,每一個頁面都代表了一個層次樹狀結構的控件集,控件與控件之間關系是通過父子關系聯系在一起的。
這種父子關系可以通過模板文件來建立。模板文件的格式和html是很像的,只不過在html的基礎上增加了一些特殊的標簽來定應控件。如果一個控件的標簽被另外一個控件的標簽所包括,那么前者就被認為是后者的子控件。靜態文本也被認為是子控件。
控件可以擁有一個模板文件來描述它的界面內容。缺省情況下模板文件和類文件在同一個目錄下,而且文件名是相同的,模板文件的后綴則為 .tpl 。同規格文件一樣,如果你需要把模板文件放在不同地方或者用鱉的文件名,可以通過設置類常量 template_file 的值來指明模板文件的位置。
每一個控件都有一個id,它可以在同一級別的控件中來唯一標識控件本身。id路徑則是在控件的樹狀結構中,從當前控件到目標控件的所有控件id的序列,它可以用于訪問一個控件。比如,在
homepage中,有一個 menu 控件,它擁有一個子控件 button 。對于控件homepage而言,可以通過id(menu,button)路徑來訪問 button 控件。如果用php代碼來書寫,就是 $this->menu->button .
2.1.2 實例化組件
組件有兩種方式來實例化。一種方法是在一個控件的模板文件中定義它,當這個控件被創建的時候,框架會自動的去實例化這個組件。還有一種方法是在php代碼中手動的實例化。我們先來看一下前面一種靜態的創建方法,稍后再來看一下動態的創建方法。
通常只有控件在模板文件中被靜態創建。
一個控件在模板文件組件標簽來聲明,其語法如下:
......
<com:componenttype id="..." propertyname="..." eventname="...">
....body contents....
</com:componenttype>
......
這里 componenttype, propertyname, 和 eventname 應該被真正的組件類名,屬性名和事件名所替代。 id 屬性是可選的。如果定義了id 屬性,那么id 的值必須在平級的控件中是唯一的。如果沒有定義,那么框架會自動為這個控件分配一個唯一的id的。當然,這要求組件的標簽被正確的嵌套,每一個開放的組件標簽都應該和一個閉合的組件標簽組成一對,這個規則和xml的規則是一樣的。(譯者著:如果你對xml一點都不了解,建議你先看一下一些基本的xml概念,要求并不高,你只需要知道如何正確書寫一個xml文件即可。)
注意:控件的id必須是一個以字母開頭的,后面只包含字母,數字和下劃線的字符串。
模板文件中屬性的初始值字符串會被自動轉換為正確的屬性類型。目前有六種屬性類型: string, integer, float, boolean, array face="courier new">和 object. 前面三種類型的字符串格式是非常簡單的。 boolean 類型只允許使用兩個字符串: true face="courier new">和 false。 array 類型接受如 (value1,value2,key3=>value3,...) 格式一樣的字符串,這和 php 數組的初始化是一樣的。 而 object 類型就比較復雜了,它取決于屬性是如何被定義的。一些屬性可以允許使用字符串,并會把它轉換為對象,但是有些屬性就不行了。
當組件被創建(實例化)的時候,通過規格文件定義的組件的屬性和對應事件處理方法就會立刻生效了。
動態創建組件
prado
允許開發者在自己的php代碼中實例化組件。組件可以通過調用一個靜態類方法application::createcomponent($type)來被實例化,該方法的參數$type 指明了要創建的組件的名稱。組件也可以使用new 操作符來實例化。這兩者方法的區別是:前者會使用一種緩存機制,下一次創建相同的組件時速度會快很多;而后者不會使用緩存機制,每次都要完全重復執行實例化的步驟。通常情況你應該使用tapplication::createcomponent($type) 來實例化(如果你想了解更多,請參考下面的注釋)。
如果新創建的組件是一個控件,那么可以通過調用把這個控件作為其他控件的子控件。注意,如果你需要指定這個控件的id,應該在將它添加作為其他控件的子控件之前就指定id;否則的話框架會自動為它分配一個id,而且這個id是不能更改的。
注:如果在構造函數中使用或申請了資源句柄的話,只能使用 new 操作符來實例化。因為prado使用了緩存機制來實例化組件,因此如果在構造函數中使用了資源句柄的話,下次實例化的時候從緩存讀出來的數據中的資源句柄部分依然對應著原來的那塊內存地址,這樣就極容易導致系統崩潰。所以如果你要設計一個自己的組件,也盡可能的不要在構造函數中使用和申請資源句柄,而是應該把這些代碼組織在一個別的方法中,在頁面的oninit事件或其它事件中調用。一般來說構造函數只需要實現簡單的變量初始化即可。在prado的核心代碼中,所有的組件的構造函數都沒有使用到資源句柄。你在開發自己的組件的時候,可以參考一下它們的源代碼。
2.1.3 訪問組件屬性
php 5 使用了一種很好的方式來訪問組件的屬性。你可以把一個組件的屬性當作組件的成員變量來使用。比如要設置tbutton 控件的text 屬性,你可以使用$button->text="xxx"的代碼,這里$button 代表了控件的實例。對于控件來說,你還可以使用它的id路徑來訪問屬性。假定現在homepage頁面有一個子控件menubar ,menubar 控件有一個子控件hyperlink ,那么在頁面環境中,可以使用的代碼$this->menubar->hyperlink->navigateurl來讀取hyperlink 控件的navigateurl 屬性。
注意,由于php5.0中的一個bug,如果你需要設置一個屬性的值,那么你必須首先通過它的控件的id路徑來獲得控件,然后再來設置這個屬性的值。在上面這個例子中,需要用下面兩行代碼來設置navigateurl 屬性。
$link=$this->menubar->hyperlink;
$link->navigateurl="...";
如果直接使用 $this->menubar->hyperlink->navigateurl 來設置屬性值會產生一個錯誤。但是你還可以下面這段代碼來設置屬性的值,這樣就可以避免那個錯誤了。
$this->menubar->hyperlink->setnavigateurl("...");2.1.4 使用事件
事件響應函數通常在規格文件或者模板文件中指定給對應的事件,指定事件響應函數和指定屬性的初始值是類似的。注意,在規格文件或者模板文件中指定的事件響應函數,必須在此規格文件或模板文件對應的組件中定義,它的語法如下:
function handlername($sender,$param)
{
...
}
這里 $sender 指向的是觸發這個事件的控件, $param 是事件的參數,它的內容取決于事件的類型。
在編程的時候也可以使用tcomponent::addeventhandler() 方法來動態的指定一個事件響應方法。
你可以為一個事件指定多個響應方法。當這個事件被觸發的時候,所有指定的響應方法都被自動調用。所以,prado實現的是多點派發事件觸發機制。
2.1.5 數據綁定(data binding)
只有控件才可以數據綁定。
你可以給控件的屬性綁定一個表達式,當這個控件的databind() 方法被調用的時候,這個屬性的值回自動被設置為這個表達式的值。數據綁定在開發數據組件時是非常有用的,這些組件的很多屬性值都是來源于數據源提供的數據的。你可以在組件的規格或者模版文件中設定數據綁定,也可以在代碼中設定。
在模版中設定數據綁定的話,只要給屬性的值指定一個有效的php表達式的字符串,并在前面加上一個# 作為前綴。比如在頁面模版文件中使用如下的代碼:
<com:tlabel text="#$this->page->id" />
這段代碼給tlabel 組件的text 屬性綁定了一個的表達式$this->page->id 。這個表達式的作用是獲得當前控件所在頁面的id。注意,這個表達式中的$this指的是tlabel控件本身,因為$this所在上下文環境是在tlabel 中。
在代碼中要設定數據綁定,可以調用組件的bindproperty() 方法,這時候不需要在前面加上字符# 。
注意,給屬性綁定的表達式只有在databind() 被調用時才會計算該表達式的值,并把它賦值給屬性。具體內容可以參考databind() 的相關文檔。
另外,如果在模板文件中你需要給一個屬性賦初始值,而不是數據綁定的話,如果這個值是以#開頭的,那么應該將#重復一次,就像propertyname="##...." 一樣。
2.1.6 prado 組件類樹
目前發布的 prado 包括如下所示的組件樹,這些組件的屬性,事件和類的方法在prado文檔中都可以查到。
tcomponent
tadodb
tcontrol
texpression
tform
tliteral
tpage
tplaceholder
trepeater
trepeateritem
tstatements
twebcontrol
tbutton
tcheckbox
tradiobutton
tdropdownlist
thyperlink
timage
timagebutton
tlabel
tlinkbutton
tlistbox
tpanel
ttextbox
tdatepicker
thtmlarea
tvalidationsummary
tvalidator
tcomparevalidator
tcustomvalidator
trangevalidator
tregularexpressionvalidator
temailaddressvalidator
trequiredfieldvalidator
2.2 頁面
頁面是 tpage 或者它的子類的一個實例。它是最高級別的組件,即沒有父組件也不包含在一個容器中。prado的應用是由一些頁面組成的。
2.2.1 頁面的生命周期
理解頁面的生命周期對掌握prado編程是非常重要的。
首先我們要介紹一下postback的概念。我們把一個form的提交稱之為postback,如果form的數據是提交給包含該form的頁面的。postback可以被認為是由用戶在客戶端觸發的一個事件。prado會區分出把postback事件交給哪一個服務器端的組件來響應。如果找到了這個組件,比如是一個tbutton
,那么我們就把這個tbutton 組件稱為事件的發送者(sender)。
頁面在被請求調用的時候會經過幾個狀態。當一個頁面是由于它發生了一個postback而被調用的時候,這個頁面會經歷以下的生命周期:
當頁面是第一次被請求的時候,上述的生命周期會簡單一些。具體來說,導入顯示狀態,導入提交的數據,產生提交數據變化事件,輸入驗證和postback事件這幾個狀態是沒有的。
2.3 應用
每一個 prado web應用都有且只有一個的實例。它主要是負責編碼解碼用戶請求,服務器的頁面請求,和維護應用級別的參數。
2.3.1 應用的配置
每一個prado應用都應該有一個xml格式的配置文件。在"hello world"這個例子中,這個文件的文件名為 application.spec .
應用配置文件的格式如下:
<?xml version="1.0" encoding="utf-8"?>
<application default-page="..." cache-path="..."
session-class="..." user-class="...">
<alias name="..." path="..." />
<alias name="..." path="..." />
<using namespace="..." />
<using namespace="..." />
<secured page="..." role="..." />
<secured page="..." role="..." />
<parameter name="...">...</parameter>
<parameter name="...">...</parameter>
<parameter file="..." />
</application>
alias 元素定義了文件路徑的別名,路徑可以是絕對是絕對路徑也可以是相對路徑。
using 元素定義了要增加到 php搜索路徑 include_path 變量中的命名空間。命名空間是用"."號連接的。第一段是路徑的別名,之后是子目錄。比如system.web.ui 代表了框架所在目錄下的web/ui/子目錄。框架的目錄的別名已經被定義成system 。在編程的時候,也可以調用using() 函數來增加一個命名空間。
secured 標簽知名了頁面是否需要驗證/授權,page 屬性指名的是頁面的名稱或者是頁面名稱的匹配表達式。如果一個頁面被標明是的,那么說明這個頁面需要用戶驗證的。另外如果role 屬性不是空的,那么要求用戶是屬于指定的角色的。
parameter 元素定義了用戶參數。可以通過設置parameter 元素的file 屬性來導入一個參數文件。參數文件的格式如下:
<?xml version="1.0" encoding="utf-8"?>
<parameters>
<parameter name="...">...</parameter>
<parameter name="...">...</parameter>
</parameters>
default-page 屬性和 cache-path 屬性分別知名了缺省的頁面和緩存路徑。如果應用需要使用session, session-class 屬性也需要設置。如果應用需要使用框架的驗證/授權支持,那么還應該指定user-class 屬性。
2.3.2 頁面服務
prado 使用 get 變量 page 來指明要請求的頁面。比如下面的這個url:
/examples/phonebook.php?page=addentrypage
這段代碼將會請求 addentrypage 頁面。如果沒有指定page 變量,那么就會調用應用配置中的缺省頁面。
你也可以使用 tapplication::constructurl() 方法來請求一個頁面。
2.3.3 數據編碼和解碼
缺省情況 tapplication 會對所有的post和get數據進行html編碼。 ', ", <, >, & 會被轉化為',"e;,<,>和&。之所以要進行編碼主要是為了讓這些字符能被保存到數據庫中,并且能正確顯示回給用戶。如果有必要的話,可以調用pradodecodedata()函數來進行解碼。也可以重載tapplication::beginrequest() 函數不進行編碼。
有兩種方法來定義新的組件類:繼承和組合。
繼承是面向對象的設計概念。派生類定義它的父類的一些內容,還可以提供一些其他的功能。在prado框架中,組件的屬性和事件都是可以繼承的。派生類也可以提供更多的屬性和事件。所有的組件類都應該從tcomponent 或者它的派生類繼承。
所有父組件的屬性和事件會被派生組件類繼承。
組合是用于基于組件的框架的。在prado中,主要對控件使用。一個新的控件類可以被定義為幾個其他幾個控件的組合。新的類負責協調這些控件之間的通信,并代表它們和外界通信。比如可以定義一個labeledtextbox 控件類(繼承tcontrol ),它由tlabel 一個控件和一個tlabel
控件組成。新的類負責配置這兩個組成控件的屬性和方法。
一般的約定(并不強求),控件的id和屬性的首字母應該大寫,比如homepage, navigateurl,
logopict;事件以on開頭,比如onclick 。id和屬性的名字命名和變量命名一樣,必須是以字母開頭的,僅包括字母數字和下劃線的字符串。
要定義一個新的組件通常需要寫一個組件類文件,一個規格文件和一個模板文件。有些時候后面兩個文件可以不要的。
2.4.1 定義屬性
組件的屬性在組件類的規格文件中定義,規格文件的語法如下:
<?xml version="1.0" encoding="utf-8"?>
<component>
......
<property name="<property name>"
get="<getter method>"
set="<setter method>"
type="<property type>"
default="<default value>" />
......
</component>
在這里,name 用來唯一標識屬性;get 定義了一個類方法來讀取屬性,set 定義了一個類方法來寫屬性,type 定義了屬性的類型(string, integer, float, boolean, array 和object),default 標識了屬性的缺省值。其中name 屬性是必須的。getter方法的語法如下:
function gettermethodname()
{
....
return $value.
}
如果一個屬性是不可讀的,那么不要設置get 屬性。setter方法的語法如下:
function settermethodname($value)
{
// set some variable with $value
}
如果一個屬性是不可寫的,那么不要設置set 屬性。如果這個屬性的類型是string,那么也可以不指明。類型的屬性用來自動轉換配置在規格文件或者模板文件中的屬性初始值。default
屬性一般用來作為引用的目的,也是可選的。如果一個屬性是可讀的而且之前沒有被設置,那么getter方法應該返回一個缺省值。
可以用下面的語法定義組件的組件屬性:
<?xml version="1.0" encoding="utf-8"?>
<component>
......
<component type="component type" id="component id">
<property name="property name" value="property value"/>
....
<event name="event name" handler="function name"/>
....
</component>
......
</component>
這里type 屬性指明了組件類的名稱,idid 屬性指明了屬性名稱。property 元素和event 元素指明了對應的初值。
當組件被創建的時候,它的組件類型屬性會自動被實例化。
2.4.2 定義事件
組件事件在規格文件中定義,而其內在的實現機制在類文件中定義。在規格文件中定義事件的語法如下:
<?xml version="1.0" encoding="utf-8"?>
<component>
......
<event name="..." />
......
</component>
事件的name應該是一個合法的變量名。
在類文件中,通常會實現這么一個對應的方法(假定事件的名稱叫 onclick):
function onclick($param)
{
$this->raiseevent('onclick',$this,$param);
}
當這個事件實際發生的時候,這個 onclick 方法應該確實被調用了(參考框架的tbutton.php
文件)。raiseevent 方法在tcomponent 類中實現,它調用了所有對應onclick 的事件響應函數,并把參數$param 傳遞給它們。
2.4.3 編寫模板文件
在這一節里,會講一些關于如何編寫模板文件。模板是用于控件的,當時也不是所有的控件都需要模板。比如ttextbox, tbutton 這些控件因為沒有內容,因此就不需要模板。模板文件一般用于頁面或者基于組合定義的控件。
模板中的注釋,比如 <!-- ... --> 被作為靜態文本處理。
可以在組件標簽之外通過屬性標簽<prop:...>來配置組件的屬性。比如可以用以下的代碼配置模板中ttextbox 控件的text 屬性。
...
<com:ttextbox id="profile">
<prop:text>
...
</prop:text>
</com:ttextbox>
...
在一對屬性標簽中間的內容作為對應的組件屬性的值。如果一些屬性的值需要很大的數據,這樣就很方便。
在模板文件中可以使用3種特殊的標簽:
2.5 總結
這一節我們來總結一下如何基于prado來開發一個web應用。
首先需要創建兩個文件:應用的入口文件和應用的配置文件。這一部分請參考本手冊前面的"hellow,world!"這個例子。
對于一個完整的web應用而言,你還需要創建一個或者多個頁面。每一個頁面都需要有一個頁面類文件,另外可能還需要一個模板文件或者規格文件。在模板文件中,你可以把組件和其他靜態文本根據最終需要顯示給用戶看的樣子組織在一起。在規格文件中,可以定義頁面的屬性,事件和子控件,這些定義可以在模板文件和代碼中使用。這兩個中都可以設置組件的初始值和指定事件的響應函數。在頁面類文件中編寫事件響應函數和其他函數。
有些時候,為了重用代碼,你可以定義新的組件。比如你可以定一個sidebar組件來顯示用戶能看到的菜單,這樣在各個頁面中就都可以直接使用這個sidebar 組件來顯示菜單了。
prado引入了php5新的例外處理機制,能夠顯示堆棧中的錯誤信息。這樣在調試的時候你可以精確的找到什么方法發生了什么錯誤。
發布 prado 應用則非常簡單。框架建議使用相對路徑,因此如果你是這么做的話,只需要把包含你的應用的代碼拷貝到你想放置的目錄中就可以了。
新聞熱點
疑難解答