symfony

Entries

Status Code を返す / Raw HTTP Request Body を見る

Date
2007-10-22 (Mon)
Category
symfony

細かいネタをいくつか。

API を作る時には Status Code を正しく返す

外側に公開するときはなおさら、内側で使う時でも、API を作る時に HTTP ステータスコードを正しく使おう というのは最近の Web 開発のトレンド、なんて云うと怒られるかな。少なくとも、僕は暫く前から気を遣って居ます。(中には行儀の悪い Flash などは 404 を返すと、HTTP Response Body にアクセスできないとかブツブツ…)

ともかく。Symfony を使って、HTTP Response Header 中の Status Code にアクセスするにはどうすれば良いのでしょうか?非常に簡単で、任意のアクションクラスの中で以下のようにします。

$this->getResponse()->setStatusCode(400);

あれ?404 って何だっけ?という向きに、Status Code チートシート を用意しました。ご自由にお使いください。(尤も HTTP/1.1: Status Code Definitions から H3 要素を XPath で切り出して、Word で整形しただけの単純なものですが。)

連想配列化されていない Raw HTTP Request Body を見る

PHP で HTTP POST を受け付けると、$_POST という 定義済の変数 に連想配列として自動的に格納されます。便利ですね。ただ時として、POST や PUT あるいは その他拡張された HTTP メソッド(動詞) で、連想配列化される以前の生の文字列を見たいときもあるかと思います。これは Symfony に限らず PHP 入出力ストリーム を使って、以下のようにします。

$raw_input = null;
$stream = f o p e n("php://input", "r");
if ($stream) {
  $raw_input = s t r e a m _ g e t _ c o n t e n t s($stream);
  f c l o s e($stream);
}
else
{
  throw new Exception("Input stream open failure.");
}

今気がついたけど、『enctype="multipart/form-data" に対しては 使用できません。』ってあるね…という事は、ファイルアップロードを独自処理、とかは出来ないってことか…そんな条件分けをする意味が正直理解しがたいが…まぁしたくないけどね。

以上細かいネタでした。あ〜 MovableType だと f o p e n とか特別な単語を書くと、エラーが出るようになってる…上記変な書き方になってるのはそのせいです。

Symfony Tips: Error, Output Escaping

Date
2007-10-18 (Thu)
Category
symfony

今日は手短に symfony 設定ネタなどを。

dev 環境で標準出力への抑制

前にも『Symfony で Json を出力』で少し書きましたが、僕は symfony を使って XML やら JSON やら YAML やらをよく生成します。PHP を database の出力用 template にするわけですね。あとはがりがり javascript とか、flash なんかの client side アプリを書いていきます。効率の面から云うと PHP というかサーバ側でもう少し連携というか framework の中から出来る方がいいのかもしれないけれども、まだそこまでは至って居ません。

ともかく。僕みたいに symfony を使って XML やら JSON やら YAML を吐き出させる人にとって、dev 環境が(標準)出力に混ぜる PHP_NOTICE と PHP_WARNING が結構しんどいです。特に XML 文にエラーが混じると、正書式でなくなりプロセスが止まります。出力を止めるには…

/apps/APP_NAME/config/settings.yml

dev:
 .settings:
   # E_ALL | E_STRICT = 4095
   # error_reporting:        4095

と、error_reporting をコメントアウトします。もちろん、これでも php.ini に設定したエラーログには出ます。

Output Escaping の設定

symfonyで開発日記 : symfony tips vol.1 で学びました。細かいですが直ぐに役に立つ事ばかりで、非常に興味深いプレゼンです。是非、video と配布資料をご覧になる事をお勧めします。というわけで、その中で僕もこれで知った、Output Escaping のやり方を備忘録がてら、紹介します。

設定は以下のように。

/apps/{app}/config/settings.yml
all:
  .settings:
    escaping_strategy:      both
    escaping_method:        ESC_ENTITIES

こうすると、template で出力される変数が全て HTML 実体参照に変換されます。

どうしても変換したくない場合(例えば上述の XML を直接出したい、とか)は2つ方法があります。

String 型の変数の場合

要するに echo で直接出せるものですね。例えば actions.class.php で

public function executeFoo ()
{
  $this->output = "& is ampersand.";
}

のようにあると、fooSuccess.php では

<p>Escaped: <?php echo $output; ?> </p>
<p>Original: <?php echo $sf_data->getRaw('output'); ?> </p>
とします。

Object から method を呼び出す場合

ちょっとわかりにくいですね。僕もあまりしないんですが。例えば actions.class.php で

public function executeFoo ()
{
  $this->obj = new SomeObject();
}

のようにあって SomeObject インスタンスは echo() という method で、引数を直接出力できるとします。fooSuccess.php では

<p>Escaped: <?php $obj->echo('& is ampersand.'); ?></p>
<p>Original: <?php $obj->echo('& is ampersand.', ESC_RAW); ?></p>

という風に、引数の最後に ESC_RAW というおまじないを入れるといいそうです。


ところで、今日は Symfony が公開されて 2年目の誕生日だそうです。Bonne Anniversaire! お誕生日おめでとう!

自分で書いた SQL から populated Object を作る

Date
2007-10-17 (Wed)
Category
symfony

Symfony の採用する Propel O/R マッパは非常に便利で強力ですが、複雑にネストした query など、効果が発揮できない場面はあります。あるいはそれから生成される SQL の効率が悪いとか。そんなことにいちいち気を遣う僕の頭がおかしいのかもしれないけれど、SQL を直に書いていた頃に出来た事が出来ないのは、Framework を使う理由の本末転倒だなとは感じていました。僕が知らないだけなのかもしれない。ここにある Criteria Object の Reference にある事以上をうまく解説してるサイトとかあったら誰か教えてください。

という訳で、生の SQL Query を Symfony 標準の Propel+Creole を使って使う方法をまとめてみたいと思います。Askeet Tutorial はやや古くなりつつありますが、First Reference として引いておきましょう。

symfony Advent Calendar: symfony advent calendar day thirteen: Tags

symfony アドベントカレンダー 13日目: タグ

僕が最初によくわかってなかったのは、O/R マッパがあるのに生の SQL を使うのはなんだか負けた気がする、と云うか、O/R マッパを使えば、もう SQL を使わなくていいのかと思ってた。まともに考えて、SQL を Object に抽象化する、って相当難しそうだし、めんどくさいものだと。でもそれは偏見で、O/R マッパって出来ることは凄い単純で出来ることも限られてる。そのかわり理解しやすい、自動化しやすい、他のソフトウェアから扱いやすい、ということかしらん。

多分肝心なのは、既に SQL を知ってる人は、DB とお話しするのにオブジェクト O/R マッパを使う必要は必ずしも無くて、パファーマンスが必要なときとか、SQL が得意なことをする時は生の SQL を使うべき、だと云うこと。難しくないし、そんなにめんどくさくもない、ということ今回のエントリで解説してみたい。

というわけでやってみましょう。ここでは何か target に tag を付けて、分類したいとします。tag を正規化すると target と tag は m:n な関係になりますね。schema.yml は以下。

propel:
  _attributes:      {defaultIdMethod: native, noxsd: true}

target: _attributes: { phpName: Target } id: type: integer required: true primaryKey: true autoIncrement: true content: type: varchar(64) description: "Target content"
tag: _attributes: { phpName: Tag } id: type: integer required: true primaryKey: true autoIncrement: true name: type: varchar(64) description: "Tag name"
target_tag_joint: _attributes: { phpName: TargetTagJoint } pkey: type: integer required: true primaryKey: true autoIncrement: true tag_id: type: integer foreignTable: tag foreignReference: id target_id: type: integer foreignTable: target foreignReference: id

Fixture として Fortune 500 をネタにしましょう。fixtures/data.yml は最後に付けておきます。実際あんまりいい例ではなかったか…

こんな時に、例えば一つの tag から target object の集合が欲しくて、普通に Criteria を使うと…

$tag_name = 'car';

$c = new Criteria(); $c->add(self::NAME, $tag_name); $tag_obj = TagPeer::doSelectOne($c);
$c = new Criteria(); $c->add(self::TAG_ID, $tag_obj->getId()); $arr = self::doSelect($c);
$result = array(); foreach($arr as $joint) { $c = new Criteria(); $c->add(TargetPeer::ID, $joint->getTargetId()); $result[] = TargetPeer::doSelectOne($c); }
return $result;

こんな感じでしょうか。前述の fixture くらいの小さな集合だったら、全く問題ないレヴェルのクエリですが、もし、例えば、一つの tag から 100 の target が引けるとすると、ちょっとシンドイでしょうね。なにせ 100 回クエリが投げられる訳ですから。

SQL 言語を理解している人には当たり前過ぎかもしれませんが、この問題は以下のような入れ子になった INNER JOIN を使うと、1 回の SQL クエリで同じ集合を得ることが出来ます。(IN 述語を使う方がいいって?後述します)

SELECT 
  TAR.id, TAR.content 
FROM 
  target AS TAR 
  INNER JOIN (
    SELECT 
      TTJ.target_id 
    FROM 
      target_tag_joint AS TTJ 
      INNER JOIN ( 
        SELECT 
         TAG.id 
        FROM 
          tag AS TAG 
        WHERE 
          TAG.name = ? 
      ) AS T ON TTJ.tag_id = T.id 
  ) AS RES ON TAR.id = RES.target_id ; 

(Join を使った query は禁止とか、アクセスの桁があがると Join は遅いからこういう正規化はダメ、というのは別の話題、ということで。)

このクエリを実行すると、ResultSet オブジェクトになり、例えば配列として結果集合を取得できます。ただ他の部分との整合性として、特別にこの method だけは配列としてアクセス、というのもすこしおかしいし難しい。どうせなら、Criteria を使うのと同じく O/R マッピングをしたい。populate してみます。

$con    = Propel::getConnection();
$stmt   = $con->prepareStatement($query);
$stmt->setString(1, $_tag);
$rs     = $stmt->executeQuery(null, ResultSet::FETCHMODE_NUM);

$result = array(); $cl = Propel::import(TargetPeer::getOMClass());
while ($rs->next()) { $obj = new $cl(); $obj->hydrate($rs, 1); $result[] = $obj; }
return $result;

これは、もうまるまま Base***Peer の populateObjects メソッドのコピーです。hydrate の第二引数の int は、Base*** の hydrate のソースを読めばわかりますが、ResultSet の index を 1 から降り直したもの…訳が分かりませんね。ソースを追ってみましょう。以下、symfony propel-build-model で生成された BaseTarget.php から引用します。

public function hydrate(ResultSet $rs, $startcol = 1)
{
  try 
  {
    $this->id      = $rs->getInt($startcol + 0);
    $this->content = $rs->getString($startcol + 1);
    $this->resetModified();
    $this->setNew(false);
    return $startcol + 2;
  } 
  catch (Exception $e) 
  {
    throw new PropelException("Error populating Target object", $e);
  }
}

propel-build-model した時に、schema.yml をパースして、ResultSet を指数配列で受け取るようにして(ResultSet::FETCHMODE_NUM)順番を降っているだけだったのですね。だからこれをうまく使えば一回のクエリで2つのオブジェクトを受け取る事も可能なわけです。

まとめ

Query さえ書ければ、populate の部分はほとんど写経でも動くはずです。大事な点は、Prepared Statement を使って、ちゃんと値を代入する事と、executeQuery(null, ResultSet::FETCHMODE_NUM) のようにして指数配列で ResultSet を受け取る事です。

Hydrate も仕組みを理解すれば便利に使えます。今回の例でいえば、Target のオブジェクトに 1:n な関係の値が入る場合(例えば各 Target 項目に複数毎写真を付けられる、とか)、一つの row に target と picture が一緒にかえるような SQL を書いて、一気に hydrate する、とか。

捕捉: IN 述語

今回僕が挙げたクエリの INNER JOIN を IN を使って書き直すと、こんな感じに。

SELECT 
  TAR.id, TAR.content 
FROM 
  target AS TAR 
WHERE 
  TAR.id 
  IN ( 
    SELECT 
      TTJ.target_id 
    FROM 
      target_tag_joint AS TTJ 
    WHERE 
      TTJ.tag_id 
      IN (
        SELECT 
          TAG.id 
        FROM 
          tag AS TAG 
        WHERE 
          TAG.name = ? 
      )
  ) ; 

先に INNER JOIN を使ったのは…僕がそれに馴れているからで深い意味はないんですが、書いてみて、一緒に走らせてみると、項目が多くなれば INNER JOIN を使ったクエリの方が、メモリは食いますがほんのちょびっと速くなる感じでした。

Symfony snippets: Sub-selects using Propel というページに IN 述語を使ったサブクエリを Criteria オブジェクトを使って投げる、という例があります。どっちにしても、Criteria で今回のような 2 段 nest をやる方法はわかりませんし、主題としては Populate Object は難しくないYO! という事なのでよしとします。

続きに fixtures/data.yml を挙げておきます。

Continue reading

Symfony で Json を出力

Date
2007-09-05 (Wed)
Category
symfony

Symfony にも、Prototype と連携する plugin や、Askeet のサンプルでも出てくる Symfony 同梱の helper などがあります。でも最終的には結局しっくりこないで、自分でみっちりチューニングした javascript を書いてしまうことが僕は多いです。。そんな時に、僕だけかもしれないけれど、Symfony で JSON 形式の書き出しをする事があります。Symfony 側、例えば app.yml に設定を集中して書いて interface を司る javascript でも同じ設定を使い回すため、とか。

で、今日すこしハマったので、tip として挙げておきます。どういう理由なのか、自分ではさっぱりわからなかったのだけれど…

例えば、以下のような Symfony の View があります。(抜粋)

var Setting = {};
<?php foreach ($arr as $key => $val): ?>
Setting.<?php echo $key; ?> = '<?php echo $val; ?>';
<?php endforeach; ?>

これを <script src="〜〜"></script> として読み込ませてみるんだけど、javascript runtime が解釈してくれない。。ソースを browser で見て、普通にテキストとしてコピペすると、使えるのに…全体が読み込まれないならまだわかるんだけど、一部だけってのも不可解すぎた。

諦めかけた時に、ふと、ページの content-type を text/javascript から、application/x-javascript に変えてみたら、全てのオブジェクトが認識されるようになりました。/apps/modules/MODULE/config/view.yml に以下のように書きました。

default:
 has_layout:     off
 http_metas:
   content-type: application/x-javascript

white space の扱い、とかそういうのだと思うけれど。こういう事もありますよ、という事で。

Windows での logrotate

Date
2007-08-23 (Thu)
Category
symfony

Unix には logrotate というログの管理をやってくれる便利なツールがあります。管理というとあれですが、一つの機能として、アプリケーションが取り溜めた古い log ファイルを新しいファイルに切り替えて、ある程度古くなったものは廃棄します。これが revolver を rotate(回転)するような感覚なんでしょうか。

昨日の続きなんですが、Windows 環境で、そのようなユーティリティを探していました。と、そこで、Apache 自身に付属するツールで、rotatelogs.exe というのがあるのを知りました。どれどれどんな使い方…と思って調べてみると…

Apache Rotatelogs.exe for Windows Server

Conclusion: It is unusable and dangerous (it will eat up all your memory/file handlers ...etc...). You can not even use rotatelogs.exe on 4+ sites, Apache will lockup when starting (tested on Apache 2.2.0).

だそうです。バギーじゃなぁ…で、同じサイトで以下のような記事も見つけました。

Apache Better Log Rotation mod_log_rotate

Using the module mod_log_rotate, the log rotation is handled by the server process so you save on the process count and file descriptors. I've compiled that code for windows using VC++ 6 for Apache 2.0.x and Apache 2.2.x. You can find the windows binaries of mod_log_rotate at the bottom of this article.

アイデアとしてはアリですが、どうせ logrotate するなら、php のログもやりたいと思っていたのでちょっともの足りません。

で、暫く後に Apache+Windows2000小技集 というページを見つけて、Windows 付属の Shell script を使って、処理するという方法に落ち着きました。kamo 氏は2つの方法を示していますが、『日付をファイル名に持たせて保存する方式』にすこし改良を加えて使うようにしました。

' logArc.vbs
' original idea: http://kamoland.com/comp/apache_win2k.html

apacheExe = """C:\Program Files\Apache Software Foundation\Apache2.2\bin\httpd.exe""" stopApache = apacheExe & " -w -n ""Apache2"" -k stop" startApache = apacheExe & " -w -n ""Apache2"" -k start"
Set logs = CreateObject("Scripting.Dictionary")
logs.Add "main-access", "C:\var\logs\httpd\httpd-access.log" logs.Add "main-error", "C:\var\logs\httpd\httpd-error.log" ... logs.Add "php-error", "C:\var\logs\php\php-error.log"
archive = "C:\var\logs\archive\" today = Fmt( year(now), 4) & Fmt( month(now), 2) & Fmt( day(now), 2)
Dim wsh Dim fso, fo
Set wsh = WScript.CreateObject("WScript.Shell")
On Error Resume Next wsh.Run stopApache, 1, TRUE
If Err.Number = 0 Then
For Each name In logs.Keys Set fso = WScript.CreateObject( "Scripting.FileSystemObject" ) Set fo = fso.GetFile( logs(name) ) fo.move( archive & today & "_" & name & ".log" ) If Err.Number <> 0 Then 'MsgBox Err.Description & "(" & Err.Source & ")" Exit For End If Err.Clear Next
End If
wsh.Run startApache, 1, TRUE
Function Fmt( num, digit) Fmt = Right( String(digit, "0") & num, digit) End Function

暫く職場で VBScript on ASP をやっていたのが効いているのか、結構すぐ出来ました。VB なんて知らねぇよ、という人のために少し解説すると…

apacheExe   = """C:\Program Files\Apache Software Foundation\Apache2.2\bin\httpd.exe"""
stopApache  = apacheExe & " -w -n ""Apache2"" -k stop"
startApache = apacheExe & " -w -n ""Apache2"" -k start"

ここでは "" (ダブルクォート2つ) や """ (ダブルクォート3つ) が出てきます。僕は Wiki? 下と思いましたが… Java など一般的には、"~~~" のようにダブルクォートで囲まれた間は文字列として処理されます。VB でも同じなんですが、そのダブルクォートでの文字列表現内で、文字としてダブルクォートを使いたい場合どうするか。同じく Java など一般的には \ (バックスラッシュ) を escape 記号として "~~\"~~" のように使う場合が多いんですが、Windows でバックスラッシュには、directory 区切り記号、という大事な役割があり使えません(たぶん)?そこで "" というふうにダブルクォートを2つ使うんですね。最初の3つは、文字列表現の始まりを示すのと、エスケープされたダブルクォートが連なっている、ということですね。

logs.Add "main-access",   "C:\var\logs\httpd\httpd-access.log"
logs.Add "main-error",    "C:\var\logs\httpd\httpd-error.log"
...
logs.Add "php-error",     "C:\var\logs\php\php-error.log"

ここの部分で、一緒に処理するファイル群を指定しています。どんなファイルでも OK 。もちろん、ファイルロックが掛けられているとダメですが…

On Error Resume Next
wsh.Run stopApache, 1, TRUE

If Err.Number = 0 Then
For Each name In logs.Keys Set fso = WScript.CreateObject( "Scripting.FileSystemObject" ) Set fo = fso.GetFile( logs(name) ) fo.move( archive & today & "_" & name & ".log" ) If Err.Number <> 0 Then 'MsgBox Err.Description & "(" & Err.Source & ")" Exit For End If Err.Clear Next
End If

エラー処理を含めた、ファイル処理ですが、On Error Resume Next とすることで、エラーが起きても、プログラムが絶対止まらないようになります。エラーがおきそうな処理のすぐ下で、エラーが起きてないかを確認してから進めないと行けない、という鬼仕様。。If Err.Number = 0 Then はエラーが起きてない時(= (イコールサイン一つ) は普通、代入に使われ、if 節などの比較コンテキストではなんと比較演算子として処理されます。ま、Macromedia Director の Lingo も同じでしたね…)、If Err.Number <> 0 Then はエラーが起こった時に次のブロックが実行されます。

Symfony を Apache on Windows で使う

Date
2007-08-22 (Wed)
Category
symfony

もう2年も前になってしまうのですが、とあるグループのイントラネットで動くタスクマネジメントソフトを作った事がありました。そこの方々とは、ご縁あってその後もいろいろおつきあいさせてもらっています。この仕事ではマネージャが居てその人がうまくやってくれている、というのが大きいんだと思うんですが。

ともかく。その僕の作ったマネジメントソフトを書き直す作業を暫くやっていました。その当時としてはまだ珍しかった? Ajax でデータベースからデータを取り出して云々という感じ。まだまだ未熟だった僕は、自作の XML 生成クラスとか作っていたし、見た目にはほとんど気がつかわれなく、Javascript はコピペかつ何をしているのかよくわからない…それはそれは酷いコードを書いていました(一方データベースの設計は悪くなかった、コラム/テーブルの命名はまぁ old fashion だったけれど)。

せっかくなので、全て Symfony を使って書き直してみました。たしか2ヶ月くらいは掛かっていたと思うのだけれど、今回は3週間くらいかな。これなら拡張も容易に出来そうだし、結構満足しています。

ところが最後の最後でつまずいた、というか前に一度やって、つまづくポイントが多かったのをすっかり忘れていた、Windows での Symfony の deploy を今回少しまとめておきたいと思います。

前提

  • Apache のインストール
  • MySQL のインストール
  • PHP のインストール

Apache はそれこそインストーラ一発だし、MySQL は名前付きパイプなど、変な事をしなければ、インストーラが全てやってくれます。PHP だけはファイルレイアウトなどに気を遣うかもしれませんが、まぁそれはまた別の機会にでも。php.exe (CLI) のある directory へ Path が通っていた方が便利ですが、それも必須ではありません。

手順

  1. pear のインストール
    Windows では、go-pear.bat という起動すればいいだけ、のはずなんですが、環境にあわせて手直しする必要がありました。こんな感じに。
    @ECHO OFF
    set PHP_BIN=php.exe
    set PHP_INI=C:\etc\php\php.ini
    %PHP_BIN% -c %PHP_INI% -d output_buffering=0 PEAR\go-pear.phar
    pause
    まずこの変更は、インストール完了時に WARNING を出させないためです。なくても大丈夫、のはず(もちろん PEAR のインストールされる先が、include_path に組み込まれていなければ、WARNING はでます)。
    あと僕は global 環境変数、というのがキライです。バージョンアップをする時、十分な引き継ぎなく誰かのサポートをする時、global 環境変数に何か書いてあったりすると、非常に厄介。なので、PHP も PEAR にも registry は使わせません。確認されますが、断固として Local インストールです。
  2. pear.ini の変更
    Local 派はここでも少し手を加えます。php.iniの場所をpear.ini 内に書いておかないと、extension などがうまく動きません。
    pear.ini は PHP の Array を serialize したものです。JSON ほど簡単ではないかもしれないけれど、馴れれば手で書けます。大事なのはその array に、php_ini というキーで、php.ini への file path を書き入れる事です。
    #PEAR_Config 0.9
    a:11:{
    s:15:"preferred_state";s:6:"stable";
    s:7:"php_ini";s:23:"C:\etc\php\php.ini";
    ....
    }
  3. C:\path\to\pear -c C:\etc\php\pear.ini DO_WHATEVER
    という感じで、-c オプションで、pear.ini へのパスを渡してやります。そうすることで Local 設定を有効にして、作業できます。あとは unix と同じ、channel-discover / install で完了

あとは、Symfony の project を(絶対パス指定)symfony コマンドで作ってから、同じ directory に以下のような symfony.bat を作っておくと、いちいち full path を打たなくてすみます。

@echo off
SET PHP_COMMAND=C:\php\php.exe SET PHP_INI=C:\etc\php\php.ini
@setlocal
%PHP_COMMAND% -c %PHP_INI% -d html_errors=off -d open_basedir= -q ".\symfony" %1 %2 %3 %4 %5 %6 %7 %8 %9
if "%OS%"=="Windows_NT" @endlocal

Dreamhost に symfony をインストール

Date
2007-07-23 (Mon)
Category
symfony

毎日酷く疲れているので箇条書きにて。

  1. Dreamhost で php.ini を設定する方法 に従って、php.ini を設定。
    • 今回のポイントは、pear 用とその他 PHP ライブラリ用とで、別の includes dir を作った事。
  2. 環境変数(.bash_profile や .cshrc とか)を編集する。
    • デフォルトでは /usr/local/bin が含まれているけれど、使うものがなかったので削除。
    • php5 用の pear へ symbolic link を張る。
    • pear の実行ファイルを確認すると、PHP_PEAR_PHP_BIN と PHP_PEAR_INSTALL_DIR という環境変数に意味がありそうな感じがするが、設定するとうまく動かない!
    • 現段階での .bash_profile はこんな感じ
      export PATH="$HOME/bin:/usr/bin:/bin"
      alias mypear="/usr/local/php5/bin/pear"
  3. Dreamhost で PEAR ローカルコピーを作る を参考に PEAR のローカルコピーを作る。
    • 普通の pear ではなく、上で設定した mypear を使う。
    • 以前と違い、dreamhost は /tmp への書き込みを禁止したようなので、temp_dir と download_dir を設定しないと動かない。
    • コマンドをまとめると
      mypear config-create /home/USER/php .pearrc
      mypear config-set temp_dir /home/USER/tmp/pear
      mypear config-set download_dir /home/USER/tmp/pear
      mypear install --alldeps PEAR
      mypear channel-discover pear.symfony-project.com
      mypear install symfony/symfony
  4. PEAR dir 内にある、実行可能ファイルへ path を通す。現段階では mypear はもう使わなくてよくなったので削除。以下サンプル
    $HOME/php/pear:$HOME/bin:/usr/bin:/bin

こんな所でしょうか。

routing.yml について: 基本と応用

Date
2007-07-12 (Thu)
Category
symfony

最近 Symfony framework で開発するようになりました。まだ使いこなしていない部分も数多くありますが、とても便利です。

中でも今日は Askeet チュートリアル 4 日目: リファクタリング などで紹介されている routing.yml についてすこし解説しておきたいと思います。

基本

routing.yml はアプリケーション内での URL の規約を設定するファイルです。以下のような特徴があります。

  • まず mod_rewrite かまたはそれに準ずるものがホストとなる HTTP Server にインストールされていて、正しく設定されている事が必要です。dev 環境では動いても、prod 環境では動かない、(というか HTTP Server 独自の 404 File Not Found がでる)と言う場合は RewriteBase を設定してうまくいくことが多いようです(Apache の場合)。例えば、httpd.conf の Alias をつかって http://example.com/askeet/ みたいな形で site root でないところを Symfony の web directory につないだ時には、当たり前かもしれませんが RewriteBase の設定が必要です。
  • routing.yml 抜きのまっさらな状態では、Symfony の frontend controller は Symfony web root 以下の URL 文字列を /(forward slash)で区切ります。それの奇数番目を key に、偶数番目を value にした array を作って、それを以降のアクションなどに Request Parameters として渡します。
    つまり、もし http://example.com/askeet に Symfony web directory が map してあって、ユーザが http://example.com/askeet/foo/bar/hoge/huga/123/456 とリクエストしてきた場合は、[ foo => bar, hoge => huga, 123 => 456 ] という風なリクエストが出来るわけです。
  • デフォルトで、4つ、ルールが書かれています。これらは、したければ自分で変更したってかまいません。そしてこれらには順番には意味があります(順番は後述)。
  • Symfony の frontend controller は、引数(つまり directory 区切り記号)の数をかぞえているようです。数とパターンが両方一致した、上から数えて一番最初のルールを適用するようです。
  • パターンにおいて、文字列はそのままの一致。:label のように : (colon) で始めると、"label" を key とし、ユーザの送った内容を value とする array をつくり request parameter に保持されます。* (asterisk) はワイルドカードで、それのあるエントリでは、引数の数は関係なく、* 以降はあってもなくても関係なくなります。また * 以降の値は基本通り、奇数番目が key に、偶数番目が value になる request parameters を作ります。
    例えばでフォルトエントリの一つの以下を参考にすると…
    default_symfony:
      url:   /symfony/:action/*
      param: { module: default }
    1. Symfony web root より最初の引数が symfony という値で、
    2. 第二の奇数をアクションとして
    3. それ以降の値は奇数/偶数→key/value
    という風に解釈されます。例えば先ほどの例と同じ設定のサイトに http://example.com/askeet/symfony/admin/foo/bar/123/456 というリクエストを出すと、module: default, action: admin に [ foo => bar, 123 => 456 ] というリクエストパラメータを投げます。
  • :module と :action だけは特殊なラベルで、他には使えないようです。

応用

リクエストの時、

  1. 何も値がなかったら、全ての値を返し、
  2. 引数が2つ以上あったら、最初の2つをキーにした値を返し、
  3. それ以外は 404

とかはよくありそうな話です。先ほどの例と同じく、http://example.com/askeet に symfony web directory がmap されているとしましょう。まずは正解から

part:
 url:    /api/list/:id1/:id2/*
 param:  { module: api, action: list }
error: url: /api/list/:id1/* param: { module: api, action: list }
all: url: /api/list/* param: { module: api, action: list }

大事なのは api_list_error の項目が api_list よりも後に定義されているという事ですね。あと error と all の両方のエントリの最後が * で終わっている事。そうしないと、例えば http://example.com/askeet で来たのは拾えても、 http://example.com/askeet/ で来たのは拾えません。

そして受け取るアクションでは、以下のようなコードを書くといいかもしれません。

$req1 = $this->getRequestParameter('id1');
$req2 = $this->getRequestParameter('id2');
switch (true) { case (is_null($req1) && is_null($req2)): $result = "all"; break;
case (isset($req1) && isset($req2)): $result = "partial select"; break;
default: $this->forward404(); break; }

まとめ

routing.yml について解説しました。順番が大事であるという事、* をつける事の有無などの考察は何となくわかっていても、書き出してみる事で自分中でもすっきりまとまりました。

参考

Return to Page Top