コントローラーの作り込み

コントローラーの基礎クラス作成

コントローラーは機能ごとに作ることになりますが、
同じサイト内では全ての機能に共通する処理がたくさんあるはずです。

たとえばビュー、リクエストといったようなオブジェクトを保持するフィールドはどのコントローラーでも必ず必要になるはずです。
また、ビューの操作についても、例えばSmartyであれば最後のdisplayメソッドなどは必ず実行するものです。
それに、使用するテンプレートの指定なども、コントローラー名やアクション名と名前規則で関連付けてしまえば、いちいち設定の必要はなくなります。

そのようなことから、抽象クラスとして汎用的なコントローラークラスの基礎クラスを作成し、そこで共通処理が知らないうちに行われるような仕組みにしていきます。

そして各機能ではこれを継承し、機能固有の処理のみを記述すれば効率がよくなります。

ControllerBase.php
<?php

abstract class ControllerBase
{
    protected $systemRoot;
    protected $controller = 'index';
    protected $action = 'index';
    protected $view;
    protected $request;
    protected $templatePath;
    
    // コンストラクタ
    public function __construct()
    {
        $this->request = new Request();
    }
    
    // システムのルートディレクトリパスを設定
    public function setSystemRoot($path)
    {
        $this->systemRoot = $path;
    }    
    
    // コントローラーとアクションの文字列設定
    public function setControllerAction($controller, $action)
    {
        $this->controller = $controller;
        $this->action = $action;
    }

    // 処理実行
    public function run()
    {
        try {
            
            // ビューの初期化
            $this->initializeView();
            
            // 共通前処理
            $this->preAction();
            
            // アクションメソッド
            $methodName = sprintf('%sAction', $this->action);
            $this->$methodName();            
           
            // 表示
            $this->view->display($this->templatePath);
        
        } catch (Exception $e) {
            // ログ出力等の処理を記述
        }
    }

    // ビューの初期化
    protected function initializeView()
    {
        $this->view = new Smarty();
        $this->view->template_dir = sprintf('%s/view/templates/', $this->systemRoot);
        $this->view->compile_dir = sprintf('%s/view/templates_c/', $this->systemRoot);
        
        $this->templatePath = sprintf('%s/%s.tpl', $this->controller, $this->action);
    }
    
    // 共通前処理(オーバーライド前提)
    protected function preAction()
    {
    }
}

?>

runメソッドがメインになります。
「コントローラー」のところで紹介していたのは、
Dispatcherから直接アクションメソッドを実行するというものでした。
ですが、アクションメソッドの前後にお決まりで行いたい処理があるため、
コントローラークラス内にrunという1クッションを置くような形になります。

アクションメソッドはこのrunの中で間接的に呼び出します。
そしてアクションメソッドの前後に、コントローラーとして共通のお決まりの処理を行います。

まずアクションメソッドの前に行っているのはビューの初期化です。
別メソッドにしていますが、ビュークラスとしてはSmartyを使用し、
インスタンス生成と初期設定を行っています。
そしてデフォルトのテンプレートとして、コントローラー名とアクション名をそのまま使用した名前のものを設定しています。
つまりは、テンプレートのルートディレクトリ内に、コントローラー名と同じ名前のディレクトリを作り、その中にアクション名と同じ名前のテンプレートファイルを配置すれば、それが自動的に使用されるというわけです。

次のpreActionというメソッドは、ここでは空のメソッドです。
これは、継承先でオーバーライドして使用することを前提にしています。
なのでオーバーライドしなければ何も起こりません。
アクションメソッドの前に必ず実行される事になるため、全てのアクションに共通して行いたい前処理がある場合などはpreActionメソッドをオーバーライドして記述します。
ここでミソなのは、ビューの初期化より後ろのタイミングであるということです。

つまり、あくまでinitializeViewで設定しているテンプレートはデフォルトであり、
もしアクションごとにテンプレートを用意するのではなく、共通の一つのテンプレートを使いたいとしたら、preActionメソッドで設定しなおすようにすれば、initializeView内で設定されたものに対して上書きすることになります。


アクションメソッドの実行後は、ビューの最終的な表示メソッドを実行しています。

このような形に合わせて、Dispatcherも少し作り変えなければなりません。

Dispatcher.php
<?php

require_once '../view/Smarty.class.php';
require_once '../library/Request.php';

class Dispatcher
{
    private $sysRoot;

    /**
     * システムのルートディレクトリを設定
     */
    public function setSystemRoot($path)
    {
        $this->sysRoot = rtrim($path, '/');
    }

    /**
     * 振分け処理実行
     */
    public function dispatch()
    {
        // パラメーター取得(末尾の / は削除)
        $param = ereg_replace('/?$', '', $_GET['param']);
        $param = trim($param, '/');
        
        $params = array();
        if ('' != $param) {
            // パラメーターを"/"で分割
            $params = explode('', $param);
        }
        
        // 1番目のパラメーターをコントローラーとして取得
        $controller = 'index';
        if (0 < count($params)) {
            $controller = $params[0];
        }
                
        // 1番目のパラメーターをもとにコントローラークラスインスタンス取得
        $controllerInstance = $this->getControllerInstance($controller);
        if (null == $controller) {
            header("HTTP/1.0 404 Not Found");
            exit;
        }
        
        // 2番目のパラメーターをアクションとして取得
        $action= "index";
        if (1 < count($params)) {
            $action = $params[1];
        }
        // アクションメソッドの存在確認
        if (false == method_exists($controllerInstance, $action . 'Action')) {
            header("HTTP/1.0 404 Not Found");
            exit;
        }

        // コントローラー初期設定
        $controllerInstance->setSystemRoot($this->sysRoot);
        $controllerInstance->setControllerAction($controller, $action);
        // 処理実行
        $controllerInstance->run();
    }

    // コントローラークラスのインスタンスを取得
    private function getControllerInstance($controller)
    {
        // 一文字目のみ大文字に変換+"Controller"
        $className = ucfirst(strtolower($controller)) . 'Controller';
        // コントローラーファイル名
        $controllerFileName = sprintf('%s/app/controllers/%s.php', $this->sysRoot, $className);
        // ファイル存在チェック
        if (false == file_exists($controllerFileName)) {
            return null;
        }
        // クラスファイルを読込
        require_once $controllerFileName;
        // クラスが定義されているかチェック
        if (false == class_exists($className)) {
            return null;
        }
        // クラスインスタンス生成
        $controllerInstarnce = new $className();

        return $controllerInstarnce;
    }
}

?>

「コントローラー・モデル」のところのサンプルソースと比べると、
まずコントローラーインスタンスの生成部分を別メソッドに切り出しました。
今回、パラメータより取得したコントローラー名に対してコントローラーのファイルの存在確認と、クラスの定義確認、メソッドの定義確認などを加えたために、処理が膨らみ気味になります。
なので、この部分だけ別メソッドとしました。

で、最終のところで前回はアクションメソッドを直接読んでいたところをrunメソッドに変更しています。上でも述べたとおり、アクションメソッドはrunメソッド内で間接的に呼び出すので、アクションのパラメーターをコントローラークラスに伝えなければなりません。

そのため、コントローラーに設定用のメソッドを用意して渡すようにしています。

継承コントローラー

それでは実際にControllerBaseを継承して各機能のコントローラークラスを作ってみます。
先のコントローラーの例を書き換えたものです。

CartController.php
<?php

class CartController extends ControllerBase
{
    // 記事表示
    public function displayAction()
    {        
        $params = $this->request->getParam();
        $userId = $params['user_id'];

        $cart = new Cart();
        $cartInfo = $cart->getUserCart($userId);
        $this->view->assign('cart_info', $cartInfo);

        $cartProduct = new CartProduct();        
        $products = $cartProduct->getProductList($cartInfo['cart_id']);
        $this->view->assign('prouducts', $products);
    }

}

?>

ビューインスタンスの生成やビューのdisplayメソッドの実行、リクエストインスインスタンスの生成などは基礎クラスで行っているため個々のコントローラーで記述の必要が無く、コントローラーの記述がかなりスッキリしました。

このように、コントローラーを作成するときは必ずControllerBaseを継承するというお決まりにすれば、継承元の方で多くのことを知らず知らずのうちにやってくれるため、
コントローラーとしてのコーディング量が減り、実装が楽になります。

例えば更に、モデルクラスのインスタンスを得るメソッドを作ってみます。
これは、コントローラーの冒頭でrequreしておいて、いちいちnewしてももちろん良いのですが、モデルクラスを格納するディレクトリが決まっているなら、このようなメソッドを作ることによってrequireの必要もなくなります。

ControllerBase.php
<?php

abstract class ControllerBase
{
                ・
                ・
                ・

    // 処理実行
    public function run()
    {
                ・
                ・
                ・
    }

    // モデルインスタンス生成処理
    protected function createModel($className)
    {
        $classFile = sprintf('%s/app/models/%s.php', $this->systemRoot, $className);
        if (false == file_exists($classFile)) {
            return false;
        }
        require_once $classFile;
        if (false == class_exists($className)) {
            return false;
        }        
       return new $className();
    }
}

?>