組成系統的最基本元素
什么是Widget
Widget是組成Typecho的最基本元素,除了已經抽象出來的類庫外,其它幾乎所有的功能都會通過Widget來完成。在實踐中我們發現,在博客這種小型但很靈活的系統中實施一些大型框架的思想是不合適的,它會使系統靈活性降低并且維護成本增高。因此為了達到功能和抽象的統一,我們將系統中所有的功能單元都看作一個Widget,它們都繼承自一個超類''TypechoWidget''。
如何調用Widget
Widget有兩種調用方法,它們分別是
1、顯式地初始化一個Widget實例
class Foo extends Typecho_Widget
{public function test(){echo 'hello world';}public function render($a, $b){echo $a $b;}
}$widget = new Foo();
2、使用工廠函數**factory**初始化Widget,并運行入口函數。
Typecho_API::factory('Foo', 6, 8);
//outputs: 14
在系統中,我們通常使用后者來實例化一個Widget -> Typecho::widget函數將直接返回一個實例化的Widget對象。它的第一個參數是Widget的絕對名稱,在Typecho系統中Widget有兩個存放的地方,他們分別是**./widget**目錄和**./var/plugins**目錄。Widget與其文件名保持一致且區分大小寫,比如一個Widget的名稱為TestMe那么它的文件名就是TestMe.php。
Widget文件可以按目錄存放,調用的時候用點分號分割目錄。比如存放路徑為**./widget/contents/TestPost.php**,調用方法就是
Typecho::widget('contents.TestPost');
在上面的解釋中,我們提到widget函數的作用是實例化一個Widget并運行它的入口函數。Widget的入口函數是render,這是一個公開函數,子類在繼承超類TypechoWidget時必須實現這一方法。因此widget函數后面的參數將傳遞給入口函數render,比如上例中的Typecho::widget('Foo',6,8),后面的兩個參數將傳遞給Foo::render($a, $b)。
何時調用
Widget通常在以下三個地方被調用。
1、默認組件。Typecho系統共有兩個入口(前臺和后臺),在這兩個入口初始化的時候分別會運行一些必要的默認組件,在index.php中有以下代碼
Typecho::widget('Options')->to($options); //初始化配置組件
Typecho::widget('Access')->to($access); //初始化權限組件
在./admin/header.php中有以下代碼
Typecho::widget('Options')->to($options); //初始化配置組件
Typecho::widget('Access')->to($access); //初始化權限組件
Typecho::widget('Menu')->to($menu); //初始化菜單組件
這些Widget在系統初始化時被強制執行。
2、通過路由器默認執行的組件。[[develop:route|路由器]]是系統的重要組成部分,前臺的所有頁面都會通過路由器來展現,**路由器在默認組件執行之后被執行**。我們可以在路由表中設置默認執行的組件,這樣當頁面被導向這條路由記錄時,默認組件將被執行。下面是一個路由表的示例,你可以在./config.php中找到它的完整定義。
/** 定義路由結構 **/
global $route;$route = array('index' => array('/', 'index.php', NULL, NULL, array('contents.Posts')), //最后的一個參數表示默認組件'index_page' => array('/page/([0-9])[/]?', 'index.php', array('page'), '/page/%d', array('contents.Posts')),'post' => array('/archives/([0-9])[/]?', 'post.php', array('cid'), '/archives/%d', array('contents.Post')),'rss' => array('/feed[/]?', '../../xml/rss.php', NULL, '/rss', NULL),'do' => array('/([_0-9a-zA-Z-])\.do?', array('DoWidget'), array('do'), '/%s.do'),'job' => array('/([_0-9a-zA-Z-])\.job?', array('JobWidget'), array('job'), '/%s.job'),'login' => array('/login[/]?', 'admin/login.php'),'admin' => array('/admin[/]?', 'admin/index.php'),'page' => array('/([_0-9a-zA-Z-])[/]?', 'page.php', array('slug'), '/%s', array('contents.Post')),);
3、在最終頁面中被執行的組件。最終頁面也就是開發者最常接觸到的模板之類的頁面,我們通過widget函數來調用我們需要的組件。
<html>
<head>
<title><?php $options->title(); ?></title>
</head>
<body><div>
<!-- 第一種調用方法,調用公開方法生成列表 -->
<?php Typecho::widget('contents.Posts')->output('li', '_blank', 'list', true, 10, '[...]'); ?><!-- 第二種調用方法,調用集成方法生成列表 -->
<?php Typecho::widget('contents.Posts')->parse('<li><a href="{permalink}">{title}</a></li>'); ?><!-- 第三種調用方法,每行輸出 -->
<?php Typecho::widget('contents.Posts')->to($posts); ?><?php while($posts->get()): ?><h2><a href="<?php $posts->permalink(); ?>"><?php $posts->title(); ?></a></h2><cite><?php $post->date('Y-m-d'); ?> | <?php $post->category(','); ?></cite><div><?php $post->content('...'); ?></div><?php endwhile; ?>
</div>
</body>
</html>
由于widget函數將直接返回實例化的組件(Widget)對象,因此我們可以通過返回值直接使用Widget的公開方法。其中一些比較特殊的方法將在后面被陸續介紹到。
一次執行規則
什么是一次執行規則
?當一個組件被實例化以后,它將被保存在對象堆棧中,當我們第二次使用這一組件時,它將被直接從堆棧中取出然后返回。這一規則可以在保證組件唯一性的同時節約服務器資源。
如何調用已經被實例化的組件
一般來說我們通過函數widget調用一個組件時,它會返回一個實例化組件,你不必擔心它是否是第一次被實例化。
Widget內部方法與接口
to方法
to方法是一個非常有用的偽方法,它的作用在于把一個實例化的Widget賦到一個變量中。我們可以通過以下代碼來展示。
Typecho::widget('Foo')->to($foo);
$foo->test();
在上面的代碼中我們通過to方法把實例化的Foo組件賦值給$foo。這樣我們在后面的代碼中可以用$foo變量來代表實例化的Foo對象,它等效于
$foo = Typecho::widget('Foo');
之所以不直接使用賦值語法,是因為賦值在模板中會經常用到,這一簡寫語法比較好理解,可以簡化模板語法。**to方法在超類TypechoWidget中被實現,通過繼承TypechoWidget可以自動具備這一方法**。
push以及相關方法
每個Widget都有一個內部隊列,用于存儲多行數據,這個堆棧的數據存取方法在超類TypechoWidget中被實現,我們通過繼承超類獲得對隊列的操作能力。每次執行push方法,都將把一行數據壓入隊列,并移動隊列的指針。數據壓入完畢后,我們可以通過get方法移動指針,并且通過直接訪問組建的內部屬性獲得某行的具體數據,我們通過如下代碼來展示這一過程。
class Foo extends TypechoWidget
{public function render(){//定義三行數據$row1 = array('name' => 'Mike', 'age' => 17, 'sex' => 'man');$row2 = array('name' => 'Lucy', 'age' => 27, 'sex' => 'woman');$row3 = array('name' => 'Clark', 'age' => 23, 'sex' => 'man');//按順序壓入隊列$this->push($row1);$this->push($row2);$this->push($row3);//從隊列中取出相應值while($this->get()){echo "My name is " . $this->name . ", I'm " . $this->age . " years old, I am a " . $this->sex . ".\r\n";}}
}/***
輸出:
My name is Mike, I'm 17 years old, I am a man.
My name is Lucy, I'm 27 years old, I am a woman.
My name is Clark, I'm 23 years old, I am a man.
***/
parse方法
parse方法實際上是對隊列內數據輸出的改善,以方便用戶在html視圖模板頁面中顯示列表,你不需要使用while之類的循環語法,直接指定一個列表模板即可,此函數會按照模板直接輸出列表。因此我們可以對上面的例子做一點改進,用更少的代碼達到同樣的效果。
class Foo extends TypechoWidget
{public function render(){//定義三行數據$row1 = array('name' => 'Mike', 'age' => 17, 'sex' => 'man');$row2 = array('name' => 'Lucy', 'age' => 27, 'sex' => 'woman');$row3 = array('name' => 'Clark', 'age' => 23, 'sex' => 'man');//按順序壓入隊列$this->push($row1);$this->push($row2);$this->push($row3);//使用parse輸出$this->parse("My name is {name}, I'm {age} years old, I am a {sex}.");}}/***
輸出:
My name is Mike, I'm 17 years old, I am a man.
My name is Lucy, I'm 27 years old, I am a woman.
My name is Clark, I'm 23 years old, I am a man.
***/
alt方法
在視圖中輸出widget列表內容時,可能產生如下隔行輸出不同風格的需求。
<ul>
<li class="style-one">hello world</li>
<li class="style-two">hello china</li>
<li class="style-one">hello earth</li>
<li class="style-two">hello moon</li>
</ul>
注意以上代碼中class的內容,也就是以2為界的循環輸出。我們可以通過調用alt方法達到目的。
<?php Typecho::widget('Hello')->to($hello); ?><?php while($hello->get()): ?>
<li class="<?php $hello->alt('style-one', 'style-two'); ?>"><?php $hello->word(); ?></li>
<?php endwhile; ?>
我們在上述代碼中使用了alt方法,alt方法接受不定數目的參數,也就是說你可以給它傳遞任意個數的參數,它將按照你傳遞參數的個數循環輸出這些參數。