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
僕が最初によくわかってなかったのは、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 を挙げておきます。
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 が通っていた方が便利ですが、それも必須ではありません。
手順
- 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 インストールです。 - 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";
....
} - 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
毎日酷く疲れているので箇条書きにて。
- Dreamhost で php.ini を設定する方法 に従って、php.ini を設定。
- 今回のポイントは、pear 用とその他 PHP ライブラリ用とで、別の includes dir を作った事。
- 環境変数(.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"
- 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
- 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 }- Symfony web root より最初の引数が symfony という値で、
- 第二の奇数をアクションとして
- それ以降の値は奇数/偶数→key/value
- :module と :action だけは特殊なラベルで、他には使えないようです。
応用
リクエストの時、
- 何も値がなかったら、全ての値を返し、
- 引数が2つ以上あったら、最初の2つをキーにした値を返し、
- それ以外は 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 について解説しました。順番が大事であるという事、* をつける事の有無などの考察は何となくわかっていても、書き出してみる事で自分中でもすっきりまとまりました。
参考
- The Definitive Guide to symfony Chapter 9 - Links And The Routing System
- branches/1.0/lib/controller/sfRouting.class.php
しっかり読み込んでないけれど、504 行目から始まる parse という method がここで解説している処理部分の様子。