WEBサービス創造記

WEBサービスを作ったり保守したりしてる人のメモブログです。

SmartyをZendFrameworkのビューとして利用する(モジュール化対応版)

      2012/12/17

ディレクトリ構成

PHPのテンプレートエンジンSmartyを、ZendFrameworkのビューとして利用し、なおかつフレームワーク側のモジュール化機能にも対応させるための手順です。

この手法では、以下のようなディレクトリ構成をとります。
ZendFrameworkとSmartyがインストールされていてパスが通っていることが前提となっています。

# tree
.
|-- application
|   |-- modules
|   |   |-- default
|   |   |   |-- controllers
|   |   |   |   `-- IndexController.php
|   |   |   `-- templates
|   |   |       `-- index
|   |   |           `-- index.tpl
|   |   `-- test
|   |       |-- controllers
|   |       |   `-- TestController.php
|   |       `-- templates
|   |           `-- test
|   |               `-- test.tpl
|   `-- plugins
|       `-- SmartyPlugin.class.php
|-- library
|   `-- Zend_View_Smarty.class.php
|-- public
|   `-- index.php
`-- smarty
    |-- cache        777
    |-- configs
    `-- templates_c  777

smarty/cache と smarty/templates_c は共にパーミッションを777に設定してください。

Smartyラッパークラスの作成

ここではZendFrameworkの公式ドキュメントにあるラッパークラスをそのまま利用します。
以下転載となります。

library/Zend_View_Smarty.class.php
<?php echo '<?php'; ?>

class Zend_View_Smarty implements Zend_View_Interface
{
  /**
   * Smarty object
   * @var Smarty
   */
  protected $_smarty;

  /**
   * コンストラクタ
   *
   * @param string $tmplPath
   * @param array $extraParams
   * @return void
   */
  public function __construct($tmplPath = null, $extraParams = array())
  {
      $this->_smarty = new Smarty;

      if (null !== $tmplPath) {
          $this->setScriptPath($tmplPath);
      }

      foreach ($extraParams as $key => $value) {
          $this->_smarty->$key = $value;
      }
  }

  /**
   * テンプレートエンジンオブジェクトを返します
   *
   * @return Smarty
   */
  public function getEngine()
  {
      return $this->_smarty;
  }

  /**
   * テンプレートへのパスを設定します
   *
   * @param string $path パスとして設定するディレクトリ
   * @return void
   */
  public function setScriptPath($path)
  {
      if (is_readable($path)) {
          $this->_smarty->template_dir = $path;
          return;
      }

      throw new Exception('無効なパスが指定されました');
  }

  /**
   * 現在のテンプレートディレクトリを取得します
   *
   * @return string
   */
  public function getScriptPaths()
  {
      return array($this->_smarty->template_dir);
  }

  /**
   * setScriptPath へのエイリアス
   *
   * @param string $path
   * @param string $prefix Unused
   * @return void
   */
  public function setBasePath($path, $prefix = 'Zend_View')
  {
      return $this->setScriptPath($path);
  }

  /**
   * setScriptPath へのエイリアス
   *
   * @param string $path
   * @param string $prefix Unused
   * @return void
   */
  public function addBasePath($path, $prefix = 'Zend_View')
  {
      return $this->setScriptPath($path);
  }

  /**
   * 変数をテンプレートに代入します
   *
   * @param string $key 変数名
   * @param mixed $val 変数の値
   * @return void
   */
  public function __set($key, $val)
  {
      $this->_smarty->assign($key, $val);
  }

  /**
   * empty() や isset() のテストが動作するようにします
   *
   * @param string $key
   * @return boolean
   */
  public function __isset($key)
  {
      return (null !== $this->_smarty->get_template_vars($key));
  }

  /**
   * オブジェクトのプロパティに対して unset() が動作するようにします
   *
   * @param string $key
   * @return void
   */
  public function __unset($key)
  {
      $this->_smarty->clear_assign($key);
  }

  /**
   * 変数をテンプレートに代入します
   *
   * 指定したキーを指定した値に設定します。あるいは、
   * キー => 値 形式の配列で一括設定します
   *
   * @see __set()
   * @param string|array $spec 使用する代入方式 (キー、あるいは キー => 値 の配列)
   * @param mixed $value (オプション) 名前を指定して代入する場合は、ここで値を指定します
   * @return void
   */
  public function assign($spec, $value = null)
  {
      if (is_array($spec)) {
          $this->_smarty->assign($spec);
          return;
      }

      $this->_smarty->assign($spec, $value);
  }

  /**
   * 代入済みのすべての変数を削除します
   *
   * Zend_View に {@link assign()} やプロパティ
   * ({@link __get()}/{@link __set()}) で代入された変数をすべて削除します
   *
   * @return void
   */
  public function clearVars()
  {
      $this->_smarty->clear_all_assign();
  }

  /**
   * テンプレートを処理し、結果を出力します
   *
   * @param string $name 処理するテンプレート
   * @return string 出力結果
   */
  public function render($name)
  {
      return $this->_smarty->fetch($name);
  }
}

Zend Framework: Documentation: ビュースクリプト – Zend Framework Manual

Zend_View互換クラスを定義する場合は、Zend_View_Interfaceを実装する必要があり、上記ラッパークラスもこれを実装しています。
Zend_View_Interface実装クラスで定義すべきメソッドに関しては以下の旧ページで解説しておりますので、必要に応じて参考にしていただければと思います。
Smarty を ZendFramework の View として利用する

フロントコントローラでの処理

フロントコントローラでビューに関する設定を行なっている箇所を以下に抜粋します。

public/index.php(抜粋)
define('ROOT_PATH', '/path/to/webapp/');
define('APP_PATH', ROOT_PATH . 'application/');
define('LIB_PATH', ROOT_PATH . 'library/');
define('SMARTY_PATH', ROOT_PATH . 'smarty/');

require_once ('libs/Smarty.class.php');    // Smarty本体を読み込み
require_once (LIB_PATH . 'Zend_View_Smarty.class.php');    // ラッパークラスを読み込み

$front = Zend_Controller_Front::getInstance();
$front->addModuleDirectory(APP_PATH . 'modules');    // モジュールディレクトリ指定

$view = new Zend_View_Smarty(
    null, array(
        'compile_dir' => SMARTY_PATH . 'templates_c',
        'config_dir'  => SMARTY_PATH . 'configs',
        'cache_dir'   => SMARTY_PATH . 'cache',
    )
);
$render = new Zend_Controller_Action_Helper_ViewRenderer($view);
$render->setViewBasePathSpec(APP_PATH . 'modules/:module');
$render->setViewScriptPathSpec(APP_PATH . 'modules/:module/templates/:controller/:action.:suffix');
$render->setViewScriptPathNoControllerSpec(':action.:suffix');
$render->setViewSuffix('tpl');
Zend_Controller_Action_HelperBroker::addHelper($render);

[A-Z]+_PATHといったような定数部分がありますが、それはディレクトリ構成を考慮して適宜設定するようにします。
例えば、このページで採用しているディレクトリ構成の場合、SMARTY_PATHはpublicの一つ上の”../smarty”ディレクトリとなります。

スクリプトの実行

ラッパークラスを作成し、フロントコントローラに上記処理を組み込めば、Smartyを利用したレンダリングが可能となっているはずです。

デフォルトモジュールにアクションコントローラを、テンプレートディレクトリにビュースクリプトをそれぞれ設置してみて動作を確認します。

application/modules/default/controllers/IndexController.php
<?php echo '<?php'; ?>

require_once 'Zend/Controller/Action.php'; 
 
class IndexController extends Zend_Controller_Action{ 
    public function indexAction() { 
        $this->view->assign('result', 'Sumarty is running!'); 
    }   
} 
application/modules/default/templates/index/index.tpl
<!DOCTYPE HTML> 
<html lang="ja"> 
<head> 
>---<meta charset="UTF-8"> 
>---<title>Smarty Test</title> 
</head> 
<body> 
>---{$result} 
</body> 
</html>  

ブラウザからアクセスすると、”Sumarty is running!”と表示されるはずです。

ただし、この段階では完全にモジュール化に対応できたわけではありません。

Smartyコンパイルファイルのユニーク化

モジュールを分けている場合には、Smartyのcompile_idなどを使ってコンパイルファイルを変更する必要があります。
対策をしていないと別のモジュールに同じコントローラ・アクションがあった場合には、そっちのテンプレートコンパイルファイルを読んだりします。

Smartyを利用する方法 – [Zend Framework] ぺんたん info

この例のディレクトリ構成では、全てのモジュールのコンパイルファイルを一箇所にまとめていますので、上記引用で触れられているように、Smartyクラスのcompile_idメソッドを利用して重複しないように設定する必要があります。

ここでは、プラグインを利用してモジュール毎にコンパイルファイルの命名を重複しない処理を行っています。
application/pluginsディレクトリに以下のプラグインを設置し、フロントコントローラで登録します。

application/plugins/SmartyPlugin.class.php
<?php echo '<?php'; ?>

require_once 'Zend/Controller/Plugin/Abstract.php'; 
 
class SmartyPlugin extends Zend_Controller_Plugin_Abstract { 
    public function dispatchLoopStartup($req) { 
        global $view; 
 
        $module  = $req->getModuleName(); 
        $controller  = $req->getControllerName(); 
        $action  = $req->getActionName(); 
 
        $smarty = $view->getEngine(); 
        $smarty->compile_id = $module . '_' . $action . '_' . $controller;
 
    }   
} 
public/index.php(抜粋)
require_once APP_PATH . 'plugins/SmartyPlugin.class.php';
$front->registerPlugin(new SmartyPlugin()); 

この段階で、他のモジュールに先の例のようなアクションコントローラを設置してみてアクセスしてみると、正常にレンダリングできると思います。
また、コンパイルファイルの格納ディレクトリには、以下のような形でコンパイルファイルが生成されるはずです。

smarty
    |-- cache
    |-- configs
    `-- templates_c
        |-- default_index_index^d5324d1c12e636e6195c8d27eee31dc3b1cd159a.file.index.tpl.php
        `-- test_test_test^5a675c4b01f48d67fa18d4808b6353fb99b374c6.file.test.tpl.php

以上でひと通り設定完了です。

要点・注意点など

  • Smartyのコンパイルファイルをひとつのモジュールにまとめるときは、compile_idを適切に割り当てて重複しないようにすること
  • このページではSmartyをZendFrameworkのビューとして採用し、モジュール化にも対応するための最低限の設定しか行っていません。実際に利用する際はビュー変数のエスケープ処理などを適宜行って下さい。
  • このページの手順では、Smarty3.0.6でテンプレート変数に smarty:nodefaults 修飾子をかけることができないことを確認しました。Smarty2.6.26では正常に smarty:nodefaults 修飾子が動作することを確認しております。

 - ZendFramework , , , , ,