iT邦幫忙

DAY 29
13

逐步提昇PHP技術能力系列 第 29

逐步提昇PHP技術能力 - 逐步改善軟體架構 - 寫一個簡單的Controller

Controller的責任可大可小,不過最基本的部份大概有:

  1. 接收使用者的request,剖析url並處理input之後,傳給負責的controler與方法
  2. 提供方便取得View與Model的介面

使用controller,除了讓應用程式的結構更清楚,也可以把一些標準處理與流程結合進去,減少開發的功夫。
Controller的責任可大可小,不過最基本的部份大概有:

  1. 接收使用者的request,剖析url並處理input之後,傳給負責的controler與方法
  2. 提供方便取得View與Model的介面

使用controller,除了讓應用程式的結構更清楚,也可以把一些標準處理與流程結合進去,減少開發的功夫。

內容

很多Framework的Controller,都會使用front-controller的方式來實作,也就是說,所有的request實際上都是丟給一支程式(index.php),然後他再根據規則剖析URL,決定要使用哪個controller的哪個方法,來處理目前的request。

系統通常會用這樣的方式組織起來:

  1. 模組:一個獨立的單元,以一個論壇來說,可能論壇列表是一個模組、模組文章列表是一個模組、文章與回應是一個模組
  2. 功能:在模組中的個別功能,
  3. 參數:要傳給程式的GET參數

例如"index.php"不帶任何參數,也許就是呼叫Index controller的index方法,然後功能是論壇的列表;"index.php?mod=Forum&id=1"就會呼叫Forum controller的index方法,帶入參數id=1,顯示論壇id=1的文章列表;"index.php?mod=Forum&func=edit&id=2"就會呼叫Forum controller的edit方法,帶入參數id=2,顯示編輯論壇id=1的一些資料的介面等等。

所以...如果系統的參數如果沒有一定的規則,第一步並不是使用controller,而是修改系統,讓參數遵守一定的規則,這樣才有辦法用一致的方式來組織程式。

不管三七二十一,先實作一個簡單的controller吧。

<?php
namespace Fillano\Core;

class Controller
{
    public static function dispatch()
    {
        //import input vars
        $get = array();
        foreach($_GET as $k => $v) {
            $get[$k] = (is_numeric($v))? (is_int($v))? intval($v):floatval($v):(is_string($v))? filter_var($v, FILTER_SANITIZE_STRING):$v;
        }
        $post = array();
        foreach($_POST as $k => $v) {
            $post[$k] = (is_numeric($v))? (is_int($v))? intval($v):floatval($v):(is_string($v))? filter_var($v, FILTER_SANITIZE_STRING):$v;
        }

        //start dispatching
        if(isset($get['mod'])) {
            $class = "Fillano\\Controllers\\".strval($get['mod']);
        } else {
            $class = "Fillano\\Controllers\\Index"
        }
        if(isset($_GET['func'])) {
            $method = strval($get['func']);
        }
        else {
            $method = 'Index';
        }
        //search and dispatching
        if(class_exists($class)) {
            $request = new Request($get, $post);
            $response = new Response(TEMPLATE_ENGINE, 'views');
            $controller = new $class;
            if(method_exists($controller, $method)) {
                call_user_method_array($method, $controller, array($request, $response));
            } else {
                $controller->index($request, $response);
            }
        } else {
            header('HTTP/1.1 404 Not Found');
        }
    }
    public function index($request, $response)
    {

    }
    public function useModel($name)
    {
        return \Fillano\Core\ModelFactory::getInstance(MODEL_IMPL, $name);
    }
}

所有的controller都要繼承這個Controller類別,透過方法的參數接收Request與Response物件,Request->get是過濾與跳脫過的GET參數,Request->post是過濾與跳脫過的POST參數。Response物件提供一個userView方法,可以取得指定的view物件。Controller->getModel()方法,則可以取得指定的model物件。就先這樣做,先不管設計好不好...

Reqeust類別很簡單:

<?php
namespace Fillano\Core;

class Request
{
	public $get;
	public $post;
	public function __construct($get, $post)
	{
		$this->get = $get;
		$this->post = $post;
	}
}

Response類別也很簡單:

<?php
namespace Fillano\Core;

class Response
{
	private $engine;
	private $path;
	public function __construct($engine='Twig', $path='views')
	{
		$this->engine = $engine;
		$this->path = $path;
	}
	public function useView() {
		return \Fillano\Core\View::getInstance($this->engine, $this->path);
	}
}

簡單地說,就是把輸入集中在Request物件,輸出集中在Response物件,不過目前的設計是急就章XD

接下來寫controller,把他們集中在class/Fillano/Controllers目錄,也就是Fillano\Controllers這個namespace中。

先針對首頁來做,預設的類別是Index,預設的方法是index,所以在Fillano\Controllers中增加一個類別Index,繼承Controller。寫法很簡單,就把原先在index.php中除了bootstrap之外的程式碼,先搬到Index->index方法中,然後根據剛剛設計的Controller運作方式做一下調整:

<?php
namespace Fillano\Controllers;

use Fillano\Core\Controller;

class Index extends Controller
{
	public function index($req, $rep)
	{
		if(isset($_SESSION['user']['account'])) {
			$member = true;
			$name = $_SESSION['user']['name'];
		} else $member = false;
		if(isset($_SESSION['msg'])) {
			$message = mysql_real_escape_string($_SESSION['msg']);
			unset($_SESSION['msg']);
		}
		//$model = new Fillano\Models\MysqlIndex($conn);
		$model = $this->useModel('Index');
		$data = $model->getForumList();
		$view = $rep->useView();
		$view->assign(array(
			'member'=>$member,
			'data'=>$data,
			'name'=>$name,
			'message'=>$message
		));
		$view->render('index', 'html');
	}
}

實際上程式只改了兩行,就是取得model與view的這兩行。

至於原本的index.php呢?現在長這樣:

<?php
require 'bootstrap.php';
Fillano\Core\Controller::dispatch();

現在只剩兩行,一行引入bootstrap.php,一行做dispatch...功能只剩下Front Controller了。

這個簡單controller只做了四件事情:

  1. 分析參數,找到相應的controller及方法來處理
  2. 過濾、跳脫GET與POST參數,然後包裝成Request物件傳給controller的方法
  3. 把取得view的方法放在Response物件
  4. 內建取得model的方法

這樣寫的好處是:原本的程式轉移到controller非常快速,也幾乎不會出錯。不過當然功能還有非常多欠缺,也需要規劃更好的架構,讓其他需要的功能,可以有彈性地載入進來。

另外,其實已經有不少的開放原始碼的Router架構,適合用來作Controller,不妨上Github找找。

======

明天是最後一天了,先把目前的架構套到列出論壇文章的forum.php上,然後做一個簡單的總結吧。


上一篇
逐步提昇PHP技術能力 - 逐步改善軟體架構 - 單元測試
下一篇
逐步提昇PHP技術能力 - 逐步改善軟體架構 - 套用MVC架構與結語
系列文
逐步提昇PHP技術能力30

1 則留言

我要留言

立即登入留言