[PHP note.] 微型專案 Part 1

這是技術外加嘴砲文。

專案就是這樣,從許多大中小型的 Framework 挑一個適合的來用,萬一沒有中意的,那只好-複製貼上-自己刻一個比較快。當然,不是什麼時候都可以重造輪子的,案子很急很趕-屎都在門邊-的時候,挑幾個小型的 Framework 快速上手應該是比較好的解法。

那我寫這個幹麼(被揍飛!

需求認知

快速開發,又得要考慮多人同時進行,版本控制就很重要,git 當然是不二選擇。除了 git 我想不到其他的辦法,沒辦法,誰叫 git 這麼好用呢。版本控制確定了,那麼就得決定專案需求的方向。各大類型的 Framework 總是免不了一大堆的設定、Controllers、Modules、Views、Plugins 等等,當我不需要得時候,是不是能有一個檔案搞定的解法呢?

又,資料庫怎麼辦?在 gitHub 上面不乏有許多小型的資料庫工具可用,像是以 @pct4money 專案所使用的 Idiorm 就是一個不錯的工具。當然,如果你想自己用 PDO 來刻也是可以的

那,Views 怎麼辦呢?用 Smarty 嗎?還是?

確認需求

所以,我決定這麼做:

  • 自己刻一個簡易的 Bootstrap,然後可以使用 Controllers, Plugins, Modules 等等。
  • 資料庫使用 idiorm。

  • Views 直接使用 PHP(其實 PHP 本身就是樣板引擎)。
  • 我要做一個 RESTful 的 API 介面,其實用不太到 Views 的功能,所以才會選擇用 PHP 自己刻。那麼,Bootstrap 該怎麼開始比較好?

    自己刻,比較快?

    真的嗎?

    首先,我知道 RESTful 的 API 需要回傳 HTTP 狀態碼,然後要將 body 填入回傳的資訊。我們有兩種方法,一種是 JSON,另一種是 XML,所以我們就會有兩種回傳資訊的功能。

    HTTP 狀態碼則可以用 header() 來送,簡單明瞭(喂

    <?php 
      header('HTTP/1.0 400 BAD REQUEST');
    ?>
    

    那麼,Bootstrap 還是沒有解決啊?

    <?php 
      class Controller {
          public function __construct() {
          }
      }
      class Container extends Controller {
          public function __construct() {
          }
          public function startup() {
              // 開始跑
          }
      }
      $container = new Container();
      $container->startup();
    ?>
    

    那我們的 Controllers 呢?舉一個簡單的例子,我們開一個目錄叫做 controllers,然後裡面放了個 Api.php 這樣。

    <?php 
      class Api extends Controller {
          public function __construct() {
          }
          public function startup() {
              // 開始跑
          }
      }
    ?>
    

    然後訪問 index.php 的時候,發現 Api.php 並不會動,那一定是有什麼-奇怪的東西混進來了-寫錯了。所以我們再來看一下 index.php 是不是有漏掉的地方。

    <?php 
      class Controller {
          public function __construct() {
          }
      }
      class Container extends Controller {
          private $controller;
          public function __construct() {
          }
          public function startup() {
              // 開始跑
              require_once 'controllers/Api.php';
              $this->controller = new Api;
              $this->controller->startup();
          }
      }
      $container = new Container();
      $container->startup();
    ?>
    

    疑?那最近每一套 Framework 都很夯的 Routing 功能,或是去除掉 index.php 的動作要怎麼做呢?當然,如果你有用過 Rewrite Engine 的話,這個問題應該很快就能夠解決。要怎麼把網址拆開來,依照 Controller/Method/Value1/Value2 來傳遞到你的 Controller 裡面,這就是請自己努力的地方了。而其實,你也可以參考各大 Framework 的作法,也是可以學到一點東西的。

    所以,我把網址所傳入的東西依照 Controller/Method/Value1/Value2 拆開來了,那麼,我們的 Container 就可以這樣改寫:

    <?php 
      class Container extends Controller {
          private $controller;
          private $segments;
          public function __construct() {
          }
          public function startup() {
              // 開始跑
              // 做了一狗票的事情,把網址轉成下列格式,存入 segments 中。
              // Controller/Method/Value1/Value2
              // 所以我們就把 Controller 提出來
              $controllerName = ucfirst(strtolower(array_shift($this->segments)));
              $controllerPath = dirname(__FILE__) . DIRECTORY_SEPArATOR . 'controllers'. DIRECTORY_SEPARATOR . $controllerName . '.php';
              if (file_exists($controllerPath)) {
                  require_once $controllerPath;
                  $this->controller = new $controllerName;
                  $this->controller->startup();
              } else {
                  die('Controller does not exists.');
              }
          }
      }
    ?>
    

    方法呼叫也是一樣,繼續改寫 Container 的部份:

    <?php 
              if (file_exists($controllerPath)) {
                  require_once $controllerPath;
                  $this->controller = new $controllerName;
                  $this->controller->startup();
                  $methodName = ucfirst(strtolower(array_shift($this->segments)));
                  if (method_exists($this->controller, $methodName)) {
                      $this->controller->$methodName();
                  } else {
                      die('Controller's method does not exists.');
                  }
              } else {
                  die('Controller does not exists.');
              }
    ?>
    

    傳值呢?我們有好多好多的值要傳,怎麼傳?在 PHP 中有一個方法,叫做 call_user_func_array,使用的方式也很簡單,我們看 code 比較快:

    <?php 
                  if (method_exists($this->controller, $methodName)) {
                      // 原本的呼叫方式我們不要用了
                      // $this->controller->$methodName();
                      call_user_func_array( array(&$this->controller, $methodName), $this->segments );
                  } else {
                      die('Controller's method does not exists.');
                  }
    ?>
    

    那,如果我要回傳呢?我們在這裡用 JSON 回傳的方式來舉例說明。我們寫一個靜態方法在 Controller 裡面,用靜態的方式來呼叫就好了。

      <?php 
      class Controller {
          public function __construct() {
          }
          public static function responseJSON($data = null) {
              if ($data !== null) {
                  if (!is_array($data)) {
                      $data = array((string) $data);
                  }
                  if ($data = json_encode($data)) {
                      header("pragma: no-cache");
                      header("cache-control: no-store, no-cache, max-age=0, must-revalidate");
                      header("content-type: text/x-json; charset=utf-8");
                      header("X-JSON: ". $data);
                      echo $data;
                      exit;
                  } else {
                      die('JSON object invalid.');
                  }
              } else {
                  die('JSON object invalid.');
              }
          }
      }
      // ... 後面省略一萬行
      ?>
    

    你可以這麼做:

      <?php 
      class Api extends Controller {
          public function __construct() {
          }
          public function startup() {
              // 開始跑
          }
          public function ThereIsTheTest() {
              // 這裡省略一萬行
              $data = array('value' => 'There is the test');
              Controller::responseJSON($data);
          }
      }
      ?>
    

    是的,這樣一個簡易的 Bootstrap 就完成了。

    錯誤控制

    上面看到不少 die(),寫太多的話你以後要除錯真的會 DIE!不過這個下次再談(疑)。

    資料控制

    idiorm 我們下次見(喂喂)。

    小結

    以入門者來說,要寫一個簡易的控制器並不是難事,你看像我這種弱雞都寫得出類似 RESTful API 的東西了,可見這並非太過於困難。-只要有心,人人都可以自己刻-。程式碼的部份無法交代太多,不過,已經有一個專案是以這種類似模式在開發,然後最近要正式上線測試了。

    所以說,在程式上還是有很多眉眉角角是必須要去注意的。就像是你永遠沒辦法預防使用者在 ajax 的時候,按下 ESC 或是 F5 鍵一樣(這在瀏覽器動作上,是有可能中斷 ajax 傳輸的操作)。

    這是一個很入門的輪子,就像是 @pct 說得,如果沒有辦法改變世界,那最起碼改變自己的人生

    Hina Chen
    偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
    Taipei