[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 說得,如果沒有辦法改變世界,那最起碼改變自己的人生