index

2007年 6月
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30  
2007年 7月
              1
  2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
  30 31          
2007年 8月
      1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
  20 21 22 23 24 25 26
27 28 29 30 31    

アレ

1日中 Symfony とかいじってました   ▽20070727a #プログラミング #PHP

Symfony をあれこれいじった

「Symphony」の誤表記ではねーのですよー. 「Symfony」ね. PHP の Web アプリケーションフレームワークの中で, 特に最近評価が高いというブツだ. 一番根っこの部分は Mojavi にあるらしいんだけど, Django とか Ruby on Rails とかの影響を受けている部分もあるらしい.

ということでインストールして色々といじくって, ハマったというか迷った箇所を書き連ねておこう. 後で思い出すのに困る部分もあるかもしれないし, 検索でここを見つけて役立てるひともいるかもだしね.

インストール

まずインストール.PEAR コマンドで channel-discover して install して…… この辺は特に困る箇所は無い.

困りはしないけど,あれ? と思ったのが, この Symfony は DB アクセスレイヤとして propel を標準で使えるのだが, propel も PEAR でインストールできるようになってるのね.

じゃこの propel も別途インストールが必要? てことは propel が動作するために必要な creole や コマンドラインツール用の Phing も別途?

と思ったら.必要ありません.Symfony のパッケージの /vendor ディレクトリにごっそり入ってます.

schema.xml と schema.yml

インストールして,コマンドラインから init-project して init-app して init-module して,と. 今回は既存 DB を使うので(といってもダンプして別スキーマ立てて使うが), それを読むように /config/database.yml をいじくって, propel-build-schemaschema.yml を自動生成してくれる. そしたら propel-build-model でモデルクラス,いわゆる DAO を作ってくれる.

ちなみに propel-convert-yml-schema で XML 版の schema.xml を生成してくれる. propel は元々 XML で定義して使うもので, YAML の定義ファイルは簡易版.XML で表現できることが YAML だとできない場合もあったりするらしい. 遭遇してないけど.

で,そんなことをしてからまた propel-build-model を実行すると,

[propel-om] Processing: schema.xml
[propel-om] Processing: generated-schema.xml
Execution of target "om-template" failed for the following reason: /usr/local/share/pear/symfony/vendor/propel-generator/build-propel.xml:470:1: Duplicate table found: propel.
[phingcall] /usr/local/share/pear/symfony/vendor/propel-generator/build-propel.xml:470:1: Duplicate table found: propel.

というエラーが出て,あれー?てなる. この引用の先頭をよーっく見るとわかるが, schema.xml があるのに schema.yml もあってしまうと, 両方処理しようとして duplicate 扱いになってしまうみたい. schema.xml を削除しましょう. もちろん YAML メインでなく最初から XML を書いたなら YAML の方を消すんだけど, 個人的に XML は面倒だからキライなんでそんなこたーしませんw

Postgres でシーケンスによる auto increment ID

テーブルの ID となるフィールドが integer で定義されてて, シーケンスでシリアル番号を取得して使ってたような場合, propel-build-schema ではただの「プライマリキーの integer 項目」にしか見てくれない. ので,手で schema.yml を変更して,ID フィールドのところに

id:
  autoIncrement: true

と付け加えてやってから propel-build-schema をやりなおすと, レコード挿入時にいい感じにシーケンスから採番してくれる. この場合のデフォルトのシーケンス名は「(テーブル名)_SEQ. シーケンス名を定義したい場合は,

id:
  autoIncrement: true
  sequence: hoge_fuga_seq

みたいに名前も指定できるらしい.試してないけど.

DAOクラスを作ったのに読んでくれない

propel-build-model で,/lib/model ディレクトリに DB のテーブル数 * 2 くらいのファイルがわんさか出力されたんで, さっそくモジュールを作って DB から読み出してみよう.

てことで /app/(appname)/modules/(modname)/actions/actions.class.php(長い)にちゃちゃっと記述. Symfony はオートローダが全部処理してくれるんで, いちいち require_once() を書かなくてもいいよ! いいよ! ……ってガイドに書いてあるんだけど, いきなり new ClassName() すると「クラスねーよ」って PHP に怒られる.

オートローダはクラスの書かれたファイル名をキャッシュするので, /lib 配下のファイル構成が変わったら symfony cc しましょう. dev モードで cache: off ってなっててもローダの情報はキャッシュされるみたい.

addSelectColumn()するとdoSelect()でエラーが出る

普通の読み書きは順調に問題なし. Criteria で SQL を組み立てるインタフェースも, まぁ直感的でわかりやすいと思う. メソッド名がいちいち長いのは最近の文化ですかね.コードがうざくなるんだけどw

で,ちょっと複雑なことをしようとしたらハマった. Criteria->clearSelectColumn() してから Criteria->addSelectColumn() して, 例えばテーブルのうちのいくつかのフィールドを読まないように設定すると, テーブルの Peer クラスの doSelect() でエラーとなってしまう

原因は,このベースとなる BasePeer::doSelect() でレコードセットを通常配列モード (数値連番のキー → テーブルの値,という配列で返す)でフェッチして, それをテーブルクラスで番号指定でフィールド名のオブジェクト変数に取り込んでるから. そうすると実質的にはフィールド全てが順番通りに並べられたクエリ (SELECT field1, field2, field3, ... みたいなお行儀良いやつ)しか使えないってことだ.

こいつはヤベえ.ヤベえカッコ悪さだ. 検索したら海外とか日本でも同じように困ってる人がいるみたいで.そりゃそうだよなぁ.

ひとまずテーブル拡張クラスでメソッドをオーバライドして対処

class HogePeer extends BaseHogePeer
{
  public static function doSelect(Criteria $criteria, $con = null)
  {
    $rs = HogePeer::doSelectRS($criteria, $con);
    $rs->setFetchmode( ResultSet::FETCHMODE_ASSOC );
    return HogePeer::populateObjects( $rs );
  }
}
class Hoge extends BaseHoge
{
  public function hydrate(ResultSet $rs, $startcol = 1)
  {
    if ( $rs->getFetchmode() === ResultSet::FETCHMODE_NUM ) {
      return parent::hydrate( $rs, $startcol );
    } else {
      try {
        $fieldNames = $this->getPeer()->getFieldNames( BasePeer::TYPE_FIELDNAME );
        $keys = array_keys( $rs->getRow() );
        foreach ( $keys as $key ) {
          switch ( $key ) {
          case 'id':
            $this->id = $rs->getInt( 'id' );
            break;
          case 'name':
            $this->name = $rs->getString( 'name' );
            break;
      ...
          }
        }
        $this->resetModified();

        $this->setNew(false);

        return count( $keys );

      } catch (Exception $e) {
        throw new PropelException("Error populating Hoge object", $e);
      }
    }
  }
}

今回はひとまず,

  • doSelect() からは FETCHMODE_ASSOC を指定してクエリ発行
  • 取得時にモードが FETCHMODE_ASSOC でなければ元の処理に飛ばす
  • 名前指定にしただけで,元とほとんど同じ処理を行なう

という風にしてみた.

PEAR::DB_DataObject では,とりあえずどんなクエリを投げても, 結果のフィールド名(SELECT の別名で作った架空名であっても)の名前のオブジェクト変数を持つ DAO として動いてくれたんだけど, こいつも「見知らぬ名前のフィールドはとにかく getString() で取り込んじゃう」とかすれば, それに近い動作になるかも. 元の設計思想とか粉砕してる気がするけど気にしなーいw

というわけで

DB まわりは一通り問題なさげになってきた. 後は? フォームとか,プレゼンテーションまわりとかか.

プレゼンテーションまわりはいいとして, フォームの取り扱いは既に HTML_QuickForm に慣れてしまってるのだが, こいつは癖がありすぎて応用が利かないけど使いこなすと異様に快適なのが困る. Symfony のフォーム取り扱いが,まぁ楽だといいんだけど.

がんばろー.

index