Zend Frameworkにおけるグローバル変数

Posted by Hiraku on 2011-08-14

Zend Frameworkを久しぶりに使っているのですが、変数を共有する方法についてよく忘れるのでメモです。

Zend Framework 1.11.2(FreeBSDのportで入れた最新版)をベースに書いています。コマンドのバグ情報などは少し古いかもしれません。

おさらい

Zend Frameworkをベースにしたアプリケーションは、こんな構造をしています。

ZF_values.png

どのレイヤーで共有するかによって、適する書き方が変わります。

PHPプログラム全域⇒グローバル変数

ZF_values_globals.png

グローバル変数はもっともスコープが広く、そして最悪な方法です。

//代入
$GLOBALS['hoge'] = 'hoge';

//参照
echo $GLOBALS['hoge'];

後で述べる方法と比べると、使った後のプログラムはもちろん、使った行以前のプログラムにすら影響を与える点が大きな特徴です。

また、PHP特有の問題かもしれませんが、グローバル変数は書き方がいろいろあります。

これに加えてローカル変数の$hogeと見分ける必要があるなど、とにかくグローバル変数をソースコードから検索するのは大変な作業になりがちです。

せっかくZend Frameworkを使っているのなら、変数を共有する方法が他にもたくさんあるので、別のやり方を検討するべきだと思います。

ZFアプリ全域⇒Zend_Registry

ZF_values_registry.png

次善の策。Zend Framework公式で、グローバル変数を安全に使うためのコンポーネントとしてZend_Registryがあります。これも、どこでも読み書きできますが、文脈非依存で書き方が統一される分、使用箇所の検索もしやすく、まだマシになります。

また、Zend Frameworkに依存する書き方になるため、ZFを使っていないコードには影響を与えません。この意味で、グローバル変数よりもスコープが狭くなったと言えます。

//値を設定
Zend_Registry::set('foo', 1234);

//値を取得
$foo = Zend_Registry::get('foo');

application全域⇒Bootstrap_Bootstrap

ZF_values_bootstrap.png

Zend_Applicationをベースに作ったアプリは、フロントコントローラーの他にZend_Application_Bootstrap_Bootstrapという長ったらしい名前のクラスを継承したBootstrapというクラスを持っています。(zfコマンドが自動的に生成します)

これはアプリケーションの初期化処理や共通処理などを書く場所です。このクラスにinitメソッドを書いていくことで、アプリケーション内で共有する変数やインスタンスなどを管理できます。一種のDI(dependency injection)コンテナです。

BootstrapはデフォルトではDbアダプタやコントローラーの設定など、大きなインスタンスの管理に使われていますが、別に文字列だろうと数字だろうと突っ込むことが可能です。

// application/Bootstrap.php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
  // 「_init」+「リソース名」
  // という名前でメソッドを作ると
  // 戻り値が「リソース名」に紐づけられる
  protected function _initHoge
  {
    return 'hoge123';
  }
}
echo $bootstrap->getResource('hoge'); //小文字なので注意
 // hoge123

// ------- $bootstrapの取得方法いろいろ --------
// 1. アクションコントローラー内なら
//  $this->getInvokeArg('bootstrap')
class HogeController extends Zend_Controller_Action
{
  public function hogeAction()
  {
    $bootstrap = $this->getInvokeArg('bootstrap');
    // ...
  }
}

// 2. これ以外の場面ではインスタンスを直接渡しておく
class FooModel {}
// ...
$foo = new FooModel();
$foo->setContainer($bootstrap);

正直、ちょっと面倒くさいインターフェースですね。文字列なんかをいちいち持っていたら大変なので、設定や定数などはオブジェクトにまとめておき、そのオブジェクトだけ持ちまわすのがいいかもしれません。

ちなみに、不便だからと言ってZend_Registry::set('bootstrap', $bootstrap); みたいなことをやってしまうと、せっかくの絞ったスコープが広くなってしまうので、あまり感心しません。

module全域⇒Module_Bootstrap

ZF_values_module.png

更にスコープを絞って、あるモジュール内のみで共有したいものを集めることもできます。それがZend_Application_Module_Bootstrapです。 zfコマンドでもうまく作ってくれず(そのうち直ると思いますが)、更にややこしいインターフェースを持ちますが、Zend Frameworkを使いこなすために、なくてはならないものです。

まずapplication/config/application.iniでモジュールリソースを有効にします。

resources.modules[] =

次に、Module_Bootstrapを作ります。中身はほとんどBootstrap_Bootstrapと同じです。継承するクラスが先ほどと違うので注意です。

// hogeモジュールのbootstrap
class Hoge_Bootstrap extends Zend_Application_Module_Bootstrap
{
  protected function _initMoge()
  {
    return 'moge123';
  }
}

参照するにはまずBootstrap_Bootstrapのインスタンスを取得し、modulesリソースを経由して取得します。


$hogeBS = $bootstrap->getResource('modules')->hoge; // hogeは小文字
echo $hogeBS->getResource('moge');
// moge123

かなり面倒なインターフェースですね。。もう少し楽なメソッドがZend_Controller_Actionに追加されるといいんですが。

参考:Zend_Application(4) モジュール別ブートストラップを使う - noopな日々

ActionController全域⇒クラス変数

action全域⇒ローカル変数

ZF_values_controller.png ZF_values_action.png

これ以降は説明の必要もないでしょう。Zend Frameworkでは、ActionControllerは複数のactionメソッドを集めた一つのクラスとして記述します。そのため、クラス内で共有したいものはクラス変数に、メソッドの中でのみ共有したい変数はローカル変数として書けばいいのです。

その他の共有方法

ZFをベースにしたアプリケーションはツリー状の構造になるため、枝の根っこの部分で何かを共有するのがわかりやすい方法です。

しかしこの方法ではうまくいかないケースもあるでしょう。たとえばとても重くてたまにしか使わないけど、複数個所で使う処理とか。Bootstrapなどで初期化してしまうと、関係ない場面でも初期化処理が走ってしまい、無駄になります。

このようなケースではヘルパープラグインといった機構が役に立つと思います。ここでは詳細は割愛します。

まとめ

Zend Frameworkのコントローラーは4階層に分けることができます。

  • application
  • module
  • action controller
  • action

それぞれの階層ごとに必要な初期化処理を書いておいて、無駄に何度もコードを書かなくてよくするのがエレガントです。Registryやグローバル変数の安易な多用は厳禁です。

原則的に、コントローラーをこれ以上細かく分割することはできません。actionを別のactionに委譲することはできますが、同階層内の話であり、階層が増えたわけではありません。

無理に分割するなら、何らかのカスタマイズが必要になってしまいます。カスタマイズなしで頑張るなら、あらかじめmoduleやaction controllerの階層を細かく分割しておき、actionのレイヤーに複数処理が集まらないように注意するべきだと思います。



keyword: PHP zendframework controller

PHPの最新記事