最近更新: 2021-06-05

PHP框架 - CommonGateway 初步

CommonGateway Framework (以下簡稱 CG)。概念請見「CommonGateway 介紹」。源碼託管於 github : common-gateway-framework ,主要程式碼只有一個 index.php 。

本文範例之完整內容請見「demo/api-first-step」。以下將逐步說明如何使用 CG 設計你的第一個 RESTful API 。

CG (也就是那個 index.php) 放在網頁根目錄下,然後以瀏覽器或你慣用的 REST client 工具開啟網頁。本文範例的網頁根目錄是 /home/rock/public/cg ,瀏覽器開啟的 URL 是 http://localhost/cg/index.php 。開啟後,會看到下列的提示內容:

index.php/{control_name}/{object_id}.

You may put your controller class in controllers/{class_name}.php.

看到提示了嗎?根據提示,建立 controllers 子目錄,再於 controllers 子目錄下建立 book.php ,用以撰寫本文示範用的 Book 控制項。 controllers/book.php 最初內容如下:

<?php
class Book {

}
?>

再用瀏覽器或 REST client 開啟 URL http://localhost/cg/index.php/book (以下提到 URL 時,會省略 index.php 前的主機部份),CG 就會根據 URL 後面的路徑 "book" 去找 book.php 並執行它。因為我還沒在 Book 類別中實作任何東西,所以 CG 會顯示下列內容:

501 This service does not implement get or index method.
使用 REST client 工具時,HTTP request header 請記得設置一行「Accept: application/json」。 CG 遵守 RESTful 規範,依請求標頭的 Accept 內容決定回傳的文件型態,而不是一律回傳 JSON 資料。

以瀏覽器或 REST client 直接開啟 URL 時,預設的請求方法(REQUEST_METHOD)就是 GET ,所以 CG 會去調用 get()index() 方法。而 get()index() 的差異表現在語意上。 index() 用於列出 Book 記錄的索引列表,所以它不需給予參數。而 get() 至少要一個參數指示取回哪一筆 Book 記錄。

我先寫 index()。就簡單列出兩筆書名記錄吧。

<?php
class Book {
    function index() {
        // $books = $query->from('book')->select();

        $index_model = array(
            'books' => array(
                new BookModel('123', 'book 123'),
                new BookModel('456', 'Book XYZ')
            ),
            'time'  => date('Y-m-d H:i:s')
        );
        return $index_model;
    }
}

class BookModel {
    var $isbn;
    var $title;
    function __construct($isbn, $title) {
        $this->isbn = $isbn;
        $this->title = $title;
    }
}
?>

再次用瀏覽器或 REST client 讀取 index.php/book 。

瀏覽器會看到下列內容:

Template is missing. Missing views/book/index.phtml.

RESTful client 若正確設置 Accept 標頭,則會看到 JSON 資料,如下圖所示:

restful client 範例圖

當客戶端送來的 Accept 標頭是 application/json 時, CG 會自動回傳 JSON 資料 (參考 JSON 處理之自動回傳)。用瀏覽器送來的 Accept 標頭則會是 html ,所以 CG 會按控制項方法,嘗試載入視圖 views/book/index.phtml 。以下繼續說明如何建立 HTML 視圖。

根據提示,建立子目錄 views/book 。這裡將放置 Book 控制項所需要的視圖(View)。繼續建立視圖文件 index.phtml ,內容如下:

<html>
<ul>
    <?php foreach ($books as $book): ?>
    <li><?=$book->isbn?> : <?=$book->title?>
    </li>
    <?php endforeach ?>
</ul>
<p>
Index date: <?=$time?>
</p>
</html>

範例 Book 的 index() 回傳了一個陣列 $index_modelCG 會按 自己的回傳值轉換規則 把它的內容展開變成視圖 index.phtml 中的區域變數內容,變數名稱就是陣列的鍵。所以在 index.phtml 會出現 $books$time 兩個變數。然後用 PHP 的樣板語法印出 $books$time 的內容。

再次讀取 idnex.php/book ,終於出現能看的內容了。


    123 : book 123
    456 : Book XYZ

Index date: 2013-01-30 06:33:05

繼續實作 get() 方法,它需要一個 ISBN 參數以便查詢。所以宣告 get() 方法參數清單的第一個參數為 $isbn。參數內容會由 CG 根據 URL 路徑第二節的內容傳遞過來,不需控制項插手。

class Book {
    function index() {
        // 省略...

    }

    var $book;
    function get($isbn) {
        /*
        $book = $query->from('book')->
                    ->where(array('isbn' => $isbn))
                    ->select();
        */
        if ($isbn != '123') {
            HttpResponse::not_found();
        }
        $this->book = new BookModel('123', 'book 123');
        return;
    }
}

接著建立 GET 對應的 get.phtml 視圖。此處 get() 方法示範的是沒有回傳值處理方式,和上述 index() 所示範的方式不同。所以 CG 在此選擇的回傳值轉換規則是直接將 Book 控制項當成資料模型($model),將它的公開屬性成員($this->book)變成視圖 get.phtml 內的區域變數 $book

<html>
<h2><?=$book->title?></h2>
<p>ISBN of this book: <?=$book->isbn?>
</p>
</html>

用瀏覽器開啟 index.php/book/123 ,會出現下列內容:

book 123

ISBN of this book: 123

到此目前,我已經建立了一個非常基本的 Book 查詢功能的 RESTful API 服務了。

如果 Book->get() 方法是回傳 new BookModel() ,則視圖內可用 $model 或 $book 取得資料內容。 例如:

class Book {
    function get($isbn) {
        if ($isbn != '123') {
            HttpResponse::exception(HttpResponse::NOT_FOUND);
        }
        return new BookModel('123', 'book 123');
    }
}
問答時間

本問答中沒提到的內容,可能在「CommonGateway 介紹」就說明了。

問:等等,我的控制項的父類別是什麼?

答:使用過其他框架的使用者,應該會注意到本文的控制項類別 Book 沒有繼承任何父類別。

大部份框架確實需要使用者實作的控制項繼承它們指定的控制項父類別。但 CG 不需要。 CG 有很多把資源塞給控制項的途徑,所以你的控制項不需要繼承任何類別。 CG 把類別繼承關係的決定權還給你,

問:我一定要實作 index 方法嗎?

答:不需要。如果你的控制項原本就不打算提供索引服務,那就不需要實作 index 方法。 GET, POST, PUT, DELETE 或其它方法也一樣,如果你不需要,你就不必留個位子。

當客戶端請求的方法未實作時,CG 會按 HTTP 協定的標準作法,回應 501 Not implemented 訊息給客戶端。

問:我不用載入視圖與指派視圖的資料嗎?

答:CG 會根據使用方法自動載入對應的視圖(View)。 你只要在 views 子目錄建立符合預期名稱的目錄與視圖文件即可;也就是相同控制項名稱的子目錄,以及相同方法名稱的視圖文件。

例如 CG 調用 Book 控制項的 get() 方法之後,就會自動載入 views/book/get.phtml 視圖。

如果你的控制項想自己處理視圖,那就讓你的控制項方法回傳 false 或狀態碼,詳見「控制項動作函數回傳狀態碼的作法」。 又或者回傳 cg\View 實例,詳見「CommonGateway 的 View 類別」。 此時 CG 就不會用自己的方法去找尋與載入視圖。

視圖也是一份 PHP 文件,所以它的副檔名首字冠上 'p' 以便和一般文件區隔。 而副檔名後的內容則表示回傳給客戶端的視圖的文件型態。 瀏覽器預設要求的文件型態是 html ,所以本文建立的視圖副檔名都是 .phtml 。 稍後的文章會說明如何回傳像 JSON, XML 的文件給客戶端。

最後,從 CG 的立場來看,它與控制項的關係是它調用了控制項的方法,所以控制項方法應該會把結果回傳給它。 因此 CG 會自己從控制項方法的回傳內容決定最後要呈現給客戶端的資料是什麼,並將資料塞到視圖中。 故你也不需要指派視圖的資料。

問:這裡說明了控制項和視圖的處理策略,但好像沒有規劃資料模型?

答:CG 沒有設計資料模型的處理策略。 換句話說,你可以自由選擇你偏好的資料庫框架。

相關文章