モノノフ日記

普通の日記です

sfPropel13Pluginを導入したときの注意点まとめ

symfony1.0.XでPropel1.3のDBレプリケーションが使いたい!という理由だけでsfPropel13Pluginを導入してみたので簡単に手順をまとめておきます。

インストール

SVNリポジトリからプラグイン本体をゲットすれば終了です。チェックアウトでもいいと思いますが、1.2ではデフォルトでPropel1.3が導入されていることから、もう更新はおそらく無いので自分のリポジトリに入れるようにexportしてます。

$ svn export http://svn.symfony-project.com/plugins/sfPropel13Plugin/ plugins/

導入(基本編)

まずdatabase.ymlをPropel1.3仕様に書き換えます。dsnの仕様はPHP: PDO_MYSQL DSN - Manualを参照してください。

all:
  propel:
    class:          sfPropel13Database
    param:
      dsn:          mysql:dbname=master;host=localhost
      username:     foo
      password:     foofoo
      instance-pooling:  false
    slaves:
      slave1:
        dsn:          mysql:dbname=slave_first;host=localhost
        username:     bar
        password:     barbar
      slave2:
        dsn:          mysql:dbname=slave_second;host=localhost
        username:     hoge
        password:     hogehoge

propel.iniも1.3仕様に書き換えます。加えて、sfPropel13Pluginを参照するよう指定します。

propel.targetPackage       = lib.model.propel
propel.packageObjectModel  = true
propel.project             = symfony
propel.database            = mysql
propel.database.createUrl  = mysql://foo:foofoo@localhost/

propel.addGenericAccessors = true
propel.addGenericMutators  = true
propel.addTimeStamp        = false

propel.schema.validate     = false

; sfPropel13Plugin
propel.database.user       = foo
propel.database.password   = foofoo
propel.database.url        = mysql:dbname=master;host=localhost
propel.database.creole.url = mysql://foo:foofoo@localhost/master

; directories
propel.home                    = .
propel.output.dir              = /home/Kiske/symfony
propel.schema.dir              = ${propel.output.dir}/config
propel.conf.dir                = ${propel.output.dir}/config
propel.phpconf.dir             = ${propel.output.dir}/config
propel.sql.dir                 = ${propel.output.dir}/data/sql
propel.runtime.conf.file       = runtime-conf.xml
propel.php.dir                 = ${propel.output.dir}
propel.default.schema.basename = schema
propel.datadump.mapper.from    = *schema.xml
propel.datadump.mapper.to      = *data.xml

;sfPropel13Plugin builder settings
propel.builder.peer.class              = plugins.sfPropel13Plugin.lib.propel.builder.SfPeerBuilder
propel.builder.object.class            = plugins.sfPropel13Plugin.lib.propel.builder.SfObjectBuilder

propel.builder.objectstub.class        = addon.propel.builder.SfExtensionObjectBuilder
propel.builder.peerstub.class          = addon.propel.builder.SfExtensionPeerBuilder
propel.builder.objectmultiextend.class = addon.propel.builder.SfMultiExtendObjectBuilder
propel.builder.mapbuilder.class        = addon.propel.builder.SfMapBuilderBuilder
propel.builder.interface.class         = propel.engine.builder.om.php5.PHP5InterfaceBuilder
propel.builder.node.class              = propel.engine.builder.om.php5.PHP5NodeBuilder
propel.builder.nodepeer.class          = propel.engine.builder.om.php5.PHP5NodePeerBuilder
propel.builder.nodestub.class          = propel.engine.builder.om.php5.PHP5ExtensionNodeBuilder
propel.builder.nodepeerstub.class      = propel.engine.builder.om.php5.PHP5ExtensionNodePeerBuilder

propel.builder.addIncludes = false
propel.builder.addComments = false

propel.builder.addBehaviors = true

;using innodb
propel.mysql.tableType = InnoDB

セッションをMySQLに格納するようにしているのであればstorageクラスも変更します。apps/[app_name]/config/factories.ymlを修正します。

  storage:
    class: sfPDOSessionStorage
    param:
      session_name: symfony
      database:     propel
      db_table:     session_storage
      db_data_col:  session_data
      db_id_col:    session_id
      db_time_col:  session_time

設定ファイルの修正は以上です。
ModelオブジェクトをPropel1.3でリビルドします。

$ symfony propel-13-build-model

次にModelオブジェクトを修正していきます。Baseのまま利用しているのはリビルドで書き換えてくれるので自前のModelオブジェクトが修正対象となります。注意点をリストアップします。サンプルコードはPropelの公式ドキュメントから引用しています。

  • saveメソッドの引数のオブジェクトのタイプヒンディング指定
<?php
public function save(PropelPDO $con = null)
{
  // save method...
}
  • doSelectRS -> doSelectStmtに変更
<?php

// example of how to manually hydrate objects
$stmt = AuthorPeer::doSelectStmt(new Criteria());
while($row = $stmt->fetch(PDO::FETCH_NUM)) {
  $a = new Author();
  $a->hydrate($row);
}

// example of how to create array of single column
$stmt = AuthorPeer::doSelectStmt(new Criteria());
$names = array();
while($res = $stmt->fetchColumn(1)) {
  $names[] = $res;
}
  • begin メソッドが beginTransaction に変更
<?php

$con->beginTransaction();
try {
  /* db logic */
  $con->commit();
} catch (PDOException $sqle) {
  $con->rollBack();
  throw $sqle;
}
  • プリペアドステートメンドの文法の変更
<?php

$con = Propel::getConnection(SomeTablePeer::DATABASE_NAME);
$stmt = $con->prepare("SELECT * FROM some_table WHERE name = ?");
$stmt->bindValue(1, $name);
$stmt->execute();
while($row = $stmt->fetch()) { 
   print "Name: " . $row['name'] . "\n";
}

導入(応用編)

今まではPropel1.3にする上で必ず必要となる項目でしたが、ここからは個人的に嵌ったところを書きます。

カスタムクエリをPagerに投げるとき

今まではsfAdvancedPropelPagerというsymfony公式のCode Snippetに書かれてるカスタムクラスを使っていたのですが、当然ながらsfPropel13Pluginには対応してないので動きません。
思いついた解決方法は3つでした。

  1. sfAdvancedPropelPagerをPropel1.3に対応するようカスタマイズ
  2. Criteria::CUSTOMを使う
  3. プリペアドステートメントでデータ処理してPager部分は内製(or ライブラリ利用)

自分はCriteria::CUSTOMで回避しています。文字列エスケープはPDO::quoteを使っています。

sfPropelParanoidPluginが動かなくなる

DBを論理削除してくれるようになるプラグインですが、そのままでは動きませんでした。symfony1.2用にリリースされているバージョンを参考に修正しました。

  • plugins/sfPropelParanoidBehaviorPlugin/config/config.php
<?php

sfPropelBehavior::registerMethods('paranoid', array(
  array('sfPropelParanoidBehavior', 'forceDelete'),
));

sfPropelBehavior::registerHooks('paranoid', array(
  ':delete:pre'                => array('sfPropelParanoidBehavior', 'preDelete'),
  'Peer:doSelectStmt'          => array('sfPropelParanoidBehavior', 'doSelectStmt'),
  'Peer:doSelectJoin'          => array('sfPropelParanoidBehavior', 'doSelectStmt'),
  'Peer:doSelectJoinAll'       => array('sfPropelParanoidBehavior', 'doSelectStmt'),
  'Peer:doSelectJoinAllExcept' => array('sfPropelParanoidBehavior', 'doSelectStmt'),
));
  • plugins/sfPropelParanoidBehaviorPlugin/lib/sfPropelParanoidBehavior.class.phpの22行目
public function doSelectStmt($class, $criteria, $con = null)
{
// ...
}
レプリケーション設定

デフォルトだとsfPropel13Pluginはレプリケーションに対応していないようなので公式フォーラムのsymfony framework forum: General plug-ins => [sfPropel13Plugin] Propel replication support? SOLVEDを見て解決しました。Forumのやり方だとプラグインとフレームワーク本体を直接編集することになるので抵抗がある人は app/[app_name]/lib/ 下にカスタムクラスでオーバーライドすれば良いかと思います。
特にsymfony本体側は更新が今後もありそうなのでカスタムクラス化した方がよいかと思います。編集するのは[sf_symfony_lib_dir]/config/sfDatabaseConfigHandler.class.phpです。カスタムクラス化したときは app/[app_name]/config/config_handers.ymlを作成し下記コードを記述してカスタムクラスをロードするようにしてください。

config/databases.yml:
  class:    myDatabaseConfigHandler


以上です。案外すんなり導入できるので興味がある人は試してみてください。PDOなのでCreoleよりはおそらく処理速度も上がっているはずです。(実測はしてません)
しかし、一番ベストなのはsymfony1.2へ移行するほうが良いと思います。