第16章 - アプリケーションの運用ツール

開発とデプロイの両方の期間において、開発者はアプリケーションが期待どおりに動いているかを判断するには絶え間なく一貫した診断情報が必要になります。この情報は一般的にロギングとデバッグユーティリティに集約されます。symfony のようなフレームワークは稼働中のアプリケーションで中心的な役割を果たすので、効率的な開発と運用活動を保証するためにこれらの機能がしっかりと統合されていることは極めて重要です。

アプリケーションが運用サーバーに設置されている期間、アプリケーションの管理者はログのローテーションからアップグレードまで膨大な数のタスクを繰り返します。フレームワークはこれらのタスクを可能なかぎり自動化するツールも提供しなければなりません。

この章では、symfony アプリケーションの運用ツールがこれらのニーズに対してどのように答えるのかを説明します。

ロギング

リクエストが実行されている間に間違ったことが行われていることを理解する唯一の方法は実行プロセスのトレースを洗い直すことです。幸いにして、このセクションで学ぶように、PHP と symfony の両ほうがこの種の膨大なデータをログに記録してくれます。

PHP のログ

PHP には error_reporting パラメーターがあります。これは php.ini のなかで定義され、PHP のイベントが記録される場所を指定します。リスト16-1で示されるように、symfony の settings.yml ファイルのなかで、アプリケーションと環境ごとにこの値をオーバーライドできます。

リスト16-1 - エラーレポートのレベルを設定する (frontend/config/settings.yml)

prod:
 .settings:
    error_reporting:  <?php echo (E_PARSE | E_COMPILE_ERROR | E_ERROR | E_CORE_ERROR | E_USER_ERROR)."\n" ?>

dev:
  .settings:
    error_reporting:  <?php echo (E_ALL | E_STRICT)."\n" ?>

運用環境でパフォーマンスの問題を避けるには、サーバーは PHP の重大なエラーだけをログに記録します。しかしながら、開発環境において、すべてのタイプのイベントがログに記録されるので、開発者はエラーを追跡するために必要なすべての情報を入手できます。

PHP のログファイルの位置は php.ini の設定によって決まります。この位置を定義しなくてもすむのであれば、おそらく PHP は Web サーバーによって提供される (Apache のエラーログのような) ロギングファシリティを利用しています。この場合、Web サーバーのログディレクトリのもとで PHP ログが見つかります。

symfony のログ

PHP の標準のログ機能に加えて、symfony は多くのカスタムイベントログを記録できます。myproject/log/ディレクトリのもとで symfony が記録するすべてのログを見ることができます。環境とアプリケーションごとに1つのファイルが存在します。たとえば、frontend アプリケーションの開発環境のログファイルは frontend_dev.log と名づけられます。運用環境のログファイルは frontend_prod.log と名づけられます。

symfony アプリケーションを稼働させているのであれば、ログファイルをご覧ください。構文はシンプルです。1つのイベントごとに1つの行がアプリケーションのログファイルに追加されます。それぞれの行はイベントの正確な時間、イベントの性質、処理されているオブジェクトに関連する詳細内容が含まれます。リスト16-2はログファイルの内容の例を示します。

リスト16-2 - symfony ログファイルの内容のサンプル (log/frontend_dev.log)

Nov 15 16:30:25 symfony [info ] {sfAction} call "barActions->executemessages()"
Nov 15 16:30:25 symfony [info ] {sfPropelLogger} executeQuery: SELECT bd_message.ID...
Nov 15 16:30:25 symfony [info ] {sfView} set slot "leftbar" (bar/index)
Nov 15 16:30:25 symfony [info ] {sfView} set slot "messageblock" (bar/mes...
Nov 15 16:30:25 symfony [info ] {sfView} execute view for template "messa...
Nov 15 16:30:25 symfony [info ] {sfView} render "/home/production/myproject/...
Nov 15 16:30:25 symfony [info ] {sfView} render to client

データベースに送られた実際の SQL クエリ、呼び出されたテンプレート、オブジェクト間の呼び出しチェーンなど、これらのファイル内で多くの情報が見つかります。

NOTE リスト16-3で示されるようにロギングするファイルのフォーマットは factories.yml のなかの format かつ/もしくは time_format 設定をオーバーライドすることで設定可能です。

リスト16-3 - ログフォーマットを変更する

all:
  logger:
    param:
      sf_file_debug:
        param:
          format:      %time% %type% [%priority%] %message%%EOL%
          time_format: %b %d %H:%M:%S

symfony のログレベルの設定

symfony のログメッセージには8つのレベルが存在します: PEAR::Log パッケージと同じく、emergalertcriterrwarningnoticeinfodebug です。リスト16-4で示されているように、それぞれのアプリケーションの logging.yml 設定ファイルのなかでそれぞれの環境でロギングされる最大レベルを設定できます。

リスト16-4 - デフォルトのロギング設定 (frontend/config/logging.yml)

prod:
  logger:
    param:
      level: err

デフォルトでは、運用環境を除いたすべての環境において、すべてのメッセージがログに記録されます (もっとも重要度が低い debug レベルまで)。デフォルトでは、運用環境のロギングは無効です; settings.yml のなかの logging_enabledtrue に変更すると、もっとも重要なメッセージ (crit から emergまで) のみがログファイルに現れます。

ロギングされるメッセージのタイプを制限するために factories.yml ファイルのなかでそれぞれの環境に対してロギングレベルを変更できます。

TIP ロギングが有効であることを確認するには、 sfConfig::get('sf_logging_enabled') を呼び出します。

ログメッセージを追加する

リスト16-5に記述されているテクニックの1つを使うことでメッセージを symfony のログファイルに手動で追加できます。

リスト16-5 - カスタムログメッセージを追加する

[php]
// アクションから
$this->logMessage($message, $level);

// テンプレートから
<?php use_helper('Debug') ?>

<?php log_message($message, $level) ?>

ログメッセージなどで $level は同じ値を持つことができます。

代わりの方法として、アプリケーションの任意の場所からメッセージをログに書き込むには、リスト16-6で示されるように、sfLogger メソッドを直接使います。利用可能なメソッドはログレベルと同じ名前を持ちます (上記の「symfony のログレベルの設定」のセクションを参照)。

リスト16-6 - 任意の場所からカスタムログメッセージを追加する

[php]
if (sfConfig::get('sf_logging_enabled'))
{
  sfContext::getInstance()->getLogger()->info($message);
}

SIDEBAR ロギングをカスタマイズする

symfony のロギングシステムはとてもシンプルなので、カスタマイズも簡単です。 唯一の前提要件は doLog() メソッドを定義する sfLogger クラスを拡張するロガークラスです。symfony は2つのパラメーターで: $message (ロギングされるメッセージ)と$priority(ログのレベル)でdoLog()メソッドを呼び出します

myLoggerは PHP のerror_log 関数を利用してシンプルなロガーを定義します:

[php]
class myLogger extends sfLogger
{
  protected function doLog($message, $priority)
  {
    error_log(sprintf('%s (%s)', $message, sfLogger::getPriorityName($priority)));
  }
}

既存のクラスからロガーを作成するには、log メソッドを定義する sfLoggerInterface インターフェイスを実装するだけです。メソッドは doLog() メソッドと同じ2つのパラメーターを受けとります:

[php]
require_once('Log.php');
require_once('Log/error_log.php');

// symfony で使いたいロガーのために
// インターフェイスを実装するために薄いラッパーを定義する
class Log_my_error_log extends Log_error_log implements sfLoggerInterface
{
}

ログファイルをパージしてローテーションを決める

アプリケーションの log/ ディレクトリを定期的にパージ(消去)することを忘れないでください。これらのファイルは、トラフィック次第ですが、たいていの場合、数日で数メガバイトに成長するからです。symfony はこの目的のために特別なlog:clearタスクを提供します。たとえば、つぎのコマンドは symfony のログファイルを削除します:

$ php symfony log:clear

ベターなパフォーマンスとセキュリティのために、おそらくは1つの大きなファイルの代わりに、symfony のログをいくつかの小さなファイルに保存したいと思うでしょう。ログファイルのための理想的な保存戦略はメインのログファイルをバックアップして定期的に空にする一方で、制限された数のバックアップだけ維持することです。logging.yml のなかでこのようなログのローテーションを有効にしてパラメーターを指定できます。たとえば、リスト16-7で示されるように、7日の period10history (バックアップ数) によって、1つの有効なログファイルに加えて、それぞれが7日の履歴を含む10のバックアップファイルを扱うことになります。現在の有効なログファイルはバックアップに移動し、もっとも古いバックアップは消去されます。

リスト16-7 - ログのローテーションを起動する

$ php symfony log:rotate frontend prod --period=7 --history=10

バックアップログファイルは logs/history/ ディレクトリに保存され、保存された時点の日付がサフィックスとして名前に追加されます。

デバッグする

どんなに熟練したプログラマであれ、symfony を利用しているとしても、結局は間違いをします。エラーの検出と理解は早くアプリケーションを開発するための重要な要素の1つです。幸いにして、symfony は開発者のためにいくつかのデバッグツールを提供します。

symfony のデバッグモード

symfony にはアプリケーションの開発とデバッグを円滑にするデバッグモードがあります。このモードがオンのとき、つぎのことが起こります:

  • 設定はリクエストごとにチェックされるので、コンフィギュレーションキャッシュをクリアしなくても、設定ファイルの変更が即座に反映されます。
  • エラーメッセージは明快で使いやすい形式でフルスタックトレースを表示するので、障害のある要素を効率よく見つけられます。
  • より多くのデバッグツールが利用できます(たとえばデータベースクエリの詳細内容)。
  • Propel/Doctrine のデバッグモードも有効であれば、Propel/Doctrine オブジェクト呼び出しでのエラーは Propel/Doctrine アーキテクチャを通して詳細な呼び出しのチェーンを表示します。

一方で、デバッグモードがオフのとき、プロセスはつぎのように扱われます:

  • YAML 設定ファイルは1回だけ解析され、cache/config/ フォルダーに保存される PHP ファイルに変換されます。最初のリクエストの後のすべてのリクエストはYAMLファイルを無視して、代わりにキャッシュされた設定を利用します。結果として、リクエストの処理はより速くなります。
  • 設定を再処理できるようにするには、手動でコンフィギュレーションキャッシュをクリアしなければなりません。
  • リクエストを処理している間にエラーが起きると、問題の内部原因の説明は行われず、コード500 (Internal Server Error) のレスポンスを返します。

デバッグモードはフロントコントローラー内でアプリケーションごとに有効になります。リスト16-8で示されるように、デバッグモードは getApplicationConfiguration() メソッド呼び出しに渡される3番目の引数の値によってコントロールされます。

リスト16-8 - デバッグモードをオンにしたフロントコントローラーのサンプル (web/frontend_dev.php)

[php]
<?php

require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');

$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'dev', true);
sfContext::createInstance($configuration)->dispatch();

CAUTION 運用サーバーにおいて、デバッグモードを有効にすべきではありませんしデバッグモードが有効なフロントコントローラーも利用可能にすべきではありません。デバッグモードはページ配信を遅くするだけでなく、アプリケーション内部を公開する可能性があるからです。デバッグツールがデータベース接続情報を決して公開しないとしても、例外のスタックトレースは悪意を持った訪問者のための危険な情報で満ちています。

symfony の例外

デバッグモードで例外が起こる場合、symfony は問題の原因を見つけるために必要なすべての情報を含む便利な例外の警告を表示します。

例外のメッセージは明確に書かれ、一番問題のありそうなありそうな原因を参照します。これらのメッセージは問題を修正するための有望な解決方法を提供し、もっとも共通の問題のために、例外ページは例外に関する詳細な情報を持つ symfony 公式サイトのページへのリンクも含みます。図16-1で示されるように、メソッド呼び出しのフルスタックと一緒に、(構文をハイライトした状態で)例外ページは PHP コードのなかでエラーが発生した場所を表示します。問題が発生した最初の呼び出しをトレースできます。メソッドに渡された引数も表示されます。

NOTE 実際には symfony はエラーのレポートに関して PHP の例外に依存しています。たとえば404エラーは sfError404Exception によって起動します。

図16-1 - symfony アプリケーション用の例外メッセージのサンプル

symfony アプリケーション用の例外メッセージのサンプル

開発フェーズの期間において、symfony の例外はアプリケーションのデバッグに関してとても役に立ちます。

Xdebug エクステンション

PHP の Xdebug エクステンションによって Web サーバーに記録される情報量を広げることができるようになります。symfony は Xdebug のメッセージを独自のデバッグのフィードバック情報に統合するので、アプリケーションをデバッグするときにこの拡張機能を有効するのはよい考えです。拡張機能のインストール方法はプラットフォームに大いに依存します; 詳細なインストールガイドについては Xdebug のサイトを参照してください。Xdebug をインストールしたあとで、php.ini のなかで手動で有効にする必要があります。Unix 系に関しては、つぎの行を追加します:

zend_extension="/usr/local/lib/php/extensions/no-debug-non-zts-20041030/xdebug.so"

Windows プラットフォームでは、Xdebug を有効にするにはつぎの行を追加します:

extension=php_xdebug.dll

リスト16-9は php.ini ファイルに追加しなければならない Xdebug の設定の例を示しています。

リスト16-9 - Xdebug の設定サンプル

;xdebug.profiler_enable=1
;xdebug.profiler_output_dir="/tmp/xdebug"
xdebug.auto_trace=1             ; enable tracing
xdebug.trace_format=0
;xdebug.show_mem_delta=0        ; memory difference
;xdebug.show_local_vars=1
;xdebug.max_nesting_level=100

Xdebug モードを有効にするには Web サーバーを再起動しなければなりません。

CAUTION 運用環境では Xdebug モードを無効にすることを忘れないでください。さもなければすべてのページの実行速度がとても遅くなります。

Web デバッグツールバー

ログファイルは興味深い情報を含みますが、あまり読みやすいものではありません。もっとも基本的なタスクは、特定のリクエストに対して記録された行を見つけることですが、アプリケーションとイベントの長い履歴のイベントを同時に利用する複数のユーザを抱えている場合はとても扱いにくいです。このようなときに Web デバッグツールバーを使い始めます。

このツールバーは図16-2で示されるように、ウィンドウの右上コーナーにある、標準内容の上に重ねて設置された半透明のボックスとして現れます。これによって symfony のログイベント、現在の設定、リクエストオブジェクトとレスポンスオブジェクトのプロパティ、リクエストによって発行されたデータベースクエリの詳細情報にアクセスできます。

図16-2 - ウィンドウ右上のコーナーに存在する Web デバッグツールバー

ウィンドウ右上のコーナーに存在する Web デバッグツールバー

デバッグツールバーの背景色はリクエストの間に発行されたログメッセージのもっとも高いレベルに依存します。メッセージが debug レベルを渡さない場合、ツールバーの背景は灰色になります。単独のメッセージがerrレベルに達した場合、ツールバーの背景は赤色になります。

NOTE デバッグモードと Web デバッグツールバーを混同しないでください。デバッグツールバーはデバッグモードがオフでも表示できますが、その場合、表示される情報は少なくなります。

アプリケーションに対して Web デバッグツールバーを有効にするには、settings.yml をテキストエディタで開き web_debug キーを探します。運用環境とテスト環境において、web_debug のデフォルト値は falseなので、使いたい場合は手動で有効にする必要があります。リスト16-10で示されるように、dev 環境のデフォルト値は true に設定されています。

リスト16-10 - Web デバッグツールバーを有効にする (frontend/config/settings.yml)

dev:
  .settings:
    web_debug: true

表示されるとき、Web デバッグツールバーは多くの情報/インタラクションを提供します:

  • ツールバーの表示を切り替えるには symfony のロゴをクリックします。縮小されたとき、ツールバーはページのトップに設置された要素を隠しません。
  • 図16-3で示されるように、リクエスト、レスポンス、設定、グローバル、PHP プロパティの詳細を表示する "config" セクションをクリックしてください。一番上の行はデバッグモード、キャッシュ、PHP アクセラレータの存在など、重要なコンフィギュレーション設定の情報をまとめします(有効の場合は赤で無効の場合は緑色で表示されます)。

図16-3 - "config" セクションはリクエストのすべての変数と定数を示す

"config" セクションはリクエストのすべての変数と定数を示す

  • キャッシュが有効な場合、緑色の矢印がツールバー内に現れます。キャッシュに何が保存されているのであれ、この矢印をクリックすればページが再処理されます(ただしキャッシュはクリアされません)。
  • 図16-4で示されるように、現在のリクエストに対してログメッセージを表示するには "logs" セクションをクリックします。イベントの重要度にしたがって、これらは灰色、黄色、赤の行に表示されます。リストのトップに表示されるリンクを使うことでカテゴリによって表示されるイベントをフィルタリングできます。

図16-4 - "logs" セクションは現在のリクエスト用のログメッセージを表示する

"logs" セクションは現在のリクエスト用のログメッセージを表示する

NOTE 現在のアクションがリダイレクトの結果から由来するとき、最新のリクエストのログだけが logs & msgs のペインに現れます。ですので、ログファイルはよいデバッグのために今もなお不可欠です。

  • SQL クエリを実行するリクエストのために、データベースのアイコンがツールバーに表示されます。図16-5で示されるように、クエリの詳細内容を見るためにそのアイコンをクリックします。
  • クロックアイコンの右はリクエストを処理するために必要な合計時間です。Web デバッグツールバーとデバッグモードはリクエストの実行を減速するので、秒単位で測定することは避け、2つのページの実行時間の差だけに注意を払ってください。 図16-6で示されるように、カテゴリごとの処理時間の詳細な情報を見るには時計のアイコンをクリックしてください。symfony はリクエスト処理の特定の部分に費やされた時間を表示します。最適化するために役立つ情報は現在のリクエストに関連する時間だけなので、symfony コアに費やされた時間は表示されません。これらの時間が合計時間に追加されないのはそういうわけです。
  • ツールバーを隠すにはツールバーの右側後ろにある赤色のxをクリックします。

図16-5 - データベースクエリのセクションは現在のリクエストに対して実行されたクエリを表示する

データベースクエリのセクションは現在のリクエストに対して実行されたクエリを表示する

図16-6 - 時計アイコンはカテゴリごとの実行時間を表示する

時計アイコンはカテゴリごとの実行時間を表示する

SIDEBAR 独自のタイマーを追加する

symfony は設定、モデル、アクションとビューに費やされた時間を計算するsfTimerクラスを使います。同じオブジェクトを使うので、Web デバッグツールバー内で、カスタムプロセスを計測しほかのタイマーによって結果を表示できます。これはパフォーマンスの最適化にとり組むときにとても便利です。

特定のコードのフラグメントの時間測定を初期化するには、getTimer() メソッドを呼び出します。これは sfTimer オブジェクトを返し時間測定を始めます。時間測定を停止させるにはこのオブジェクト上で addTime() メソッドを呼び出します。経過時間は getElapsedTime() メソッドを通して利用可能であり、他方では Web デバッグツールバーのなかに表示されます。

[php]
// タイマーを初期化して計測を始める
$timer = sfTimerManager::getTimer('myTimer');

// 何かを行う
...

// タイマーを停止させて経過時間を追加する
$timer->addTime();

// 結果を取得する(まだタイマーが停止していない場合はここで停止させる)
$elapsedTime = $timer->getElapsedTime();

それぞれのタイマーに名前をつける利点は時間を測定するために何度も呼び出せることです。たとえば myTimer タイマーはユーティリティメソッドに使われます。このメソッドはリクエストごとに2回呼び出され、addTime() が最後に呼び出されたときに、getTimer('myTimer') メソッドへの2回目の呼び出しは計算された時点から時間測定を再起動させるので、時間測定は以前のものに追加されます。タイマーオブジェクト上でgetCalls()を呼び出すことでタイマーが起動した回数を得られこのデータは Web デバッグツールバーにも表示されます。

[php]
// タイマーの呼び出し回数を得る
$nbCalls = $timer->getCalls();

Xdebug モードにおいて、ログメッセージはより豊富です。すべての PHP スクリプトファイルと呼び出される機能はログに記録されるので、symfony は内部ログでこの情報を記録する方法を知っています。ログメッセージテーブルのそれぞれの行は二重の矢印ボタンを持ち、そのボタンをクリックすると関連リクエストに関する詳細な情報を見ることができます。何か間違っている場合、Xdebug モードは原因を見つけるための最大限の情報を提供します。

-

NOTE デフォルトでは Web デバッグツールバーは Ajax レスポンスと HTML ではない content-type を持つドキュメントに含まれません。そのほかのページでは、sfConfig::set('sf_web_debug', false) を呼び出すだけでアクションの範囲内から手動で Web デバッグツールバーを無効にできます。

手動でデバッグする

symfony のデバッグツールにアクセスできることはすばらしいことですが、独自のメッセージをログに記録できればより便利です。リクエストを実行している間にイベントかつ/もしくは値をトレースする作業を助けするために、symfony はアクションとテンプレートの両方からアクセスできるショートカットを提供します。

カスタムログメッセージは、通常のイベントのように Web デバッグツールバーと同様に、symfony のログファイルに現れます (リスト16-5はカスタムログメッセージ構文の例を示しています)。カスタムメッセージは、たとえばテンプレートからの変数の値をチェックするためのよい方法です。リスト16-11はテンプレートからの開発者のフィードバックのための Web デバッグツールバーを使う方法を示しています (アクションからも $this->logMessage() を利用できます)。

リスト16-11 - デバッグの目的のためにメッセージをログに挿入する

[php]
<?php use_helper('Debug') ?>
...
<?php if ($problem): ?>
  <?php log_message('{sfAction} been there', 'err') ?>
  ...
<?php endif ?>

図16-7で示されるように、err レベルを使うことでイベントがメッセージのリストのなかでわかりやすく見えることが保証されます。

図16-7 - カスタムログメッセージは Web デバッグツールバーの "logs" セクションに現れる

カスタムログメッセージは Web デバッグツールバーの "logs" セクションに現れる

Web の内容の外側から symfony を使う

たとえば、Eメールのジョブのバッチを起動させるもしくはプロセスが集中される計算を通してモデルを定期的に更新するために、symfony のすべてのクラスと機能にアクセスすることでコマンドライン (もしくは cron テーブルを通して) からスクリプトを実行したいことがあります。これを行うためのシンプルな方法は、symfony が適切に初期化されるようにするために、フロントコントローラーの最初のステップを再現する PHP スクリプトを作ることです。引数の解析と自動化されたデータベースの初期化を利用するために、symfony のコマンドラインシステムも利用できます。

バッチファイル

symfony を初期化するには数行の PHP コードが必要なだけです。PHP ファイルを作ればすべての symfony の機能を利用できます。たとえばプロジェクトの lib/ ディレクトリのもとで、リスト16-12のようなコードで始めます。

リスト16-12 - バッチスクリプトのサンプル (lib/myScript.php)

[php]
<?php

require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'dev', true);

// データベースレイヤーを使わないのであればつぎの行は削除する
$databaseManager = new sfDatabaseManager($configuration);

// ここにコードを追加する

これはフロントコントローラーの最初の行ととてもよく似ています (6章を参照)。これらの行は同じことをするからです: symfony を初期化し、プロジェクトとアプリケーションの設定を解析します。 ProjectConfiguration::getApplicationConfiguration メソッドはつぎの3つのパラメーターを必要とすることに注意してください:

  • アプリケーションの名前
  • 環境の名前
  • ブール値。デバッグ機能が有効かどうかを定義します

コードを実行するには、コマンドラインからスクリプトを呼び出します:

$ php lib/myScript.php

カスタムタスク

symfony を利用してカスタムコマンドライン機能を作成する別の方法は symfony のタスクを書くことです。cache:clear タスクと propel:build-model タスクのように、php symfony でコマンドラインから独自のカスタムタスクを実行できます。カスタムタスクはコマンドラインの引数とオプションを解析する機能は恩恵を受け、独自のヘルプテキストを埋め込み、既存のタスクを拡張できます。

カスタムタスクは sfBaseTask を拡張する単なるクラスで lib/task/ ディレクトリ、プロジェクトのルート、もしくは plugin ディレクトリに設置されます。このファイル名は Task.class.php で終わらなければなりません。リスト16-13はカスタムタスクのサンプルを示します。

リスト16-13 - タスクのサンプル (lib/task/testHelloTask.class.php)

[php]

class testHelloTask extends sfBaseTask
{
  protected function configure()
  {
    $this->namespace = 'test';
    $this->name = 'hello';
    $this->briefDescription = 'Says hello';
  }

  protected function execute()
  {
    // ここにコード
    $this->log('Hello, world!');
  }
}

execute メソッドで書かれたコードは以前のバッチスクリプトのように symfony のライブラリにアクセスできます。違いはカスタムタスクを呼び出す方法です:

$ php symfony test:hello

タスクの名前は (クラス名もしくはファイル名からではなく) protected された namespacename プロパティから由来します。タスクは symfony コマンドラインに統合されるので、つぎのコマンドを入力するだけでこのタスクは一覧に表示されます:

$ php symfony

タスクのスケルトンをあなた自身で書く代わりに、symfony の generate:task タスクを使います。このタスクは空のタスクを作り、豊富なカスタマイズ用のオプションを持ちます。つぎのコマンドを呼び出すことでこれらを確認してください:

$ php symfony help generate:task

タスクは引数 (必須パラメーターであらかじめ決められた順番で) とオプション (オプションで順不同のパラメーター) を受けとります。リスト16-14はこれらすべての機能を利用するより複雑なタスクを示しています。

リスト16-14 - より完全なタスクのサンプル (lib/task/mySecondTask.class.php)

[php]
class mySecondTask extends sfBaseTask
{
  protected function configure()
  {
    $this->namespace        = 'foo';
    $this->name             = 'mySecondTask';
    $this->briefDescription = 'Does some neat things, with style';
    $this->detailedDescription = <<<EOF
The [foo:mySecondTask|INFO]タスクはあなたの代わりに作業を実現するプロセスを管理します。
つぎの構文で呼び出します:

  [php symfony foo:mySecondTask frontend|INFO]

[verbose|COMMENT]オプションを使うことで詳細な出力を有効にできます:

  [php symfony foo:mySecondTask frontend --verbose=on|INFO]
EOF;
    $this->addArgument('application', sfCommandArgument::REQUIRED, 'The application name');
    $this->addOption('verbose', null, sfCommandOption::PARAMETER_REQUIRED, 'Enables verbose output', false);
  }

  protected function execute($arguments = array(), $options = array())
  {
    // ここにコードを追加する

  }
}

NOTE タスクがデータベースレイヤーにアクセスする必要がある場合、タスクは sfBaseTask の代わりに sfPropelBaseTask を継承します。タスクの初期化は追加のPropelクラスのロードを考慮します。つぎのように execute() メソッドでデータベースに接続することを始めることができます:

$databaseManager = new sfDatabaseManager($this->configuration);

タスクの設定が applicationenv 引数を定義する場合、タスクの設定をビルドするときに、これらは自動的に考慮されるので、タスクは databases.yml のなかで定義されたデータベースの接続を利用できます。デフォルトでは、generate:task 呼び出しよって生成されたスケルトンはこの初期化を含みます。

タスクシステムの機能に関する詳細な例に関しては、symfony の既存のタスクのソースを確認してください。

データベースを投入する

アプリケーションの開発過程において、しばし開発者はデータベース投入の問題に直面します。いくつかのデータベースのための固有の解決方法がわずかにありますが、オブジェクトリレーショナルマッピングを上回るものはありません。YAML と sfPropelData オブジェクトのおかげで、データのためにテキストファイルを書くことは CRUD インターフェイスを利用して手作業でレコードを入力するよりも作業が多いように見えますが、長期的には時間を節約できます。この機能がアプリケーションに対してテストデータを保存し投入するために便利であることがわかります。

フィクスチャファイルの構文

データファイルが data/fixtures/ ディレクトリに設置されているという前提のもとで、symfony はシンプルな YAML 構文に従うデータファイルを読み込むことができます。フィクスチャファイルはクラスによって整理され、それぞれのクラスはクラス名によってヘッダーとして導入されます。それぞれのクラスに対して、ユニークな文字列によってラベルづけされたレコードは fieldname: value のペアのセットによって定義されます。リスト16-15はデータベース投入のためのデータファイルの例を示しています。

リスト16-15 - フィクスチャファイルのサンプル (data/fixtures/import_data.yml)

Article:                             ## レコードを blog_article テーブルに挿入する
  first_post:                        ## 最初のレコードラベル
    title:       最初の記憶
    content: |
      長い間私は早く寝る習慣がありました。時々、ろうそくを消したとき、
      "寝ようとしています"と言わないようにすぐに目を閉じます。


  second_post:                       ## 2番目のレコードラベル
    title:       事態が悪化
    content: |
      ときどき彼は彼女が苦痛なしで、何かの事故で死んで欲しいと望みました。
      彼女は朝から晩まで人でごった返す街の大通りにいました。

symfony はラクダ記法のコンバータ (setTitle()setContent()) を利用してカラムキーをセッターメソッドに翻訳します。このことはテーブルが password フィールドを持たない場合でも password キーを定義できることを意味します。User オブジェクトを用いて setPassword() メソッドを定義するだけで、パスワードに基づいたほかのカラムを投入できます (たとえば、ハッシュバージョンのパスワード)。

主キーを定義する必要はありません。オートインクリメントのフィールドなので、データベースレイヤーが決定する方法を知っているからです。

created_at カラムを設定する必要はありません。その方法で名づけられたフィールドが作られたときに現在のシステム時間に設定しなければならないことを symfony が知っているからです。

インポートを起動する

propel:load-data タスクはYAMLファイルからデータベースにデータをインポートします。コンフィギュレーション設定はdatabases.yml ファイルから由来するので、実行するにはアプリケーションの名前が必要です。--env オプションを追加することで、オプションとして、環境名を指定できます(デフォルトではdev)。

$ php symfony propel:data-load --env=prod frontend

このコマンドは data/fixtures/ ディレクトリからすべてのYAMLフィクスチャを読み込むことが可能で、レコードをデータベースに挿入できます。デフォルトでは、このコマンドは既存のデータベースの内容を置き換えますが、--append を追加する場合、コマンドは現在のデータを削除しません。

$ php symfony propel:data-load --append frontend

呼び出しのなかで別のフィクスチャのディレクトリを指定できます。この場合、プロジェクトのディレクトリへの相対パスを追加します。

$ php symfony propel:data-load frontend --dir[]=data/myfixtures

リンクされるテーブルを使う

レコードを単独のテーブルに追加する方法を理解しましたが、外部キーを持つレコードを別のテーブルに追加するにはどうしたらよいでしょうか?主キーはフィクスチャデータに含まれないので、レコードを別のレコードに関連づけるには代わりの方法が必要です。

図16-8に示されるように、blog_article テーブルが blog_comment テーブルにリンクされる8章の例に戻ってみましょう。

図16-8 - データベースのリレーショナルモデルのサンプル

データベースリレーショナルモデルのサンプル

ここでレコードに付与されたラベルが本当に役立ちます。Comment フィールドを first_post の記事に追加するには、リスト16-16で示される行を import_data.yml データファイルに追加することだけが必要です。

リスト16-16 - 関連テーブルにレコードを追加する (data/fixtures/import\data.yml)

Comment:
  first_comment:
    article_id:   first_post
    author:       匿名
    content:      あなたの散文は冗長です。より短い文を書きましょう。

propel:load-data タスクは以前 import_data.yml で記事に付与したラベルを認識します。そして article_id フィールドを設定するために対応する Article レコードの外部キーを取得します。レコードのIDを見る必要はありません; これらのラベルでIDをリンクするだけです。この作業はよりシンプルになることはありません。

リンクされたレコードに対する唯一の制限は外部キーに呼び出されたオブジェクトをファイルのなかであらかじめ定義しなければならないことです; つまり、これらを1つずつ定義したように行います。データファイルは上から下へ解析されるので、レコードが書かれた順番が重要です。

これは第3のクラスを通して2つのクラスが関連づけされた、多対多のリレーションに対しても機能します。たとえば、1つの Article は多くの Authors を持ち、1つの Author は多くの Articles を持ちます。通常はこのために ArticleAuthor クラスを使うことができます。このクラスは article_id カラムと author_id カラムを持つ article_author テーブルに対応します。リスト16-17はこのモデルで多対多のリレーションを定義するためにフィクスチャファイルを書く方法を示しています。ここでは複数形のテーブル名が使われていることに注目してください。これは真ん中のクラスのために検索を起動させるものです。

リスト16-17 - レコードを多対多のリレーションで関連づけられたテーブルに追加する (data/fixtures/import_data.yml)

Author:
  first_author:
    name: John Doe
    article_authors: [first_post, second_post]

1つのデータファイルはいくつかのクラスの宣言を含みます。しかし、多くの異なるテーブルに対して多くのデータを挿入する場合、フィクスチャファイルが長すぎて簡単には操作できないことがあります。

propel:data-load タスクは fixtures/ ディレクトリのなかで見つかるすべてのファイルを解析するので、YAMLフィクスチャファイルをより小さなピースに分割することを妨げるものは何もありません。覚えておくべき大事なことは外部キーがテーブルに対して処理の順番を強制することです。これらが正しい順番で解析されたことを確認するには、ファイルのプレフィックスを序数にします。

100_article_import_data.yml
200_comment_import_data.yml
300_rating_import_data.yml

NOTE Doctrine は、自動的にタスクSQLステートメントの実行順序が正しくなるように配慮するので、ファイル名の指定を必要としません。

アプリケーションをデプロイする

symfony は2つのバージョンの Web サイトを同期化する省略記法のコマンドを提供します。多くの場合、これらのコマンドは開発サーバーからインターネットに接続されている最終的なホストに Web サイトをデプロイするために使われます。

インクリメンタルなファイル転送のために rsync を使う

FTP でプロジェクトディレクトリをまるごと転送する方法は最初のうちはよいですが、更新したアプリケーションをアップロードするときにわずかなファイルしか変更されていないのであれば FTP は理想的な方法ではありません。プロジェクト全体を再び転送する必要があるので時間と帯域の無駄遣いです。もしくは同じファイルが変更されたことを知っているディレクトリを見て、異なった修正日付を持つものだけを転送する方法がありますが、これも時間がかかる作業でエラーになりがちで、さらに、転送している間は Web サイトが利用できないもしくはバグだらけになります。 symfony によってサポートされる解決方法は SSH レイヤーを通した rsync による同期化です。Rsync は速いインクリメンタルなファイル転送を提供するコマンドラインユーティリティでオープンソースです。変更されなかったファイルはホストに転送されません。インクリメンタルな転送によって、修正されたデータだけが送られます。ファイルの一部だけが変更された場合は、差分が送られます。主な利点は rsync の同期化は小さな量のデータのみ転送するのでとても速いことです。

symfony はデータ転送を安全にするために rsync の上に SSH を追加します。より多くの商用ホストが自身のサーバーの上でファイルのアップロードを安全にするために SSH トンネルをサポートしていますが、これはセキュリティの欠陥を回避するためのよい慣習です。

symfony によって呼び出された SSH クライアントは config/properties.ini ファイルから接続設定を使います。リスト16-18は運用サーバーのための接続設定の例を示しています。同期化をするまえに独自の運用サーバーの設定をこのファイルに書きます。独自の rsync コマンドラインパラメーターを提供する単独のパラメーター設定を定義することもできます。

リスト16-18 - サーバー同期化のためのサンプルの接続設定 (myproject/config/properties.ini)

[symfony]
  name=myproject

[production]
  host=myapp.example.com
  port=22
  user=myuser
  dir=/home/myaccount/myproject/

NOTE 運用サーバー (プロジェクトの properties.ini ファイルで定義されるホストサーバー) と運用環境 (運用サーバーで使われるフロントコントローラーと設定で、アプリケーションの設定ファイルに参照される) を混同しないでください。

SSH を通して rsync を行うにはいくつかのコマンドツールが必要で、アプリケーションの生涯において同期化の作業は多くの時間を占めます。幸いにして、symfony はたった1つのコマンドだけでこのプロセスを自動化します:

$ php symfony project:deploy production

このコマンドはドライモードで rsync コマンドを立ち上げます; すなわち、同期化しなければならないファイルを表示しますが、実際には同期化しません。同期化を行いたい場合、--go オプションを追加して明確にリクエストする必要があります。

$ php symfony project:deploy production --go

同期化のあとで運用サーバーでキャッシュをクリアすることを忘れないでください。

TIP 運用サーバーへのデプロイを行う前に、最初にcheck_configuration.phpを使って環境をチェックしておきましょう。このユーティリティはdata/bin フォルダーで見つかります。これは symfony の要件に対するあなたの環境をチェックします。どこからでもこのスクリプトを立ち上げることができます:

[php]
$ php /path/to/symfony/data/bin/check_configuration.php

このユーティリティをコマンドラインから利用できるとしても、PHP はコマンドラインインターフェイスと Web のために異なる php.ini 設定ファイルを利用できるのでこのユーティリティを web フ ォルダーのルートにコピーして web フォルダーから起動させることを強くお勧めします。

-

SIDEBAR アプリケーションの公開準備は終わりましたか?

アプリケーションを運用サーバーにデプロイするまえに、公開の準備ができていることを確認します。アプリケーションを実際にデプロイすることを決心するまえにつぎの項目を実施したことを確認してください:

エラーページはアプリケーションの外見に合わせてカスタマイズできます。500エラーのページ、404エラーのページとセキュリティのページをカスタマイズする方法は19章を参照してください。サイトが利用できないときに表示されるページをカスタマイズする方法に関してはこの章の「アプリケーションを運用する」のセクションを参照してください。

default モジュールは settings.ymlenabled_modules 配列から除外されるので symfony のページが誤って表示されることはありません。

セッションハンドリングのメカニズムはクライアントサイドクッキーを利用し、このクッキーのデフォルトの名前は symfony です。アプリケーションをデプロイするまえに、アプリケーションが symfony を利用している事実を明言しないようにするためにおそらくこのデフォルトの名前をリネームすることになります。factories.yml ファイルでクッキーの名前をカスタマイズする方法は6章を参照してください。

robots.txt ファイルはデフォルトでは空でプロジェクトの web/ ディレクトリに設置されます。 Web サイトの閲覧できる部分と避けたほうがよい部分の情報を Web スパイダーとほかの Web ロボットに知らせるためにカスタマイズします。たいていの場合、このファイルは特定の URL の空間にインデックスとして記録されないようにするために使われます。たとえば、リソースが集中しているページ、インデックスに必要のないページ (バグのアーカイブなど)、もしくはロボットが捕まる無限ループの URL 空間などです。

ユーザが最初にブラウザーでアプリケーションを閲覧するとき、モダンブラウザーは、アドレスバーとブックマークフォルダーのなかのアイコンでアプリケーションを表すために favicon.ico ファイルをリクエストします。このようなファイルを提供することでアプリケーションの外見が完全なものになり、多くの404エラーがサーバーのログに表示されないようになります。

不適切なファイルを無視する

運用ホストで symfony のプロジェクトを同期化する場合、いくつかのファイルとディレクトリを転送すべきではありません:

  • バージョン管理ツールのすべてのディレクトリ (.svn/CVS/など) とそれらの内容は開発と統合のためだけに必要です。
  • エンドユーザーが開発環境用のフロントコントローラーを利用できる状況にしてはなりません。アプリケーションを利用するときにこのフロントコントローラーを通してデバッグツールとロギングツールも利用できるのでアプリケーションの動作が遅くなりアクションのコア変数に関する情報が公開されてしまいます。これは一般利用者からは遠ざけておくべき類のものです。
  • 同期化するたびにプロジェクトの cache/ ディレクトリと log/ ディレクトリを削除してはなりません。これらのディレクトリは同様に無視しなければなりません。存在するのであれば stats/ ディレクトリはおそらく同じ方法で扱うことになります。
  • ユーザーによってアップロードされたファイルは転送されません。symfony のプロジェクトのよい習慣の1つはアップロードされたファイルを web/uploads/ ディレクトリに保存することです。このことによって1つだけのディレクトリを指定するだけでこれらのファイルを同期化の対象から除外できるようになります。

rsync の同期化からファイルを除外するには、myproject/config/ ディレクトリの下にある rsync_exclude.txt ファイルをテキストエディタで開き編集します。それぞれの行はファイル、ディレクトリ、パターンを含みます。symfony のファイル構造は論理的に整理され、同期化から手動で除外するファイルもしくはディレクトリの数を最小化するために設計されています。リスト16-19で例をご覧ください。

リスト16-19 - rsync の除外設定のサンプル (myproject/config/rsync_exclude.txt)

# Project files
/cache/*
/log/*
/web/*_dev.php
/web/uploads/*

# SCM files
.arch-params
.bzr
_darcs
.git
.hg
.monotone
.svn
CVS

NOTE 開発環境で cache/ ディレクトリと log/ ディレクトリを同期化してはなりませんが、これらのディレクトリは少なくとも運用サーバーでは存在します。myproject/ プロジェクトのツリー構造内部でこれらのディレクトリが存在しない場合、手動でこれらのディレクトリを作ってください。

運用アプリケーションを運用する

運用サーバーでもっとも共通で使われるコマンドは clear:cache です。(たとえば project:deploy タスクを呼び出すなど) symfony もしくはプロジェクトをアップグレードする、または運用環境で設定を変更するたびにこのコマンドを実行しなければなりません。

$ php symfony cache:clear

TIP コマンドラインインターフェイスが運用サーバーで利用できない場合、cache/ フォルダーの内容を手動で削除することでキャッシュをクリアできます。

一時的にアプリケーションを無効にできます。たとえば、ライブラリや大量のデータのアップグレードが必要なときです。

$ php symfony project:disable APPLICATION_NAME ENVIRONMENT_NAME

デフォルトでは、無効にされたアプリケーションは $sf_symfony_lib_dir/exception/data/unavailable.php ページを表示しますが、プロジェクトの config/ ディレクトリのなかで独自の unavailable.php ファイルを作る場合、symfony はそれを代わりに使います。

project:enable タスクはアプリケーションを再び有効にしてキャッシュをクリアします。

$ php symfony project:enable APPLICATION_NAME ENVIRONMENT_NAME

CAUTION project:disable は、現在はsetting.ymlにあるcheck_lockパラメータがtrueにセットされていないと、効果がありません。

-

SIDEBAR キャッシュをクリアするときに利用できないページを表示する

settings.yml ファイルのなかの check_lock パラメーターを true にセットする場合、キャッシュがクリアされている最中に symfony はアプリケーションをロックし、キャッシュが最後にクリアされるまえに到達したすべてのリクエストはアプリケーションが一時的に利用できないことを伝えるページにリダイレクトされます。キャッシュが巨大な場合、これをクリアするための遅延は数ミリ秒よりも長くなることがあり、サイトのトラフィックが高い場合、これはお勧めの設定です。この利用できないことをお知らせするページは symfony の disable タスクを呼び出すときに表示されるものと同じです。check_lock パラメーターはデフォルトで無効です。わずかですがパフォーマンスにネガティブな影響があるからです。

project:clear-controllers タスクは運用環境で動作しているもの以外の web/ ディレクトリのすべてのコントローラーをクリアします。 開発環境のフロントコントローラーを rsync_exclude.txt ファイルに含めないのであれば、これはバックドアがアプリケーションの内部を暴露しないことを保証します。

$ php symfony project:clear-controllers

プロジェクトのファイルとディレクトリのパーミッションは SVN リポジトリからチェックアウトすると壊れる可能性があります。たとえば、log/ ディレクトリと cache/ディレクトリのパーミッションを0777に変更したいのであれば(これらのディレクトリは正しく動作させるためにフレームワークが書き込みできるようにする必要があります)、project:permissionsタスクはディレクトリのパーミッションを修正します。

$ php symfony project:permissions

まとめ

PHP のログと symfony を組み合わせることで、アプリケーションのモニタリングとデバッグ作業が楽になります。開発期間において、デバッグモード、例外、Web デバッグツールバーは問題をつきとめるための助けになります。よりデバッグ作業を楽にするにはカスタムメッセージをログファイルもしくはツールバーに挿入します。

開発フェーズと運用フェーズにおいて、コマンドラインインターフェイスはアプリケーションの運用を円滑にする膨大な数のツールを提供します。これらのツールのなかで、データの投入、同期化のタスクは多くの時間を節約します。