symfony book 日本語ドキュメント

第2章 - symfonyのコードを探求する

一見すると、symfony駆動のアプリケーションの背後に存在する(巨大で複雑な)コードにやる気をくじかれてしまうかもしれません。symfonyは多くのディレクトリとスクリプトで構成され、PHPクラスのファイルとHTMLのファイルが混在しており、ファイルのなかには両方の言語が含まれるものもあります。クラスのリファレンスがアプリケーションのフォルダーのなかでは見つからず、ディレクトリの深さが6レベルに達していることも見ることになります。しかしながら、ひとたびこの見た目の複雑のすべての背後にある理由を理解したのであれば、構造がごく自然なものなのでsymfonyのアプリケーション構造を別のものに置き換えようとは思わなくなるでしょう。この章ではおびえた感情を説明でとり除きます。

MVCパターン

symfonyは3つのレベルから構成される、MVCアーキテクチャ(Model-View-Controller architecture)として知られる古典的なWebのデザインパターンに基づいています:

図2-1はMVCパターンを図示しています。

MVCアーキテクチャはビジネスロジック(モデル)とプレゼンテーションを分割するので、結果としてよりすばらしく維持管理できるようになります。たとえば、アプリケーションが標準的なWebブラウザーと携帯端末の両方で動く場合、新しいビューだけが必要です; オリジナルのコントローラーとモデルはそのままにできます。コントローラーはモデルとビューからリクエスト(HTTP、コンソールモード、メール、など)のために使われるプロトコルの詳細を隠すための助けを行います。そしてモデルはデータのロジックの抽象化を行い、ビューとアクションを、たとえば、アプリケーションによって使われるデータベースの種類から独立したものにします。

図2-1 - MVCパターン

MVCパターン

MVCレイヤーリング

MVCの利点を理解するために、PHPの基本的なアプリケーションをMVCアーキテクチャのアプリケーションに改造する方法を見てみましょう。blogのアプリケーションのために投稿の一覧を表示する機能が完璧な例です。

ベタ書きのプログラミング

ベタ書きのPHPファイル(flat PHP file)において、データベースのエントリーの一覧表示機能はリスト2-1のようなスクリプトになります。

リスト2-1 - ベタ書きのスクリプト

[php]
<?php

//データベースに接続、選択する
$link = mysql_connect('localhost', 'myuser', 'mypassword');
mysql_select_db('blog_db', $link);

// SQLクエリを実行する
$result = mysql_query('SELECT date, title FROM post', $link);

?>

<html>
  <head>
    <title>投稿の一覧</title>
  </head>
  <body>
   <h1>投稿の一覧</h1>
   <table>
     <tr><th>日付</th><th>タイトル</th></tr>
<?php
// 結果をHTML形式で出力する
while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
echo "\t<tr>\n";
printf("\t\t<td> %s </td>\n", $row['date']);
printf("\t\t<td> %s </td>\n", $row['title']);
echo "\t</tr>\n";
}
?>
    </table>
  </body>
</html>

<?php

// 接続を閉じる
mysql_close($link);

?>

素早く書けて、速く実行できますが、維持することは不可能です。このコードの重大な問題はつぎのとおりです:

プレゼンテーションを分離する

リスト2-1のechoprintf呼び出しがコードを読みづらくしています。プレゼンテーションを強化するためにHTMLコードを修正する作業は現在の構文では骨が折れます。ですのでコードを2つの部分に分割する方法を採用することにします。最初に、リスト2-2で示されるような、ビジネスロジックをともなう純粋なPHPコードをコントローラーのスクリプトに移動させます。

リスト2-2 - コントローラー部分(index.php)

[php]
 // データベースに接続、選択する
 $link = mysql_connect('localhost', 'myuser', 'mypassword');
 mysql_select_db('blog_db', $link);

 // SQLクエリを実行する
 $result = mysql_query('SELECT date, title FROM post', $link);

 // ビューのために配列を充填する
 $posts = array();
 while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
 {
    $posts[] = $row;
 }

 // 接続を閉じる
 mysql_close($link);

 // ビューのスクリプトを読み込む
 require('view.php');

 ?>

リスト2-3で示されるように、テンプレートのようなPHPの構文を含むHTMLコードはビューのスクリプトに保存されます。

リスト2-3 - ビューの部分(view.php)

[php]
<html>
  <head>
    <title>投稿の一覧</title>
  </head>
  <body>
    <h1>投稿の一覧</h1>
    <table>
      <tr><th>日付</th><th>タイトル</th></tr>
    <?php foreach ($posts as $post): ?>
      <tr>
        <td><?php echo $post['date'] ?></td>
        <td><?php echo $post['title'] ?></td>
      </tr>
    <?php endforeach; ?>
    </table>
  </body>
</html>

ビューが十分に明解であるのか判断するためのよい経験則は、PHPの知識のないHTMLデザイナーが理解できるように最小量のPHPコードだけが含まれるかです。ビューのもっともありふれたステートメントはechoif/endifforeach/endforeachとそれだけです。また、HTMLタグをechoするPHPコードが存在してはなりません。

すべてのロジックはコントローラースクリプトに移動させ、内部にHTMLが存在しない純粋なPHPコードだけを含みます。当然のことながら、同じコントローラーが全体的に異なるプレゼンテーション、おそらくはPDFファイルもしくはXML構造のなかで、再利用できることを想像すべきです。

データの操作機能を分離する

コントローラーのスクリプトの大部分はデータの操作専用です。しかし、別のコントローラーのための投稿の一覧が必要な場合、たとえばblog投稿のRSSフィードを出力するには?コードの重複を避けるために、すべてのデータベースのクエリを一ヶ所に保存したいのであればどうしますか? postテーブルがweblog_postにリネームされたのでデータモデルを変更することを決定したらどうしますか?MySQLの代わりにPostgreSQLに切り替えたい場合はどうしますか?これらすべてを実現するには、リスト2-4で示されるように、コントローラーからデータ操作のコードを削除し、モデルと呼ばれる、別のスクリプトに設置します。

リスト2-4 - モデルの部分(model.php)

[php]
function getAllPosts()
{
  // データベースに接続し、選択する
  $link = mysql_connect('localhost', 'myuser', 'mypassword');
  mysql_select_db('blog_db', $link);

  // SQLクエリを実行する
  $result = mysql_query('SELECT date, title FROM post', $link);

  // 配列を充填する
  $posts = array();
  while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
  {
     $posts[] = $row;
  }

  // 接続を閉じる
  mysql_close($link);

  return $posts;
}

?>

改訂されたコントローラーはリスト2-5で示されています。

リスト2-5 - 改訂されたコントローラーの部分(index.php)

[php]
// モデルのコードを読み込む
require_once('model.php');

// 投稿のリストを読みとる
$posts = getAllPosts();

// ビューをrequireする
require('view.php');

?>

これでコントローラーはより読みやすくなりました。コントローラーの唯一のタスクはモデルからデータを取得してビューに渡すことです。より複雑なアプリケーションにおいて、コントローラーはリクエスト、ユーザーセッション、認証なども扱うことができます。モデルの関数のために明確な名前を使うことでコントローラーのコードのコメントは不要になります。

モデルスクリプトはデータアクセス専用で、それに応じて編成されます。(リクエストパラメーターのような)データレイヤーに依存しないすべてのパラメーターはコントローラーから渡されなければならず、またモデルによって直接アクセスされないようにしなければなりません。モデルの関数は別のコントローラーで簡単に再利用できません。

MVCを超えるレイヤーの分離

MVCアーキテクチャの原則はコードの性質にしたがってコードを3つのレイヤーに分離することです。データロジックのコードはモデルの範囲内に、プレゼンテーションのコードはビューの範囲内に、アプリケーションのロジックはコントローラーの範囲内に設置されます。

追加のデザインパターンによってコーディング作業がより簡単なものになります。モデル、ビュー、コントローラーレイヤーはさらに細かく分割できます。

データベースの抽象化

モデルレイヤーはデータベースアクセスレイヤーとデータベース抽象化レイヤーに分割できます。この方法では、データアクセス関数はデータベース依存のクエリのステートメントを使いませんが、ほかの関数がそれら自身でクエリを行います。もしあとでデータベースシステムを変更する場合、データベース抽象化レイヤーの更新だけが必要になります。

サンプルのデータベース抽象化レイヤーはリスト2-6、続くリスト2-7はMySQL固有のデータアクセスレイヤーの例です。

リスト2-6 - モデルの抽象化されたデータベースの部分

[php]
<?php

function open_connection($host, $user, $password)
{
  return mysql_connect($host, $user, $password);
}

function close_connection($link)
{
  mysql_close($link);
}

function query_database($query, $database, $link)
{
  mysql_select_db($database, $link);

  return mysql_query($query, $link);
}

function fetch_results($result)
{
  return mysql_fetch_array($result, MYSQL_ASSOC);
}

リスト2-7 - モデルのデータアクセスの部分

[php]
function getAllPosts()
{
  // データベースに接続する
  $link = open_connection('localhost', 'myuser', 'mypassword');

  // SQLクエリを実行する
  $result = query_database('SELECT date, title FROM post', 'blog_db', $link);

  // 配列を充填する
  $posts = array();
  while ($row = fetch_results($result))
  {
     $posts[] = $row;
  }

  // 接続を閉じる
  close_connection($link);

  return $posts;
}

?>

(リスト2-7で)データベース依存の関数がデータベースアクセスレイヤーで見つからないことを確認できます。このことによって関数がデータベースから独立したものになります。これに加えて、データベースの抽象化レイヤーで作成された関数はデータベースにアクセスする必要があるほかの多くのモデル関数で再利用できます。

NOTE リスト2-6とリスト2-7の例はまだ十分に満足のゆくものではなく、完全なデータベース抽象化を行うための作業が残されています(データベースから独立したクエリビルダーを通してSQLコードを抽象化すること、すべての関数をクラスに移動させることなど)。しかしこの本の目的は手作業ですべてのコードを書く方法を示すことではなく、8章でsymfonyがネイティブですべての抽象化を上手に行うことを見ることになります。

ビューの要素

ビューレイヤーもコード分離から恩恵を受けます。Webページがアプリケーション全体で一貫した要素を持つことはよくあります: たとえばページヘッダー、グラフィカルなレイアウト、フッター、グローバルナビゲーションなどです。ページ内部の部分のみが変わります。ビューがレイアウトとテンプレートに分離される理由はそういうわけです。通常、レイアウトはアプリケーションもしくはページのグループに対してグローバルです。テンプレートにはコントローラーによって利用可能になる変数のみが含まれます。若干のロジックがこれらのコンポーネントを連携させるために必要とされ、このビューロジックのレイヤーはビューの名前を保持します。リスト2-8、2-9、2-10で示されるように、これらの原則にしたがって、リスト2-3のビューの部分を3つに分割できます。

リスト2-8 - ビューのテンプレート部分(mytemplate.php)

[php]
<h1>投稿の一覧</h1>
<table>
<tr><th>日付</th><th>タイトル</th></tr>
<?php foreach ($posts as $post): ?>
  <tr>
    <td><?php echo $post['date'] ?></td>
    <td><?php echo $post['title'] ?></td>
  </tr>
<?php endforeach; ?>
</table>

リスト2-9 - ビューのロジック部分

[php]
<?php

$title = '投稿の一覧';
$posts = getAllPosts();

?>

リスト2-10 - ビューのレイアウト部分

[php]
<html>
  <head>
    <title><?php echo $title ?></title>
  </head>
  <body>
    <?php include('mytemplate.php'); ?>
  </body>
</html>

アクションとフロントコントローラー

前の例ではコントローラーはあまり多くのことを行いませんでしたが、実際のWebアプリケーションでは、コントローラーは多くの仕事を担います。この仕事の重要な部分はアプリケーションのすべてのコントローラーに共通なものです。共通のタスクにはリクエストの扱い、セキュリティの扱い、アプリケーション設定の読み込み、および似たような雑用作業が含まれます。しばしコントローラーがフロントコントローラーとアクションに分割されるのはそういうわけです。フロントコントローラーはアプリケーション全体で唯一のもので、アクションは1つのページに固有のコントローラーのコードのみを含みます。

フロントコントローラーの大きな利点の一つはアプリケーション全体で唯一のエントリーポイントが提供されることです。アプリケーションを閉鎖することを決定した場合、必要なことはフロントコントローラーのスクリプトを編集することだけです。フロントコントローラーなしのアプリケーションでは、それぞれの個別のコントローラーをオフに切り替えることが必要です。

オブジェクト指向

以前のすべての例では手続き型のプログラミングの方法を利用しました。現代のプログラミング言語の手法であるオブジェクト指向プログラミング(OOP)の機能によってプログラミングはさらに簡単なものになります。なぜなら、オブジェクトがロジックをカプセル化し、別のオブジェクトから継承し、クリーンな名前の規約を提供できるからです。

MVCアーキテクチャをオブジェクト指向ではない言語で実装すると名前空間の衝突とコードの重複問題が発生し、全体のコードが読みにくいです。

オブジェクト指向によって開発者はビューのオブジェクト、コントローラーのオブジェクト、モデルのオブジェクトといったものを扱うことが可能で、以前の例のすべての関数はメソッドに翻訳されます。オブジェクト指向はMVCアーキテクチャのための必須の方法です。

TIP オブジェクト指向の文脈でWebアプリケーションのためのデザインパターンについてもっと学びたい場合、Martin Fowler(マーチン・ファウラー)が書いたPatterns of Enterprise Application Architecture(Addison-Wesley, ISBN: 0-32112-742-0 (邦訳は翔泳社より刊行された「エンタープライズ アプリケーションアーキテクチャパターン」)をお読みください。Fowlerの本にあるコードの例はJavaもしくはC#ですが、PHPの開発者にとっても読みやすいです。

symfonyによるMVCの実装方法

少しお待ちください。blogで投稿の一覧を表示する単独のページに対して、必要なコンポーネントはいくつ存在するでしょうか? 図2-2で説明されているように、私たちにはつぎのような部品があります:

7つのスクリプト、全体の多くは新しいページを作るたびに開いて修正するファイルです!しかしながら、symfonyはこれらの作業を簡単にします。MVCアーキテクチャを最大限に活用にする一方で、symfonyはこのアーキテクチャを速くて苦痛のともなわないアプリケーションの開発方法で実装します。

最初に、フロントコントローラーとレイアウトはアプリケーションのすべてのアクションに対して共通です。複数のコントローラーとレイアウトを保有するのは可能ですが、必要なものはそれぞれ1つだけです。フロントコントローラーは純粋なMVCロジックのコンポーネントで、単独のものを書く必要は決してありません。symfonyが代わりに生成してくれるからです。

ほかのよいお知らせは、データ構造に基づいて、モデルレイヤーのクラスも自動的に生成されることです。これはPropelライブラリの仕事で、クラスのスケルトンとコードの生成方法を提供します。Propelは外部キー制約もしくはデータフィールドを見つけると、データ操作を簡単にする特別なアクセサー(accessor)メソッドとミューテーター(mutator)メソッドを提供します。そしてデータベースの抽象化は全体的にあなたには見えません。なぜなら、Creoleと呼ばれるほかのコンポーネントによって処理されるからです。データベースエンジンを変更することを決心した場合、書き直す必要のあるコードはゼロです。設定パラメーターを1つ変更することだけが必要です。

そして最後のお知らせは、プログラミングを行わずに、ビューのロジックをシンプルな設定ファイルに簡単に翻訳できることです。

図2-2 - symfonyのワークフロー

symfonyのワークフロー

リスト2-11、2-12、2-13で示されるように、このことは私たちの例で説明された投稿の一覧機能をsymfonyで動かすには3つのファイルだけが必要になることを意味します。

リスト2-11 - listアクション(myproject/apps/myapp/modules/weblog/actions/actions.class.php)

[php]
<?php
class weblogActions extends sfActions
{
  public function executeList()
  {
    $this->posts = PostPeer::doSelect(new Criteria());
  }
}

?>

リスト2-12 - listテンプレート(myproject/apps/myapp/modules/weblog/templates/listSuccess.php)

[php]
<h1>投稿の一覧</h1>
<table>
<tr><th>日付</th><th>タイトル</th></tr>
<?php foreach ($posts as $post): ?>
  <tr>
    <td><?php echo $post->getDate() ?></td>
    <td><?php echo $post->getTitle() ?></td>
  </tr>
<?php endforeach; ?>
</table>

リスト2-13 - listビュー(myproject/apps/myapp/modules/weblog/config/view.yml)

listSuccess:
  metas: { title: List of Posts }

加えて、リスト2-14で示されるように、まだ1つのレイアウトを定義する必要がありますが、これは何度も再利用されます。

リスト2-14 - レイアウト(myproject/apps/myapp/templates/layout.php)

[php]
<html>
  <head>
    <?php echo include_title() ?>
  </head>
  <body>
    <?php echo $sf_data->getRaw('sf_content') ?>
  </body>
</html>

これが本当に必要なもののすべてです。これが以前のリスト2-1で示されたベタ書きのスクリプトとまったく同じページを表示するために必要で正しいコードです。(すべてのコンポーネントを連携させる)残りの部分はsymfonyによって処理されます。行数を数えると、ベタ書きのファイルで書くよりもsymfonyによるMVCアーキテクチャで投稿の一覧機能を作成するほうが時間がかからないもしくはコーディング作業が少なくてすむことを理解することになります。それにもかかわらず、symfonyは非常に大きな利点、とりわけきれいなコードの編成、再利用性、柔軟性を提供し、そしてコーディング作業がずっと楽しくなります。おまけに、XHTMLの適合性(conformance)、デバッグ機能、簡単な設定、データベースの抽象化、スマートURLのルーティング、複数の環境、そしてより多くの開発ツールが手に入ります。

symfonyのコアクラス

symfonyのMVCアーキテクチャはこの本で何度も遭遇することになるいくつかのクラスを利用します:

6章でこれらのオブジェクトについて詳しく学びます。

ご覧のとおり、symfonyのすべてのクラスはプレフィックスのsfを使います。テンプレート内のsymfonyのコア変数も同様です。これによってあなたの独自クラスと変数の名前の衝突を回避することが可能で、symfonyのコアクラスは親しみやすく認識しやすくなります

NOTE symfonyで利用されているコーディング規約のなかで、UpperCamelCase(アッパーキャメルケース)はクラスと変数の命名に関する規約です。ただし、2つの例外が存在します: symfonyのコアクラスは小文字のsfで始まり、テンプレートで見つかる変数はアンダースコア(_)で区切られた構文を使います。

コードの編成

これであなたはsymfonyアプリケーションの異なるコンポーネントを知りましたが、これらがどのように編成されるのかおそらく疑問に思っているでしょう。symfonyはコードを1つのプロジェクト構造に編成して、プロジェクトファイルを1つの標準的なツリー構造に設置します。

プロジェクトの構造: アプリケーション、モデルとアクション

symfonyにおいて、プロジェクト(project)とは、同じオブジェクトモデルを共有し、指定されたドメイン名のもとで利用できるサービスとオペレーションの集合です。

プロジェクトの内部では、オペレーションはアプリケーションに論理的に分類されます。通常は、アプリケーションは同じプロジェクトのほかのアプリケーションから独立して動作できます。多くの場合、プロジェクトには2つのアプリケーションが含まれます: 1つはフロントオフィス用のアプリケーションで、もう1つはバックオフィス用のアプリケーションで、これらは同じデータベースを共有します。しかし、それぞれが異なるアプリケーションである小さなサイトを多く含む1つのプロジェクトを持つこともできます。アプリケーション間のハイパーリンクは絶対パスでなければならないことに注意してください。

それぞれのアプリケーションは1つもしくは複数のモジュールの一式(セット)です。通常、1つのモジュール(module)は同じ目的を持つページもしくはページのグループを表します。たとえば、homearticleshelpshoppingCartaccountモジュールなどを持つことができます

モジュールはアクションを保有します。アクション(action)は1つのモジュール内で行うことができるさまざまな行動を表します。たとえば、shoppingCartモジュールはadd(追加する)、show(表示する)、update(更新する)アクションを持つことができます。一般的にアクションは動詞として記述できます。アクションの扱いかたは古典的なアプリケーションのページの扱いかたとほとんど同じですが、2つのアクションの結果が同じページになることもあります(たとえば、blogでコメントを投稿画面に追加すると新しいコメントが含まれる投稿画面が再表示される)。

TIP プロジェクトを始めるのに際してこれが多すぎるレベルを表示する場合、すべてのアクションを単独のモジュールにまとめることはとても簡単です。アプリケーションがより複雑になるときが、アクションを異なるモジュールに分類するためのふさわしい時期です。1章で説明したように、(ふるまいを保ちながら)構造もしくは読みやすさを改善するためにコードを書き直す作業はリファクタリングと呼ばれ、RAD(ラピッドアプリケーション開発)の原則を適用するときに、この作業を多く行います。

図2-3はプロジェクト/アプリケーション/モジュール/アクション構造のblogプロジェクトのサンプルのコードの編成を示します。しかしプロジェクトの実際のファイルツリー構造は図で示されたセットアップと異なることをご了承ください。

図2-3 - コードの編成の例

コードの編成の例

ファイルのツリー構造

一般的に、すべてのWebプロジェクトはつぎのような同じタイプの内容を共有します:

symfonyはアーキテクチャ上の選択(MVCパターンとプロジェクト/アプリケーション/モジュールの分類)と一致した、論理的な方法で、これらすべての内容を編成する標準的なファイルのツリー構造を提供します。すべてのプロジェクト、アプリケーションもしくはモジュールを初期化するときに、このツリー構造は自動的に作成されます。もちろん、あなたの都合にあわせてファイルとディレクトリを再編成するもしくは顧客の要件を満たすために、、このツリー構造を完全にカスタマイズできます。

ルートのツリー構造

これらはsymfonyのプロジェクトのrootで見つかるディレクトリです:

apps/
  frontend/
  backend/
batch/
cache/
config/
data/
  sql/
doc/
lib/
  model/
log/
plugins/
test/
  unit/
  functional/
web/
  css/
  images/
  js/
  uploads/

テーブル2-1でこれらのディレクトリの内容が説明されています。

テーブル2-1 - ルートディレクトリ

ディレクトリ | 説明 ------------ | ------------ apps/ | プロジェクトのアプリケーションごとに1つのディレクトリが含まれる(よくあるのはfrontendbackendでそれぞれはフロントオフィス用とバックオフィス用) batch/ | バッチ処理を実行するために、コマンドラインもしくはスケジューラから呼び出されるPHPスクリプトが含まれる cache/ | 設定のキャッシュバージョン、(有効にした場合)アクションのキャッシュバージョン、そしてプロジェクトのテンプレートが含まれる。キャッシュのメカニズム(詳細は12章)はWebリクエストへのレスポンスを加速するためにこれらのファイルを使う。それぞれのアプリケーションはここにサブディレクトリを持ち、あらかじめ処理されたPHPとHTMLファイルを含む。 config/ | プロジェクトの一般的な設定が含まれる。 data/ | ここでは、プロジェクトのデータファイルを保存できる。たとえば、データベーススキーマ、テーブルを作成するSQLファイル、もしくはSQLiteデータベースファイルなど。 doc/ | プロジェクトのドキュメントが保存される。あなた独自のドキュメントやPHPdocによって生成されたドキュメントも含む。 lib/ | 外部クラスもしくは外部ライブラリ専用。ここでは、アプリケーション間で共有が必要なコードを追加できる。model/サブディレクトリはプロジェクトのオブジェクトモデルを保存する(8章で説明)。 log/ | symfonyによって直接生成されたアプリケーションのログファイルを保存する。Webサーバーのログファイル、データベースのログファイル、もしくはプロジェクトのどの部分からのログファイルなども含めることができる。symfonyはアプリケーション単位と環境単位で1つのログファイルを作成する(ログファイルは16章で説明)。 plugins/ | アプリケーションにインストールされたプラグインが保存される(プラグインは17章で説明) test/ | PHPで記述されsymfonyのテストフレームワークと互換性のあるユニットテストと機能テストを含む(15章で説明)。プロジェクトのセットアップ中に、symfonyはいくつかの基本的なテストを使用してスタブを自動的に追加する。 web/ | Webサーバーに対するroot。インターネットからアクセスできるファイルはこのディレクトリに設置されたもののみ。

アプリケーションのツリー構造

すべてのアプリケーションディレクトリのツリー構造は同じです:

apps/
  [application name]/
    config/
    i18n/
    lib/
    modules/
    templates/
      layout.php
      error.php
      error.txt

テーブル2-2でアプリケーションのサブディレクトリが説明されています。

テーブル2-2 - アプリケーションのサブディレクトリ

ディレクトリ | 説明 ------------ | ----------- config/ | たくさんのYAMLの設定ファイルの一式を保有する。フレームワーク自身で見つかるデフォルトパラメーターとは別に、たいていのアプリケーションの設定が存在する場所。必要な場合、ここでデフォルトパラメーターをさらにオーバーライドできることに注意。5章でアプリケーションの設定についてさらに学習する。 i18n/ | アプリケーションの国際化のために使われるファイルが含まれる。多くはインターフェイスの翻訳ファイルが含まれる(13章で国際化機能を扱う)。国際化のために1つのデータベースを選択した場合、このディレクトリを回避できる。 lib/ | アプリケーションに固有のクラスとライブラリが含まれる。 modules/ | アプリケーションの機能を含むすべてのモジュールが含まれる。 templates/ | アプリケーションのグローバルテンプレートの一覧が表示される。グローバルテンプレートはすべてのモジュールに共有される。デフォルトでは1つのlayout.phpファイルが含まれる。このファイルはモジュールテンプレートが挿入されるメインのレイアウト。

NOTE i18n/lib/modules/ディレクトリは新しいアプリケーションのために空です。

アプリケーションのクラスは同じプロジェクトのほかのアプリケーションのメソッドもしくは属性にアクセスできません。同じプロジェクトの2つのアプリケーション間のハイパーリンクは絶対パスでなければならないことを注意してください。1つのプロジェクトを複数のアプリケーションに分割する方法を選ぶとき、この最後の制約に留意する必要があります。

モジュールのツリー構造

それぞれのアプリケーションは1つもしくは複数のモジュールを含みます。それぞれのモジュールは独自のサブディレクトリをmodulesディレクトリ内に保有し、このディレクトリの名前はセットアップの間に選択されます。

つぎのものはモジュールの典型的なツリー構造です:

apps/
  [アプリケーションの名前]/
    modules/
      [モジュールの名前]/
          actions/
            actions.class.php
          config/
          lib/
          templates/
            indexSuccess.php
          validate/

テーブル2-3でモジュールのサブディレクトリが説明されています。

テーブル2-3 - モジュールのサブディレクトリ

ディレクトリ | 説明 ------------- | ------------ actions/ | 一般的にaction.class.phpと呼ばれる単独のファイルを含む。モジュールのすべてのアクションをこのファイルに保存できる。異なるファイルで1つのモジュールの異なるアクションを書くこともできる。 config/ | モジュールのためのローカルパラメーターを持つカスタム設定ファイルを含むことができる。 lib/ | モジュール固有のクラスとライブラリを保存できる。 templates/ | モジュールのアクションに対応するテンプレートが含まれる。デフォルトのテンプレートの名前はindexSuccess.phpで、モジュールのセットアップの間に作成される。 validate/ | フォームのバリデーションに対して使われる設定ファイル専用(10章で説明)。

NOTE config/lib/validate/ディレクトリは新しいモジュールのために空です。

webディレクトリのツリー構造

webディレクトリは一般利用者がアクセスできるファイルのディレクトリで、ごくわずかですが制約が存在します。基本的な命名規約に従うことでデフォルトのふるまいと便利なショートカットが提供されます。webディレクトリ構造の例はつぎのとおりです:

web/
  css/
  images/
  js/
  uploads/

慣習的には、静的なファイルはテーブル2-4でリストされるディレクトリに配置されます。

テーブル 2-4 - 典型的なwebのサブディレクトリ

ディレクトリ | 説明 ------------- | --------------------------------- css/ | .css拡張子を持つスタイルシート images/ | .jpg.pngもしくは.gif形式の画像が含まれる js/ | .js拡張子を持つJavaScriptファイルが含まれる uploads/ | ユーザーによってアップロードされたファイルを含まなければならない。通常このディレクトリには画像が含まれるが、開発サーバーと運用サーバーの同期化がアップロードされた画像に影響を与えないようにimagesディレクトリとは区別される。

NOTE デフォルトのツリー構造の維持は大いに推奨されますが、特定のニーズのために修正することは可能です。たとえば、異なるツリー構造とコーディング規約を持つサーバーでプロジェクトを運営できるようにするためなどです。ファイルのツリー構造を修正することに関する詳細な情報は19章を参照してください。

共通の手法

少数のテクニックがsymfonyで繰り返し使われ、この本とあなた独自のプロジェクトでもこれらをよく目にすることになります。これらはパラメーターホルダー、定数、そしてクラスのオートロードを含みます。

パラメーターホルダー

symfonyの多くのクラスは1つのパラメーターホルダー(parameter holder)を含みます。これはゲッターとセッターメソッドを持つ属性をカプセル化するために便利な方法です。たとえば、sfResponseクラスはgetParameterHolder()メソッドを呼び出すことで読みとりできるパラメーターホルダーを含みます。リスト2-15で示されるように、それぞれのパラメーターホルダーは同じ方法でデータを保存します。

リスト2-15 - sfResponseクラスのパラメーターホルダーを使う

[php]
$response->getParameterHolder()->set('foo', 'bar');
echo $response->getParameterHolder()->get('foo');
 => 'bar'

パラメーターホルダーを使う多くのクラスはget/setオペレーションのために必要なコードを短くするプロキシ(proxy)メソッドを提供します。これはsfResponseオブジェクトにあてはまるので、リスト2-16のコードでリスト2-15と同じことができます。

リスト2-16 - sfResponseパラメーターホルダーのプロキシメソッドを使う

[php]
$response->setParameter('foo', 'bar');
echo $response->getParameter('foo');
 => 'bar'

パラメーターホルダーのゲッターはデフォルト値を2番目の引数として受けとります。これは条件文で可能なことよりもはるかに簡潔で便利なフォールバックメカニズム(訳注:障害が起きても最低限の機能を維持するメカニズム)を提供します。リスト2-17で例をご覧ください。

リスト2-17 - 属性ホルダーのゲッターのデフォルト値を使う

[php]
// 'foobar' パラメーターは定義されていないので、ゲッターは空の値を返す
echo $response->getParameter('foobar');
 => null

// デフォルト値はゲッターを条件文に設置することで利用可能
if ($response->hasParameter('foobar'))
{
  echo $response->getParameter('foobar');
}
else
{
  echo 'default';
}
 => default

// しかしそれに対して2番目のゲッターの引数を使うほうがはるかに速い
echo $response->getParameter('foobar', 'default');
 => default

パラメーターホルダーは名前空間もサポートします。3番目の引数をセッターもしくはゲッターに指定する場合、この引数は名前空間として使われ、パラメーターはその名前空間の範囲内でのみ定義されます。リスト2-18で例が示されています。

リスト2-18 - sfResponseパラメーターホルダーの名前空間を使う

[php]
$response->setParameter('foo', 'bar1');
$response->setParameter('foo', 'bar2', 'my/name/space');
echo $response->getParameter('foo');
 => 'bar1'
echo $response->getParameter('foo', null, 'my/name/space');
 => 'bar2'

もちろん、その構文のファシリティを利用するためにパラメーターホルダーを独自のクラスに加えることもできます。リスト2-19はパラメーターホルダーでクラスを定義する方法を示しています。

リスト2-19 - パラメーターホルダーをクラスに加える

[php]
class MyClass
{
  protected $parameter_holder = null;

  public function initialize ($parameters = array())
  {
    $this->parameter_holder = new sfParameterHolder();
    $this->parameter_holder->add($parameters);
  }

  public function getParameterHolder()
  {
    return $this->parameter_holder;
  }
}

定数

定数は一度定義すると変更できないのでsymfonyでは定数は見つかりません。symfonyはsfConfigと呼ばれる独自の設定オブジェクトを使い、これは定数を置き換えます。このオブジェクトはどこからでもパラメーターをアクセスできる静的なメソッドを提供します。リスト2-20はsfConfigクラスのメソッドの使いかたを示しています。

リスト2-20 - 定数の代わりにsfConfigクラスのメソッドを使う

[php]
// PHP定数の代わり
define('SF_FOO', 'bar');
echo SF_FOO;
// symfonyはsfConfigオブジェクトを使う
sfConfig::set('sf_foo', 'bar');
echo sfConfig::get('sf_foo');

sfConfigメソッドはデフォルト値をサポートし、値を変更するために同じパラメーター上でsfConfig::set()メソッドを何度も呼び出すことができます。5章でsfConfigメソッドの詳細を検討します。

クラスのオートロード

通常の方法では、PHPでクラスを使うもしくはオブジェクトを作るとき、まずにクラスの定義をインクルードする必要があります。

[php]
include 'classes/MyClass.php';
$myObject = new MyClass();

しかし、多くのクラスと深いディレクトリ構造をかかえる大きなプロジェクトにおいて、インクルードするすべてのクラスのファイルとそれらのパスを追跡するには多くの時間を要します。spl_autoload_register()関数を提供することで、symfonyはincludeステートメントを不要にするので、つぎのようにクラスを直接書けます:

[php]
$myObject = new MyClass();

symfonyはプロジェクトのlib/ディレクトリの1つのなかに存在するphpの拡張子で終わるすべてのファイル内でMyClassの定義を探します。クラスの定義が見つかったら、ファイルは自動的にインクルードされます。

すべてのクラスをlib/ディレクトリに保存する場合、クラスをインクルードする必要はもはやありません。symfonyのプロジェクトが通常includeステートメントもしくはrequireステートメントを必要としない理由はそういうわけです。

NOTE パフォーマンスをより改善するために、symfonyのオートロード機能は最初のリクエストの間に(内部の設定ファイルで定義された)ディレクトリのリストをスキャンします。symfonyはこれらのディレクトリが含むすべてのクラスを登録し、PHPのファイルに対応するクラス/ファイルを連想配列として保存します。この方法によって、今後のリクエストではディレクトリのスキャンを行う必要はありません。クラスファイルをプロジェクトに追加もしくは移動させるたびにsymfony clear-cacheコマンドを呼び出してキャッシュをクリアする必要がある理由はそういうわけです。キャッシュは12章で、オートロードの設定は19章でさらに学ぶことになります。

まとめ

MVCフレームワーク(Model-View-Controller framework)を利用することで、フレームワークの規約にしたがってコードを分割して編成することが強制されます。プレゼンテーションのコードはビュー(view)に、データの操作コードはモデル(model)に、リクエストの操作ロジックはコントローラー(controller)になります。これによってMVCパターンのアプリケーションはとても便利で非常に制限されたものになります。

symfonyはPHP 5で書かれたMVCフレームワークです。その構造はMVCパターンを乗り越えながらも、非常に使いやすいように設計されました。器用で設定が柔軟であるおかげで、symfonyはすべてのWebアプリケーションのプロジェクトに適合します。

今、あなたはsymfonyの背後に存在する基本的な理論を理解したので、最初のアプリケーションを開発する準備がほとんど整いました。しかしそのまえに、symfonyをインストールして開発サーバーの上で動かすことが必要です。