add to hatena hatena.comment (4) add to del.icio.us (0) add to livedoor.clip (0) add to Yahoo!Bookmark (0) Total: 4

Symfony TemplatingをCakePHPで使ってみる

CakePHPでSymfony Templatingを使ってみる

前回はSymfony Templatingを単独で利用したり、Smartyと連動させてみました。
次に何をしようかと思い、現在のsymfonyで組み込んでみようかと思いましたが、すでにOpenPNEでおなじみの手嶋屋の海老原さんがブログで実践されていました。
というわけで、あのCakePHPのViewとしてSymfony Templatingを使ってみたらどうだろうと思い試してみました。

ただし、CakePHPのViewを完全に置き換えているわけではありません。
とりあえずCakePHPのViewとして動作させることができたという感じです。

CakePHPでSymfony Templatingを使えると何がうれしいか

CakePHPのView機能で十分な場合はSymfony Templatingを使うメリットはありません。
しかしページ数が多い場合などではSymfony Templatingのslot機能やレイアウトの継承機能が使えると便利ですよね。
そういうわけで、今回実際にできたことは以下のような機能です。

  • レイアウトを複数継承できる
  • Slotで継承元のコンテンツを書き換える
  • テンプレート内で継承元のレイアウトを変更できるようにする
  • 他のテンプレートを読み込み表示することができる

ただし、4つ目の他のテンプレートの読み込み表示については、CakePHPでもエレメントやincludeを利用すればできると思うのでそれほどメリットはないかもしれません。

実現できなかった(していない)点

今回の実装方法は、かなり強引な手段になってしまっています。
そのため、以下の点は今回では考慮していなかったり、実現できていない点です。

  • エレメントはCakePHPの標準Viewで処理される(機能を置き換えることがかなり大掛かりになりそうだったので見送り)
  • キャッシュ処理は行わない(できそうな気もしますが今回は見送り)
  • CakePHPのレイアウト機能(var $layout="hoge")とSymfony Templatingのextendを同じテンプレートで同時に使えない(複数処理されてしまうため)
  • PHP4対応

CakePHPのコアライブラリにあるViewクラスのrenderメソッドだけを上書きしたStViewクラスを用意しました。
renderメソッドも書き換えずにどうにかできないかと考えてはみたのですが、かなり複雑になるようでしたので今回はrenderメソッドを書き換えて実現できる範囲でやってみました。

最終的なテンプレート処理イメージ

というわけで、以下が実際に完成したスクリーンショットと、その継承イメージになります。今回使ったCakePHPのバージョンは1.2.5です。

  • 広告ブロッグなしの画面



テンプレートは3階層で構成されています。基礎となる画面全体のレイアウトと、bodyコンテンツ部分の青色の枠のレイアウトと、テンプレートで表示している緑色の枠の部分です。CakeDebug部分は一番最初の基礎レイアウトにて表示するようにしてあるので、青枠の外で表示されているのがわかります。

  • 広告ブロッグ表示ありの画面



さきほどと違う点は、広告表示(赤色の枠)があるということです。ただし、この広告枠の内容は緑枠の外にありますが、slot機能で緑枠を表示するテンプレートから制御しています。

では、3階層からなるテンプレートの構造を図で表してみます。

  • 各テンプレート(レイアウト)の継承関係とSlotの利用方法


まず、全体の基礎となるレイアウトをst_basic.ctpという名前で作成しました。
その中でBODYタグ内のメインとなるブロック(青色枠)をst_default.ctpというレイアウトに分割しました。また、広告がある場合のために広告用のst_default_with_ad.ctpというレイアウトを用意しました。
そして、これらのst_defaultレイアウト内にテンプレートで表示する領域(緑色枠)を作成してあります。
最後にテンプレートであるindex.ctpからst_defaultかst_default_with_adのどちらかを継承元として指定し上記のスクリーンショットのような画面を表示しています。

また、index.ctpからはst_basicレイアウトに定義してあるtitleタグの内容や、st_defaultレイアウトに定義してある広告領域をslotで書き換えています。

実装方法

Symfony TemplatingをVendorsに配置する

Symfony Templatingのインストール方法は公式サイトのINSTALLATIONを参考にしてみてください。とはいっても、アーカイブをダウンロードしてきて展開するだけです。
今回はCakePHPなので定石どおりにvendors/symfony-templatingというディレクトリを作成し、そこにtarかzipをダウンロードしてきて展開したものを放り込んでおきます。

StViewクラスを用意する

今回のコアとなる拡張クラスです。Viewクラスを親クラスとしrenderメソッドを上書きしたStViewクラスと、Symfony Templatingのテンプレートエンジンを利用するためのmySymfonyTemplatingクラスを作ります。
今回はとりあえず試してみることが優先課題ですので、1ファイル(st.php)にこの2つのクラスを定義しています。

st.phpというファイル名はCakePHPのViewを拡張するための規則にのっとったファイル名になっています。

  • app/views/st.php
PHP:
  1. <?php
  2. /**
  3. * mySymfonyTemplating class
  4. *
  5. * my symfony templating class with CakePHP
  6. */
  7. require_once ROOT . '/vendors/symfony-templating/lib/sfTemplateAutoloader.php';
  8. sfTemplateAutoloader::register();
  9.  
  10. class mySymfonyTemplating {
  11.   protected
  12.     $loader = null,
  13.     $engine = null;
  14.   public function __construct()
  15.   {
  16.     // symfony templating
  17.     $this->setLoader(new sfTemplateLoaderFilesystem(array(ROOT. '/app/views/%name%.ctp')));
  18.     $this->setEngine(new sfTemplateEngine($this->loader,array('php' => new sfTemplateRendererPhp())));
  19.     // set charset for escape
  20.     $this->engine->setCharset('UTF-8');
  21.   }
  22.   public static function create()
  23.   {
  24.     return new self;
  25.   }
  26.   public function setLoader($loader)
  27.   {
  28.     $this->loader = $loader;
  29.   }
  30.   public function getLoader()
  31.   {
  32.     return $this->loader;
  33.   }
  34.   public function setEngine($engine)
  35.   {
  36.     $this->engine = $engine;
  37.   }
  38.   public function getEngine()
  39.   {
  40.     return $this->engine;
  41.   }
  42. }
  43. /**
  44. * StView class
  45. *
  46. * CakePHP View class with Symfony Templating
  47. */
  48. class StView extends View {
  49.   public $ST = null;
  50.   public function __construct(&$controller, $register = true)
  51.   {
  52.     parent::__construct($controller, $register);
  53.     $this->ST= mySymfonyTemplating::create();
  54.   }
  55.   function render($action = null, $layout = null, $file = null)
  56.   {
  57.     // for cake helpers
  58.     $loadedHelpers = array();
  59.     if ($this->helpers != false) {
  60.       $loadedHelpers = $this->_loadHelpers($loadedHelpers, $this->helpers);
  61.     }
  62.     foreach (array_keys($loadedHelpers) as $helper) {
  63.       $camelBackedHelper = Inflector::variable($helper);
  64.       ${$camelBackedHelper} =& $loadedHelpers[$helper];
  65.       $this->loaded[$camelBackedHelper] =& ${$camelBackedHelper};
  66.     }
  67.  
  68.     // for cake layout
  69.     $debug = '';
  70.     if (isset($this->viewVars['cakeDebug']) && Configure::read()> 2) {
  71.       $params = array('controller' => $this->viewVars['cakeDebug']);
  72.       $debug = View::element('dump', $params, false);
  73.       unset($this->viewVars['cakeDebug']);
  74.     }
  75.     if ($this->pageTitle !== false) {
  76.       $pageTitle = $this->pageTitle;
  77.     } else {
  78.       $pageTitle = Inflector::humanize($this->viewPath);
  79.     }
  80.     $data_for_layout = array_merge($this->viewVars, array(
  81.                                                           'title_for_layout' => $pageTitle,
  82.                                                           'content_for_layout' => '', /* at this point, content is not rendered, so set empty */
  83.                                                           'scripts_for_layout' => join("\n\t", $this->__scripts),
  84.                                                           'cakeDebug' => $debug
  85.                                                           ));
  86.  
  87.     // set Symfony Templating
  88.     $this->set('st', $this->ST->getEngine());
  89.     $view_path = str_replace(dirname(__FILE__), '', $this->_getViewFileName($action));
  90.     $view_file = substr($view_path, 0, strrpos($view_path, "."));
  91.     // render by Symfony Templating
  92.     $view_parameters = array_merge($this->viewVars, get_class_vars(__CLASS__), $this->loaded, $data_for_layout);
  93.     $content$this->ST->getEngine()->render('php:' . $view_file, $view_parameters);
  94.     // for $layout or $this->layout is set
  95.     if ($this->layout) {
  96.       $this->stack[] = $this->ST->getEngine()->get('content');
  97.       $this->ST->getEngine()->set('content', $content);
  98.       $content = $this->ST->getEngine()->render('php:/layouts/' . $this->layout, $view_parameters);
  99.     }
  100.     return $content;
  101.   }
  102. }

mySymfonyTemplatingクラスはコンストラクタでSymfony Templatingのengineを作成する定常処理を行っています。
サンプルなので、パスなどはハードコーディングになっていますが、その分わかりやすいと思います。

また、Symfony Templatingのヘルパー機能はCakePHPのヘルパー機能で十分だと思うので、Symfony Templatingのヘルパーはアサインしていません。

StViewクラスのrenderメソッドの中で行っていることのほとんどは、CakePHPのテンプレート内で利用できる変数をSymfony Templatingのrenderメソッドで利用できるようにするための準備で、親クラスのrenderメソッド、_renderメソッド、renderLayoutメソッドを参考にしました。

そして、テンプレートでSymfony Templatingのengineを$stという名前で利用できるようにセットしています。

また、最後にCakePHPのレイアウト機能をSymfony Templatingで実現するために、レイアウト部分のみ再度Symfony Templatingでrender処理しています。

コードを見るとややこしそうですが、やっていることは意外と単純です。これで、Viewクラスを連動させる準備はできました。

レイアウトを用意する

最初に示した構成になるように、app/viewsに追加するファイルは以下のようになります。
また、今回検証で利用するコントローラーはsampleという名前にしました。

CODE:
  1. app/views/
  2. |-- layouts
  3. |   |-- st_basic.ctp           // 大本となるレイアウト
  4. |   |-- st_default.ctp         // 広告無しのレイアウト(extend st_basic.ctp)
  5. |   `-- st_default_with_ad.ctp // 広告有りのレイアウト(extend st_basic.ctp)
  6. |-- parts
  7. |   `-- news.ctp // ヘッドラインニュース表示用テンプレート
  8. |-- sample
  9. |   `-- index.ctp // テンプレート
  10. `-- st.php // StViewクラスとmySymfonyTemplatingクラス

では、順番に

  • layouts/st_basic.ctp
PHP:
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2. <html xmlns="http://www.w3.org/1999/xhtml">
  3. <head>
  4.     <?php echo $html->charset(); ?>
  5.     <title>
  6. <?php $st->output('title') ?>
  7.     </title>
  8.     <?php
  9.         echo $html->meta('icon');
  10.         echo $html->css('cake.generic');
  11.         echo $scripts_for_layout;
  12.     ?>
  13.  
  14. </head>
  15. <body>
  16.     <div id="container" style="border: 3px solid blue;">
  17.         <?php $st->output('content') ?>
  18.     </div>
  19.     <?php echo $cakeDebug; ?>
  20. </body>
  21. </html>

異なる点は2カ所です。slotでタイトルを変更できるようにtitleスロットを出力(output)している点と、このレイアウトを継承したテンプレートを表示する部分にcontentスロットを出力している点です。
header部分などCakePHPのHTMLヘルパーなどがそのまま使えていることがわかります。

  • layouts/st_default.ctp
PHP:
  1. <?php $st->extend('layouts/st_basic') ?>
  2.  
  3. <div id="header">
  4.     <h1><?php echo $html->link(__('CakePHP: the rapid development php framework', true), 'http://cakephp.org'); ?></h1>
  5. </div>
  6.  
  7. <div id="content">
  8.  
  9. <?php $session->flash(); ?>
  10. <h2>This page is rendered by SymfonyTemplating</h2>
  11. <h3>This layout has no AD block.</h3>
  12.  
  13. <div style="border: 2px solid green">
  14.   <?php $st->output('content') ?>
  15. </div>
  16.  
  17. </div>
  18. <div id="footer">
  19.     <?php echo $html->link(
  20.         $html->image('cake.power.gif', array('alt'=> __("CakePHP: the rapid development php framework", true), 'border'=>"0")),
  21.         'http://www.cakephp.org/',
  22.         array('target'=>'_blank'), null, false
  23.         );
  24.     ?>
  25. </div>

もととなっているのは,CakePHP標準のdefault.ctpです。変更している部分は最初にどのテンプレートを継承しているかをextendで定義している点と、このレイアウトを継承したテンプレートを表示する部分にcontentスロットを出力している点です。これはさきほどのst_basic.ctpと同じです。

  • layouts/st_default_with_ad.ctp
PHP:
  1. <?php $st->extend('layouts/st_basic') ?>
  2.  
  3. <div id="header">
  4.     <h1><?php echo $html->link(__('CakePHP: the rapid development php framework', true), 'http://cakephp.org'); ?></h1>
  5. </div>
  6.  
  7. <div id="content">
  8.  
  9. <?php $session->flash(); ?>
  10. <h2>This page is rendered by SymfonyTemplating</h2>
  11. <h3>This page has AD block.</h3>
  12.  
  13. <div id="ad" style="border: 3px dotted red;">
  14. <?php if (!$st->output('ad')): ?>
  15. default AD.
  16. <?php endif ?>
  17. </div>
  18.  
  19. <div style="border: 2px solid green">
  20.   <?php $st->output('content') ?>
  21. </div>
  22.  
  23. </div>
  24. <div id="footer">
  25.     <?php echo $html->link(
  26.         $html->image('cake.power.gif', array('alt'=> __("CakePHP: the rapid development php framework", true), 'border'=>"0")),
  27.         'http://www.cakephp.org/',
  28.         array('target'=>'_blank'), null, false
  29.         );
  30.     ?>
  31. </div>

さきほどのst_default.ctpと同じなのですが、広告用のdiv領域を用意し、adスロットを定義しています。このテンプレートの継承先でadスロットが定義されていれば書き代わりますし、定義されていない場合はここで定義した内容(default AD.)が出力されます。

  • sample/index.ctp

最後にsampleコントローラーのindexアクションで呼び出されるテンプレートを用意します。
もうすでに、レイアウト部分で外枠はほとんど定義できているので、以下のようにシンプルになりました。

PHP:
  1. <?php $st->start('title') ?>
  2. このページのタイトルはindex.ctpから定義しています
  3. <?php $st->stop() ?>
  4.  
  5. <?php $st->start('ad') ?>
  6. <p>Special AD. FOR YOU!!</p>
  7. <?php $st->stop() ?>
  8.  
  9. <?php echo $st->render('parts/news') ?>
  10.  
  11. <p>This Page is index.ctp</p>

titleスロット、adスロットを定義しています。ここで定義した内容は継承元で利用されます。
また、news.ctpという他のテンプレートをrenderで読み込み表示しています。
そして、最後のPタグがこのテンプレートで表示されるHTMLということになります。
また、ここではSymfony Templatingのextendを使わずに、コントローラー側でレイアウトを指定してみることにします。

コントローラーを用意する

  • controllers/sample_controller.php
PHP:
  1. <?php
  2. class SampleController extends AppController {
  3.   var $name = 'Sample';
  4.   var $view = 'St';
  5.   var $layout = 'st_default_with_ad';
  6.   public function index()
  7.   {
  8.   }
  9. }

最低限の内容のみ書いてあります。
まず、Viewクラスを自前クラスに変更するので$viewに'St'を指定しています。
あとは、テンプレートが利用するレイアウトを$layoutに指定すれば完了です。
'st_default'を指定すれば広告無しのレイアウトになり、'st_default_with_ad'を指定すれば広告有りのレイアウトになります。

index.ctpでレイアウトの指定も行う

さきほどは、コントローラーでテンプレートが利用するレイアウトの指定を行いました。
せっかくなので、テンプレートでレイアウトを指定するようにしてみます。
まずは、コントローラーでレイアウトを使用しないように次のように書き換えます。

  • controllers/sample_controller.php
PHP:
  1. <?php
  2. ....
  3.   var $layout = '';

あとは、index.ctpで継承するレイアウトをextendで指定すれば良いだけです。

  • sample/index.ctp
PHP:
  1. <?php $st->extend('layouts/st_default_with_ad') ?> 

これでさきほどと同じように表示されました。簡単ですね。

まとめ

とりあえずCakePHPとsymfonyをコラボレーションさせることはできました。
CakePHPでもレイアウトの多段継承やslotがあると便利だと思いますが、その分テンプレートが複雑かつ直感的でなくなるのでCakePHPの良さとは相反するのかもしれません。

関連するその他の記事

Comments

Leave a Reply