symfony での MVC における View 部分の実装はテンプレート、可能であればレイアウトが含まれているテンプレートですが、それを使うことによって行われています。 再利用できるコードがアクションの結果であったり、断片的なものの結果であったり、アプリケーション的なロジックを必要としない場合にスロットにおいてそれらのコードが再利用できます。symfony のコントローラーが実装している命名規則と密接した調和のおかげでテンプレート管理は簡単です。
アクションの実行結果は View です。symfony において、View は古典的な PHP ファイルや、テンプレートが他のインターフェースの要素に合うように設定ファイルで記述されたテンプレートの組み合わせです。
以下は典型的な indexSuccess.php というテンプレートです。
<h1>Welcome</h1> <p>Welcome back, <?php echo $name ?> !</p> <ul>What would you like to do ? <li><?php echo link_to('Read the last articles', 'article/read/') ?></li> <li><?php echo link_to('Start writing a new one', 'article/write/') ?></li> </ul>
アクションやヘルパーで定義されている変数呼び出している基礎的な PHP コードや HTML コードを含んでる場合もあります。
テンプレート内で呼び出される変数は利用できるショートカット ( 下記参照 ) か、関連するアクションファイルに定義されたアクションオブジェクトの属性である必要があります。たとえば、$name 変数の値を定義するために、アクションは次のような行を含まなければなりません。
$this->name = 'myvalue';
開発者はテンプレート内ではできるだけ少ない PHP コードにしようとします。なぜなら、これらのファイルはアプリケーションの GUI をデザインした人たちのものであり、ときにはアプリケーション部分ではなく、プレゼンテーション部分に特化した別のチームによって作成されたり、メンテナンスされるかもしれないからです。
symfony は前もって定義された便利な変数、もしくは ( 必要な情報にアクセスすることができる ) ショートカット をテンプレートに提供しています。
$sf_context //contextオブジェクト全体 $sf_request //オリジナルのリクエスト $sf_params //オリジナルのリクエストパラメータ $sf_user //現在のsfuserオブジェクト $sf_view //sfViewオブジェクト
たとえば、アクションが total パラメータを呼び出すなら、パラメータの値はテンプレートにおいて次のように利用できるでしょう
<?php echo $sf_params->get('total'); ?> // 以下のアクションコードと同意です echo $this->getRequestParameter('total');
POST か GET でも symfony のルーティングシステムを使ってアクションに送られたパラメータはどちらにでも取得できます。
index.php/test/index&total=123 //GET
index.php/test/index/total/123 //ルーティングによるアクション呼び出し
そして、最初のアクション呼び出しは他のアクション呼び出しへつながっている場合は ( 例えば フォワード による処理など )、アクションスタックでパラメータにアクセスする必要があるかもしれません。追加されたショートカットで簡単にアクセスできます。
$sf_first_module //最初に呼ばれたモジュール $sf_last_module //最後に呼ばれたモジュール $sf_first_action //最初に呼ばれたアクション $sf_last_action //最後に呼ばれたアクション
( さきほどのサンプルにある link_to() 関数のような ) ヘルパー はテンプレート作成と、パフォーマンスとアクセシビリティに優れた HTML コードを生成するのを手助けする PHP 関数です。3 種類のヘルパーが利用できます。
標準的な必須のヘルパーであり、対応する HTML コードの代わりに利用すべきです。なぜなら、そうすることによって、symfony の機能 ( ルーティング、管理の自動化 ) が正常に動作するようになるからです。
全ての URL を使用する HTML タグを含んでいます。つまり、ルーティングが利用できる symfony ショートカットに置き換えます。
標準的なオプションヘルパーであり、同じ目的で作成した古典的 HTML よりも少ないコードで作成できます。 さらに、symfony 構造を利用できます。つまり、このヘルパーを使うことで、ファイルのツリー構造を変更したあとも、問題なく動作させることができます。
各アプリケーションに特化させたヘルパー ( これについてはカスタムヘルパーの作成の章を参照してください )
ヘルパーを使うことで開発速度を上げることができます。そして、詳細については次の章で説明します。
ヘルパー関数を使用するために、ヘルパーを含むファイルを読み込まなくてはなりません。
いくつかのヘルパーはアプリケーション毎に読み込ませます。
use_helper() というヘルパーを定義しており、helper 読み込みのために必要なヘルパー標準で、アプリケーション設定は追加するヘルパーに特化しており、settings.yml で全要求を読み込みませられます。
default:
.settings:
standard_helpers: [Partial, Cache, Form]
これらに加えて、アプリケーション共通で読み込む必要があるヘルパーを定義できます。たとえば、アプリケーションが大量のテキストと国際化関数を利用するのなら、settings.yml に次のように記述できます。
all:
.settings:
standard_helpers: [Partial, Cache, Form, Text, I18N]
デフォルトで読み込みが必要ではないヘルパーが必要であれば、そのヘルパーを使うテンプレートで use_helper() 関数を呼びだせます。
// このテンプレートで使用する特定のヘルパーを呼び出す場合 <?php echo use_helper('Date') ?> ... <?php echo format_date($date, 'd', 'en') ?> // 一度に複数のヘルパーを呼び出す場合 <?php echo use_helpers('Date', 'I18N') ?>
テンプレートは全てグローバルテンプレートかmyproject/apps/myapp/templates/layout.php のレイアウトの中に含むことができます。以下はデフォルトの内容です。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/2000/REC-xhtml1-200000126/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <?php echo include_http_metas() ?> <?php echo include_metas() ?> <?php echo include_title() ?> <link rel="shortcut icon" href="/favicon.ico"> </head> <body> <?php echo $content ?> </body> </html>
<head> タグの中身はいまのところ暗号めいているかもしれないですが、次章で説明します。ただ、標準設定とさきほどのテンプレートでは、処理後のビューが次のようになるということだけ覚えておいてください。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/2000/REC-xhtml1-200000126/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta name="robots" content="index, follow" /> <meta name="description" content="" /> <meta name="keywords" content="" /> <title>symfony project</title> <link rel="stylesheet" type="text/css" href="/css/main.css" /> <link rel="shortcut icon" href="/favicon.ico"> </head> <body> <h1>Welcome</h1> <p>Welcome back, <?php echo $name ?> !</p> <ul>What would you like to do ? <li><?php echo link_to('Read the last articles', 'article/read/') ?></li> <li><?php echo link_to('Start writing a new one', 'article/write/') ?></li> </ul> </body> </html>
これはデコレーターデザインパターンのアプリケーションであり、アクションから呼び出されるテンプレートの内容は $content 変数をもつグローバルテンプレートに統合されています。
グローバルテンプレートは各アプリケーションのためにカスタマイズできます。
symofny がどうやってテンプレートとアクションをリンクさせているんだろうと思うかもしれません。テンプレート名がアクション名から簡単に推測できるとき、テンプレートの度重なる露骨な呼び出しをさけるために、symfony はいくつかの主な命名規則をもっています。
テンプレート名は 2 つの部分から構成されています。1 つ目の部分はアクションに関連しており、2 つ目の部分は結果に関連しています。たとえば、典型的なうまく ( succesfully ) 実行した index を呼び出すアクションのテンプレートは indexSuccess.php となります。
symfony には暗黙のルールがあります。アクションによって返される値は sfView::SUCCESS であり、そのとき呼び出されるテンプレート名はアクション名と Success.php を結合した名前になります。
たとえば、product モジュールにある 2 つのアクション index と list は
class productActions extends sfActions { public function executeIndex() { ... return sfView::SUCCESS; } public function executeList() { ... return sfView::SUCCESS; } }
各アクションの実行の終わりにおいて呼び出されているテンプレートは myproject/apps/myapp/modules/product/templates/ にある
indexSuccess.php
listSuccess.php
という名前のテンプレートになります。
これが標準の振る舞いなので、アクションが何もかえさなければ、フレームワークは暗黙で sfView::SUCCESS が返されたときのように success ビューが呼び出されたものと理解します。
他の ( sfView::NONE 以外の ) 戻り値にとって、呼び出されるテンプレートはこの値をの名前で終わっています。たとえば、sfView::ERROR の場合は、Error.php で終わるテンプレートになります。
アクションは代わりのテンプレートをセットすることもできます。
$this->setTemplate('myCustomTemplate');
アクションの戻り値によると、呼び出されるテンプレートはmyCustomTemplateSuccess.php か myCustomTemplateError.php になります。
これらのアクションにおける可能性のあるパターンを見てみましょう。
class productActions extends sfActions { public function executeIndex() { if ($test == 1) { ... return sfView::SUCCESS; } else if ($test == 2) { ... return 'MyResult'; } else if ($test == 3) { ... $this->setTemplate('myCustomTemplate'); return sfView::SUCCESS; } else { return sfView::ERROR; } } }
myapp/modules/product/templates/ にある呼び出されるテンプレートは、$test の値に左右され、次のように名前がつけられています。
indexSuccess.php
indexMyResult.php
myCustomTemplateSuccess.php
indexError.php
アプリケーションとモジュールは view.yml 設定ファイルをもっています。その設定ファイルはモジュールとそのモジュールにある各アクションにどうやってテンプレートが他のインターフェースコンポーネント ( レイアウト、スロット、スタイルシートなど ) と統合されているかというのを記述しています。
それなので、view.yml ファイルはアクションからビューに関係している関数呼び出しのもうひとつの方法です。
template: $this->setTemplate();
view.yml の詳細については次章をよんでください。
HTML や PHP コードを複数ページに読み込ませたり、読み込みの繰り返しを避けたり、1 ページに何度も同じコンテンツを表示する必要があるかもしれません。PHP の include() 関数は目的には十分かもしれない。しかし、もし、symfony オブジェクトやヘルパーを利用し、パラメータをこの断片化したコードへ通すにはどうしたらよいでしょう? symfony は次のような 3 種類のヘルパーを用意してあります。
さらに、symfony はレイアウトで範囲を定義するための技術を提供します。これにより、テンプレートで上書きすることができ、それはスロットと呼ばれます。複雑なロジックがない場合にコンポーネントスロットの代わりに利用します。
これら3種類のヘルパーは全ての symfony テンプレートにおいて利用可能です。
These helpers are available from any symfony template.
もし、コンテンツの種類が静的な HTML であれば、PHP の include() 関数を利用できます。
// 現在のテンプレートに HTML コードを読み込みます <?php include('myfile.html') ?>
もっとも、一般的な必要性は PHP コードの部品を読み込むことです。この PHP コードは symfony オブジェクトも関数も必要としないなら、include() 関数で十分です。たとえば、多くのテンプレートやテストモジュールが現在時刻を表示する断片的なコードを利用するなら、グローバルテンプレートディレクトリ ( myproject/apps/myapp/templates ) に time.php というコードを含んだファイルを保存します。そして、このコードがテンプレートで必要なときに、ただ、include() 関数を呼ぶだけです。
// 現在のテンプレートにPHPコードを読み込みます。 <?php include(sfConfig::get('sf_app_template_dir').'/time.php') ?>
パーシャルとはアプリケーションを通して、異なるモジュールから呼び出されるテンプレートの断片です。パーシャルは symfony ヘルパーやテンプレートショートカットにアクセスすることができますが、明確な引数として通過してきていないのなら、それを呼んでいるアクション内で定義されていない変数にはアクセスできません。
ユーザーがショッピングカートでアイテムを選択できるアプリケーションを想像してみてください。あるページではのある部分ではカートにあるアイテム数とそのリンクを表示しています。ショッピングカートオブジェクトはアクションによって提供されています。まず第一に、テンプレートの断片は次のようになります。
<div> <h1>Selected items</h1> <?php echo $cart->getCount() ?><br /> <?php echo link_to('Modify', 'cart/edit?cart_id='.$cart->getId()) ?> </div>
このテンプレートの断片はカートモジュール内 ( templates/_sidebar.php というファイル ) に保存されます。
パーシャルを利用したテンプレートへ戻ります。テンプレートのために、アクションは現在の選択した商品を含んだ $myShoppingCart オブジェクトを定義しています。この値をパーシャルに通すために、テンプレートは include_partial() ヘルパーを利用します。
<?php echo include_partial('cart/sidebar', array('cart' => $myShoppingCart )) ?>
第1引数は module/template 名で、第2引数はパーシャルで必要となる変数の配列です。}include_partial() 関数のパーシャル名と実際の templates/ ディレクトリのファイル名にある '_' の違いについて気づいてください。こうすることであなたのコードをクリーンにそして、ファイル検索時に他のテンプレートより前に断片化することを可能としています。
もし、1つのモジュールに制限させないのであれば、メインテンプレートディレクトリ ( myproject/apps/myapp/templates ) に断片化したものを保存してください。どんなモジュールからも次のようにして呼び出します。
<?php echo include_partial('global/sidebar', array('cart' => $myShoppingCart )) ?>
上記のサンプルで、パーシャルのロジックは軽装で、アクションを必要としません。ロジックが背後にあるパーシャルが必要な場合、コンポーネントを利用すべきです。コンポーネントはアクションのようなものであり、変数をテンプレートの断片に渡すことができます。ただし、アクションよりも高速です。actions/ ディレクトリにある components.class.php というファイルにロジックが書かれてあり、テンプレートは普通のものです。
たとえば、ニュースコンポーネントはサイドバーにユーザーのプロフィールによって、いろいろなユースのタイトルのヘッドラインを表示できるとします。ニュースヘッドライン取得したりするのは単純なパーシャルでは複雑すぎ難しいでしょう。それで、ファイルのようなアクションに移動させたほうがよい。
news モジュールで、actions/ ディレクトリに components.class.php というファイルを作成し、次のような内容を記述します。
<?php class newsComponents extends sfComponents { public function executeHeadlines() { $c = new Criteria(); $c->addDescendingOrderByColumn(NewsPeer::PUBLISHED_AT); $c->setLimit(5); $this->news = NewsPeer::doSelect($c); } } ?>
同じニュースモジュールの、templates/ ディレクトリに _headlines.php パーシャルを作成します。
<div> <h1>Latest news</h1> <ul> <?php foreach($news as $headline): ?> <li> <?php echo $headline->getPublishedAt() ?> <?php echo link_to($headline->getTitle(), 'news/show?id='.$headline->getId()) ?> </li> <?php endforeach ?> </ul> </div>
これでテンプレートにコンポーネントを用意できたので、次のように呼び出します。
<?php include_component('news', 'headlines') ?>
パーシャルのようなコンポーネントは連想配列の形で追加パラメータを受け取ります。パラメータはそれらの名前の元のパーシャルに利用することができ、$this オブジェクトを通してコンポーネントにあります。
// call to the component <?php include_component('news', 'headlines', array('foo' => 'bar')) ?> // in the component itself, echo $this->foo; => 'bar' // in the _headlines.php partial, echo $foo; => 'bar'
普通のテンプレートみたいに、コンポーネント、もしくはグローバルテンプレートにコンポーネントを含めることもできます。アクションと同様に、execute コンポーネント関数は変数を関連するパーシャルに渡したり同じショートカットにアクセスすることができます。しかし、類似点はそれで終わりです。つまり、パーシャルはセキュリティーな部分を扱わず、さまざまな 戻り値 を持たせることができず、実行アクションよりも高速です。
呼び出しの度に変化するするようなコンポーネントを呼び出す必要があるかもしれません。たとえば、アプリケーションのメインレイアウトが画面右側にインフォメーションやリンクを表示することができます。
symfony は include_component_slot() ヘルパーで簡単に扱うことができます。この関数はパラメータをラベルとして扱い、view.yml 設定ファイルを利用することで、このラベルは各モジュールのコンポーネントを定義するのに使うことができます。
例えば、アプリケーションの layout.php は次のようになるでしょう
... <div id="sidebar"> <?php include_component_slot('sidebar') ?> </div>
( myapp/config/ ディレクトリにある ) 主な view.yml 設定ファイルにおいて、このスロットのデフォルト値を定義します。
default:
components:
sidebar: [bar, default]
デフォルトでは、この sidebar は bar モジュールにある barComponents クラスの excuteDefalt() 関数を呼び出します、そしてこの関数は modules/bar/templates/ にある _default.php というパーシャルを表示するでしょう。しかし、モジュールのためにこのセッティングを上書きできるでしょう。例えば、user モデルにおいて、このユーザー名を表示するためのコンポーネントがほしいかもしれない。この場合、 modules/user/config/ の view.yml に追加します。
all:
components:
sidebar: [bar, user]
注意: あなたのモジュールの
config/view.ymlファイルはモジュールによって自動的に生成されません。手動でこのファイルを作成する必要があります。
modules/bar/actions/components.class.php に、この新しい関数を追加します。
class barComponents extends sfComponents { ... public function executeUser() { $current_user = $this->getUser()->getCurrentUser(); $c = new Criteria(); $c->add(ArticlePeer::AUTHOR_ID, $current_user->getId()); $this->nb_articles = ArticlePeer::doCount($c); $this->current_user = $current_user; } }
そして、次のような modules/bar/templates/_user.php を作成します。
User name: <?php echo $current_user->getName() ?><br /> (already published <?php echo $nb_articles ?> articles)
コンポーネントスロットはパンくずや、ナビゲーションや、あらゆる動的挿入のために利用できます。コンポーネントのように、コンポーネントスロットはグローバルテンプレートや標準テンプレートのなかや、もしくは他のコンポートネントの中でさえ利用できます。コンポーネントスロット設定はいつも最後のアクションで呼ばれます。
注意: もし、モジュールでコンポーネントスロットの利用の一時停止を想定してるのなら、
module/componentを空で宣言するだけです。all: components: sidebar: [ ]
コンポーネントスロットによってコンポーネント、パーシャル、設定ファイルを生成することが可能になります。 再利用したいときには本当に役立ちます。 アクションに毎にレイアウトの中に範囲を追加する必要があるなら、もっと早い解決方法があります。それがスロットです。
スロットはレスポンス中において全体を通して保存されているコードの塊です。 どこでも定義でき ( レイアウト中でも、テンプレート中でも、パーシャル中でも ) 、どこでも読み込むことができます。 レイアウトはテンプレートの後に実行され、パーシャルはテンプレートで呼び出されたときに実行されるため、読み込む前にスロットを定義しなくてはならいということだけを覚えておいてください。
スロットを定義するために、 slot() と end_slot() ヘルパーを使います。 たとえば、テンプレートは 'サイドバー' スロットの内容を次のように定義しています。
... <?php slot('sidebar') ?> <h1>User <?php echo $user ?>'s details</h1> <p>email: <?php echo $user->getEmail() ?></p> <p><?php echo link_to('do things', 'user/dothings?id='.$user->getId()) ?> <?php end_slot() ?>
slot() と end_slot() ヘルパー間のコードはテンプレートに出力されません。しかし、レスポンス中のプレースホルダーの中に出力され、どこでも読み込むことができます。
スロットを読む込むために、include_slot() ヘルパーを使います。 has_slot() ヘルパーを使うことで、デフォルトのスロットの値を管理が行えます。たとえば、レイアウトは 'サイドバー' スロットを <div> 中で表示することができるとします。( 前述のようにテンプレートより ) スロットがより早い段階で定義されていれば、表示されます。
もし、定義していなければ、 デフォルト内容が変わりに表示されます。 include_slot() は何も返さないので、スロットが定義されていないなら、has_slot() の利用は必須ではありません。
... <div id="sidebar"> <?php if(has_slot('sidebar')): ?> <?php include_slot('sidebar') ?> <?php else: ?> <!-- default sidebar code --> <h1>Contextual zone</h1> <p>This zone contains links and information relative to the main content of the page.</p> <?php endif; ?> </div>
1 つの一般的なスロットの利用方法は特別なタグを <head> セクションに追加することです。たとえば、あるアクション中で RSS リンクを読み込みたいときなどです。たとえばメインコンテンツが第 2 のナビゲーションを追加する場合などの、1 つ以上の動的な範囲があるレイアウトがもう 1 つのよくある使い方です。 どこでも読み込むことができるこの代替機能のおかげで、スロットはとても強力でレイアウトとテンプレートでのモジュールのような仕組みを行えます。最後に、スロットはコンポーネントスロットよりも高速なので、広範囲で自由に利用してください。
注意: わずかに意味が異なりますが、スロットは以前のバージョンのsymfonyに存在していました。