コントローラー
- コントローラークラス
- アクション
- デフォルトアクション
- デフォルトコントローラー
- 非アクションメソッド
- 共通処理メソッド
- 他のアクション・コントローラーへの処理委譲
- サブディレクトリによるコントローラーの階層管理
- リクエスト処理
- セッションの利用
- URLパラメーター
- プラグイン(コントローラー共通処理)
- URLルーティングの設定
コントローラークラス
CurryはMVCアーキテクチャを基本として設計されており、コントローラーとはこのMVCのCに当たるものです。MVCの中でのコントローラーの役割は、リクエスト情報を処理し、モデルやビューを制御することにあります。そしてURLと直接関連し、いわばWebリクエストの入口です。
コントローラーはクラスで表現します。コントローラークラスはかならずCurryのクラスである"Controller"を継承します。
そしてコントローラークラスのクラス名は必ず末尾に"Controller"が付加されなければなりません。
実際のクラス定義はこんな感じになります。
class HelloController extends Controller { }
アクション
コントローラーはアクションと呼ばれる処理単位の概念を持ちます。
これはメソッドで表現されます。
class HelloController extends Controller { public function world() { echo "Hello World!"; exit; } }
そしてこの"コントローラー"と"アクション"が直接URLになります。
上の例では"Hello"コントローラーに"world"アクションを定義しました。
このメソッドを呼び出したい場合、
http://www.xxxx.com/hello/world
でアクセスすることが可能です。
実際にブラウザからアクセスしてみてください。
Hello World!
ブラウザに表示されましたか?
デフォルトアクション
通常はURLで指定されたアクションメソッドが呼び出されますが、アクションを指定しなかった場合のデフォルトのアクションを定義することができます。それはindexアクションです。
class HelloController extends Controller { public function index() { echo "This is index"; exit; } }
indexという名前のアクションメソッドはそのコントローラーのデフォルトのアクションとして扱われます。
URLとしては、
http://www.xxxx.com/hello/index
でのアクセスは当然可能ですが、
http://www.xxxx.com/hello
というようにアクションを省略した場合はindexが呼ばれます。
デフォルトコントローラー
コントローラーもアクションと同じくindexがデフォルトのコントローラーです。
つまりIndexControllerならば、それがサイト全体のデフォルトコントローラーとなります。
class IndexController extends Controller { public function index() { } }
このように、indexコントローラーのindexアクションが存在する場合、
http://www.xxxx.com/
という、サイトのルートURLでのアクセスが可能です。
このようなURLは通例ではトップページが表示されるように、indexコントローラーのindexアクションはサイトのトップページとして利用するのが良いでしょう。
非アクションメソッド
privateメソッドとして定義する方法
コントローラーはURLと関連付きますので、コーディング上、明示的に「メソッドを呼び出す」ということはありません。その意味では特殊なクラスだといえます。
しかし、コントローラー内での内部処理的なメソッドを作りたい場合はあるでしょう。
例えばコントローラー内の複数メソッドで同じ処理が発生するような場合に別メソッドとして切り出したい場合などです。
コントローラーもただのクラスなので適当な名前でメソッドを作ればいいわけですが、内部処理用のメソッドの場合、URLから直接アクセスされたくはありません。
この場合、単純にアクセス修飾子をprivateにするだけでOKです。
class HelloController extends Controller { public function world() { $this->_echoText('Hello World!'); } private function _echoText($text) { echo $text; exit; } }
このコントローラークラスで、
http://www.xxxx.com/hello/world
でアクセスした場合、
Hello World!
と表示されますが、
http://www.xxxx.com/hello/_echo_text
にアクセスしても"404 NOT FOUND"エラーとなります。
ただprivateにするだけではアクションメソッドとの見分けがつきにくいので、privateメソッドの場合は例のように頭に"_"をつけるなどすることをおすすめします。
アクションメソッドの名前規則として接尾語を設定する方法
privateメソッドとする以外にもう一つの方法があります。
デフォルトではアクションのメソッド名はURLで指定されたアクションそのままのメソッド名ですが、コントローラークラス名には"Controller"というお決まりの接尾語をつけるように、アクションメソッド名にもお決まりの接尾語を設定することが出来ます。
例えば接尾語として"Action"を設定するには、curry.iniで設定を行うか、Initializerを利用します。
[dispatch] ;--If you want to modify suffix of action method name, set this value. action_suffix = "Action"
class Initializer extends InitializerStandard { public function initialize() { NameManager::setActionSuffix('Action'); } }
どちらの方法でも構いません。
このように設定した場合、コントローラーは以下のようになります。
class HelloController extends Controller { public function worldAction() { // http://www.xxxx.com/hello/world でのアクセスが可能 $this->_echoText('Hello World!'); } public function world() { // このメソッドにはURLからの直接のアクセスは出来ない } private function _echoText($text) { // このメソッドももちろん外部からのアクセスは出来ない } }
アクションの接尾語として"Action"を設定しているため、単に"world"というメソッドをpublicで定義しても、URLからのアクセスは不可能になります。
ただこの場合でも、可読性の観点からも非アクションメソッドはprivateとして、頭に"_"などをつけるようにするべきです。
ちなみにコントローラーの設備しも同様に設定が可能です。デフォルトは"Controller"です。
共通処理メソッド
コントローラー中の全てのアクションに共通した処理が行いたい場合、非アクションメソッドとして定義し、全アクションメソッドから呼び出すという方法が考えられますが、もっとスマートな方法があります。
メソッド名をpreProcessまたはpostProcessとして定義すると、アクションに関わらず、そのコントローラーでは必ず呼び出されます。
preProcessはアクションメソッドの直前のタイミング、
postProcessはアクションメソッドの直後のタイミングに呼び出されます。
class HelloController extends Controller { public function preProcess() { echo "preProcess called."; } public function postProcess() { echo "postProcess called."; exit; } public function world() { echo "Hello World!"; } }
preProcess called. Hello World! postProcess called.
他のアクション・コントローラーへの処理委譲
あるアクションへアクセスがあった場合に、URL上はアクセスしたURLのまま、処理は別のアクションやコントローラーで定義された処理を行うことが可能です。
それにはtransferメソッドを利用します。
同コントローラー内の別アクションへの委譲
transferメソッドの第一引数に委譲先アクションを指定することで、処理を別のアクションのアクションを委譲することが出来ます。アクションメソッドより手前でtransferを呼び出した場合、アクションメソッドは実行されません。
class HelloController extends Controller { public function index() { $this->transfer('world'); } public function world() { echo "Hello World!"; exit; } }
この場合、
http://www.xxxx.com/hello/world
と、
http://www.xxxx.com/hello
のアクセスは同じ結果となります。
デフォルトの処理をindex以外のアクションとして定義したい場合にこの例のような方法で実現が可能です。
ここで注意することは、あくまでURLで指定するのと同様のアクション名であって、メソッド名を直接指定するのではないということです。
public function index()
{
$this->transfer('helloWorld'); // これは間違い
$this->transfer('hello_world'); // これが正しい
}
public function helloWorld()
{
echo "Hello World!";
exit;
}
別コントローラーのアクションへの委譲
別のコントローラークラスのアクションへの委譲も可能です。
それには第二引数にそのコントローラー名を指定します。
class Hello2Controller extends Controller { public function index() { $this->transfer('world', 'hello'); } } class HelloController extends Controller { public function world() { echo "Hello World!"; exit; } }
例のような二つのコントローラーが存在する状態で、
http://www.xxxx.com/hello2
へアクセスすると、
Hello World!
となります。
前述のとおり、URLで呼び出される予定のアクションの実行前にtransferが行われると、アクションメソッドは実行されません。移譲先のアクションの実行をもってアクションの実行完了とみなすからです。
以下の例ではpreProcessでworldアクションへtransferしているため、このコントローラーではworld以外のアクションメソッドは意味を成しません。
class HelloController extends Controller { public function preProcess() { $this->transfer('world'); } public function japan() { // URLが/hello/japanでもこのメソッドが実行されることはない } public function world() { // preProcessメソッドのtransferによりこのメソッドが実行される } }
サブディレクトリによるコントローラーの階層管理
コントローラークラスの格納ディレクトリには、サブディレクトリを作成してコントローラークラスを階層管理することが可能です。
デフォルトではサブディレクトリが有効になっています。
しかし、サブディレクトリを使用しない事がわかっている場合にはこの機能を無効にすることもできます。無効にしても大きな意味はありませんが、わずかにパフォーマンス的に有利になります。本当に必要ない場合は無効にするとよいでしょう。
無効にするにはcurry.iniで設定をします。
[dispatch] ;--If you want to use controller sub directory, set this flag "1". sub_controller_enabled = 0
または、Initializer::initializeでの設定でも対応可能です。
class Initializer extends InitializerStandard { public function initialize() { $this->router->enableSubController(false); } }
例えばサイト管理者用の管理機能をまとめる"admin"というサブディレクトリを作成した場合のディレクトリ構成の例です。
∟site
∟app
∟controllers
∟admin
∟index_controller.php
∟user_controller.php
∟test_controller.php
サブディレクトリを作成して、一つ深い階層が出来上がります。
階層の深さに制限はありません。
このサブディレクトリ内のコントローラーにアクセスする場合、通常のURL形式とは少し異なり、
http://www.xxxx.com/(サブディレクトリ名)/(コントローラー)/(アクション)
のようにサブディレクトリがURLのパスのトップセグメントになります。
例えば上記のディレクトリ構成の、user_controller.phpにアクセスする場合は、
http://www.xxxx.com/admin/user/edit
というようになります。
この例はadminディレクトリ内のuserコントローラーのeditという名前のアクションを呼び出しています。
サブディレクトリのコントローラーのクラス名は基本的に通常と何も変わりませんが、クラス名の頭に、クラスの名前規則に従う形でサブディレクトリ名を付加しても動作します。
// http://www.xxxx.com/admin/userでアクセス可能 class UserController extends Controller { } // こちらのクラス名でもhttp://www.xxxx.com/admin/userでアクセス可能 class AdminUserController extends Controller { }
クラス名はどちらでもかまいませんが、異なるディレクトリで同じコントローラー名のクラスを作成し、しかも同時に両方のクラスが呼び出されることがあるとしたら、クラス名が同じだと定義済みエラーが発生してしまいます。これを回避したい場合はサブディレクトリ名を頭に付加します。
リクエスト処理
コントローラーの基本的な役割の一つとして$_GETや$_POSTなどのリクエスト情報を直接扱うというものがあります。Curryではこれらのスーパーグローバル変数の直接の参照は推奨されません。かわりにこれらに簡単にアクセスする仕組が用意されています。
リクエスト情報はリクエストオブジェクトに集約され、コントローラークラスのフィールドとして保持しています。そのため、$this->requestでリクエストオブジェクトが取得でき、このリクエストオブジェクトより各種リクエスト情報を取得します。
リクエストオブジェクトより取得できる情報は、
- コントローラー名
- アクション名
- コントローラーサブディレクトリ
- POST
- GET
- URLパラメーター
などです。
これらの実際の取得方法を見てみましょう。
class HelloController extends Controller { public function world() { // リクエストオブジェクト $req = $this->request; // コントローラー名 $controller = $req->getController(); // アクション名 $action = $req->getAction(); // コントローラーサブディレクトリ $subdir = $req->getControllerSubDirectory(); // POST $post = $req->getPost(); // GET $query= $req->getQuery(); // URLパラメーター $params= $req->getparams(); } }
更に、POST、GET、URLパラメーターに関してはもっと簡単な取得方法もあります。
class HelloController extends Controller { public function world() { // $this->postは$this->getRequest()->getPost()と同じ $postVar = $this->post['post_var']; // $this->queryは$this->getRequest()->getQuery()と同じ $getVar= $this->query['get_var']; // $this->paramsは$this->getRequest()->getParams()と同じ $paramVar= $this->params['param_var']; } }
オートトリミング
POSTやGETの値に対して自動的にトリミングを実行することが可能です。
デフォルトでは無効になっているので、有効にするにはcurry.iniで設定するか、Initializer::initializeメソッドなどでRequestクラスの静的メソッド、setAutoTrimにtrueを指定します。
[request] ;If you want to trim the request parameters(POST or GET) automatically, set this flag "1". auto_trim = 1
class Initializer extends InitializerStandard { public function initialize() { Request::setAutoTrim(true); } }
これでRequestクラスを利用してPOSTやGETの値を取得すると、取得値にトリミングが行われた状態での値が取得されるようになります。
セッションの利用
セッションもリクエスト情報と同じくスーパーグローバル変数($_SESSION)の直接の参照は推奨されません。代わりにSessionクラスを利用します。Sessionクラスを利用する場合、明示的にsession_start()関数を呼び出す必要はありません。
Sessionクラスは名前空間という概念を持ち、コンストラクタで引数として指定します。ここで指定した名前空間のセッションならば、別のインスタンスでも同じ値を共有することが出来ます。
class AuthController extends Controller { public function login() { $sess = new Session('auth'); $sess->user_id = 'tarou'; $this->redirect('/cart'); } } class CartController extends Controller { public function index() { $sess = new Session('auth'); // 「tarou」を出力します echo $sess->user_id; } }
例のように明示的にSessionクラスのインスタンスを生成して利用するのが通常の手続きです。
もう一つ別の利用方法もあります。
コントローラークラスには$sessionというフィールドが存在します。これはSessionクラスのインスタンスで、そのコントローラー内でのみ有効なセッションとして利用することができます。
class HelloController extends Controller { public function world() { $this->session->message = 'Hello world!'; $this->redirect('/hello/msg'); } public function msg() { // 「Hello world!」を出力します。 echo $this->session->message; } } class Hello2Controller extends Controller { public function world() { // 何も出力されません echo $this->session->message; } }
例えば認証情報など、コントローラーに関わらずサイト全体で利用するセッションの場合は前者の方法になるでしょう。
しかし例えば入力フォーム→確認表示→登録と言ったような一連の流れをセッションを利用して実現するような場合で、この一連の流れが同じコントローラー内で完結する場合には後者の方法が楽でしょう。
URLパラメーター
URLがコントローラーとアクションで構成される事は前述の通りです。コントローラーとアクションを表すだけであればURLのパスは第2セグメントまでしか使用しないことになりますが、実際は第3セグメント以降はリクエストパラメータとして利用できます。
http://www.xxxx.com/cart/list/tarou/10
class CartController extends Controller { public function products() { $userId= $this->params[0]; $limit = $this->params[1]; // "tarou"が出力されます echo $userId; // "10"が出力されます echo $limit; } }
URLのアクションセグメント以降は前から順番に0,1,2・・・というように数値インデックスをキーとしたパラメータとして取得が可能です。
しかしキーが数値インデックスではソースコードの可読性が落ちるので、名前付きパラメータにも対応しています。それにはいくつか方法があります。一つはURLにキーと値の組み合わせを含める方法です。
http://www.xxxx.com/cart/products/uid=tarou/lmt=10
class CartController extends Controller { public function products() { $userId= $this->params['uid']; $limit = $this->params['lmt']; // "tarou"が出力されます echo $userId; // "10"が出力されます echo $limit; } }
手軽に名前付きパラメータが利用できますが、クエリ文字列と似ていてあまり綺麗とはいえないかもしれません。URLだけで対応するには上記前者の数値インデックスか後者の方法での名前付きパラメータのどちらかになります。
URLだけでは対応できませんが、綺麗なURLで名前付きパラメータを実現する方法があります。それはパラメータの位置とキーを関連付ける情報を設定ファイルによって設定する方法です。それにはURLルーティング設定ファイル"routing.ini"を利用します。
routing.iniを利用すると、URLの各セグメント位置に対するパラメーターのキーを設定可能です。
routing.iniの詳細は下で別途解説しているのでそちらを参照してください。
一つの設定例を以下に示します。
[routing1] request = cart/products/*/* route = controller/action/user_id/limit
http://www.xxxx.com/cart/products/tarou/10
class CartController extends Controller { public function products() { $userId= $this->params['user_id']; $limit = $this->params['limit']; // "tarou"が出力されます echo $userId; // "10"が出力されます echo $limit; } }
プラグイン(コントローラー共通処理)
特定コントローラー内の全てのアクションに対して同じ処理を行いたい場合はpreProcessメソッドなどを利用することで実現が可能ですが、サイト内の全コントローラー共通の処理を行いたい場合というのはあるはずです。
例えば認証処理だとか、カウンターを回す処理などです。コントローラーはそれぞれ独立したクラスのため、これを実現するには全てのコントローラーのpreProcessに同じ処理を記述する必要があるように思われますが、全コントローラー共通処理を実現するプラグインという仕組みがあります。
一般的なフレームワークの場合、コントローラーの共通処理を必要とする場合、
共通の基礎クラスとして、フレームワークのコントローラークラスを継承した基礎コントローラーを定義し、各末端のコントローラーはそれを更に継承するという形をとる事が多くなります。
Curryのプラグインはこのような方法を必要とせずに共通処理を実現する仕組みです。
プラグインはデフォルトで有効になっています。
しかしプラグインの仕組みを利用しない事がわかっている場合は、無効にすることによってわずかですがパフォーマンス的に有利になります。利用しない場合は無効にするとよいでしょう。
無効にするにはcurry.iniで設定するか、Initializerを利用します。
class Initializer extends InitializerStandard { public function initialize() { $this->dispatcher->enablePlugin(false); } }
Pluginクラスを定義します。
class Plugin extends PluginAbstract { public function preProcess() { echo '共通前処理'; } }
プラグインはクラスですが、動作させるにはルールがあります。
まず、必ずPluginAbstractクラスを継承します。そしてクラス名は必ず"Plugin"でなければなりません。これをどう利用するかと言うと、コントローラーディレクトリ内に配置するだけです。
∟site
∟app
∟controllers
∟cart_controller.php
∟index_controller.php
∟plugin.php
こうすることで、コントローラーディレクトリ内の全てのコントローラーに関連付き、自動的にPluginクラスのメソッドが実行されます。
Pluginクラスの中でも自動的に実行されるメソッドは決まっています。基本的にはpreProcessとpostProcessですが、コントローラーの同名メソッドとは別のメソッドです。実行されるタイミングは以下の順になります。
1、 プラグインのpreProcess()
2、 コントローラーのpreProcess()
3、 コントローラーのアクションメソッド
4、 コントローラーのpostProcess()
5、 プラグインのpostProcess()
Pluginクラスはいくつかのコントローラーと同様のメソッドとフィールドを持ちます。
class Plugin extends PluginAbstract { public function preProcess() { // コントローラーと同一インスタンスのビューオブジェクト $view = $this->view; // コントローラーと同一インスタンスのリクエストオブジェクト $req = $this->request; // コントローラーのものと全く同じ動作 $model = $this->model('ModelName'); // コントローラーのものと全く同じ動作 $this->redirect('/'); } }
コントローラーにサブディレクトリが存在する場合、Pluginはサブディレクトリ内のコントローラーに対しても働きます。Pluginの影響範囲はPluginの存在するディレクトリを含め、下層全体です。しかし、もしサブディレクトリ内にもPluginが定義されている場合、サブディレクトリ内のコントローラーに対してはそちらが優先されます。下層のPluginが上層のコントローラーに対して働くことはありません。
∟site/
∟app/
∟controllers/
∟admin/
∟index_controller.php
∟plugin.php
∟my/
∟index_controller.php
∟cart_controller.php
∟index_controller.php
∟plugin.php
例えば上記のディレクトリ構成の場合、controllers直下のindex_controllerに対してはcontrollers直下のpluginが働きます。
admin/index_controllerに対してはadmin/pluginが働きます。
my/index_controllerに対してはcontrollers直下のpluginが働きます。
呼び出し型メソッドの作成
コントローラークラスはpluginのインスタンスを保持しています。
Pluginクラスで任意のメソッドを定義し、すべてのコントローラークラスから共通で利用できるメソッドとして利用することが可能です。
class Plugin extends PluginAbstract { public function logout() { } }
Pluginクラスに任意のメソッドを作成します。
ここでは共通のログアウト処理としてlogoutというメソッドを定義しました。
class CartController extends Controller { public function logout() { $this->plugin->logout(); } }
Pluginクラスのインスタンスはコントローラーから$this->pluginとしてアクセス可能です。
Pluginに定義したメソッドは、このインスタンスを通して実行が可能です。
URLルーティングの設定
configsディレクトリにrouting.iniファイルを配置することで、URLからコントローラー・アクションへ導くルールを設定することができます。また、パラメーターに対して任意のキーを設定することも可能です。
#http://www.xxxx.com/products/cartというリクエストの場合に、 #cartをコントローラー、productsをアクションとして処理したい場合。 [routeing0] request = products/cart route = action/controller #http://www.xxxx.com/cart/tarou/productsというリクエストの場合に、 #cartをコントローラー、productsをアクションとし、第2セグメントをuser_idというキーでパラメーター取得する場合。 [routeing1] request = cart/*/products route = controller/user_id/action #特定のコントローラーのデフォルトアクションを設定したい場合は以下のようにする。 [routing2] request = cart default_action = products #以下のようにするとサイトのトップページがcartコントローラーのproductsアクションとなる [routing3] request = * default_controller = cart default_action = products #強制的に特定のコントローラーとして処理したい場合は以下のように設定。 #以下の例はURLでパス未指定の場合、つまりトップページをcartコントローラーとする設定。 [routing4] request = controller = cart #requestはカンマ区切りで復数指定可能 [routeing5] request = cart/*/products, user/*/view route = controller/user_id/action #実際にはadmin/editは第3セグメントをパラメータとして取得するが、 未指定の場合のデフォルトを設定しておき、第3セグメント省略でもアクセス可能にする設定 [routeing6] request = admin/edit route = controller/action edit_controller=product --------------------------------------------------------- #以下の設定はデフォルトであり、何も設定しないのと同じ事になる。 request = * route = controller/action default_controller = index default_action = index
設定の基本
例では[routing0]などととしていますが、セクション名は何でもかまいません。動作にはまったく影響ありません。人間が見て分かりやすいネーミングにするのが良いでしょう。
設定はrequestとrouteというキーで行います。requestの設定値に対して、リクエストされたURLが前方一致する場合のみ、そのURLの各セグメントはrouteで設定された順番でキーが指定されているものとして処理します。
例えばrouting1の例では第1セグメントがcart、第3セグメントがproductsの場合のみ、第1セグメントをコントローラー、第3セグメントをアクションとして処理します。つまりCartControllerのproductsメソッドが呼び出されるわけです。そしてその場合に第2セクションはuser_idというキーでパラメーターとして処理されます。requestの「*」は、その位置には何が来てもいいという意味で、パラメーターの位置には基本的に「*」を指定します。
requestの設定はカンマ区切りで復数指定することも可能です(バージョン1.4.6以降)。
デフォルトコントローラー・アクションの設定
通常はデフォルトのコントローラーとアクションはindexですが、default_controllerやdefault_actionを設定すると、requestの条件に合致する場合のコントローラーやアクションのデフォルトを設定することが出来ます。
これは、routeの設定に対してリクエストが欠損している場合に有効となる設定です。例えばrouteの設定が"controller/action"の場合、上の例のようにURLリクエストがコントローラーのみの指定の場合に適用されます。URLでアクションが指定される場合は適用されません。
サイト全体のデフォルトコントローラーやデフォルトアクションを設定するには、DispatcherクラスのsetDefaultControllerメソッドやsetDefaultActionメソッドでも可能ですが、routing.iniによる設定は、Dispatcherクラスでの設定よりも優先されます。Dispatcherクラスでの設定はあくまでもrouting.iniで指定されない場合のデフォルトの設定と考えてください。
強制転送コントローラー、アクション(ver.1.4.6以降)
controllerまたはactionというキーに対して任意のコントローラーやアクションを設定すると、URLがrequestに合致した場合に強制的にコントローラーやアクションを置き換えることが可能です。
設定の優先度
また、routing.iniに複数の設定を記述した場合、上から順番にrequestの設定とURLの比較を行います。そして最初に合致した設定にしたがってルーティングが行われ、以降の設定は無視されます。複数の設定に合致するリクエストでも、より上に記述された物が優先されるということです。