sfContextとPropelから取れるConnectionは同じ?
symfonyでアプリケーションを書いている際にデータベースへのコネクションが欲しくなったとします。
見ていると二通りの書き方をする人が周囲にいました。
どちらもデータベースへのコネクションを返してくれそうな気配を漂わせています。
$conn = Propel::getConnection();
$conn = sfContext::getInstance()->getDatabaseConnection();
どちらを経由してもCreoleのConnectionに触ることができます。
またCreoleの中ではオブジェクトをキャッシュする実装があるので、結局の所同じオブジェクトが帰ってくるはずです。
loadConfiguration(); $lime = new lime_test(); $conn2 = Propel::getConnection('propel'); $conn = sfContext::getInstance()->getDatabaseConnection('propel'); $conn2->begin(); $lime->is($conn->getAutoCommit(),$conn2->getAutoCommit());
しかしながら、上記のユニットテストは失敗します。
Propelから取ったコネクションとsfContextから取ったコネクションは別のオブジェクトなのでトランザクションの状態などが一致しません。
つまりアプリ内でこの実装が混在しているとトランザクション制御が困難です。
オブジェクトが複製されてしまっている原因はCreole.php内の下記の判定の結果が異なっているからです。
DSN情報が同一なら同じキーの下にオブジェクトを作成しようとしている処理です。
// sort $dsninfo by keys so the serialized result is always the same // for identical connection parameters, no matter what their order is ksort($dsninfo); $connectionMapKey = crc32(serialize($dsninfo + array('compat_flags' => ($flags & Creole::COMPAT_ALL))));
判定が違ってしまっている原因はsfPropelDatabaseからCreole::getConnectionを呼ぶ場合とPropelからCreole::getConnectionを呼ぶ場合とで引数が異なっているのが原因です。
未指定のパラメータ(port socket など)があった場合、Propelはnullを渡していますが、sfPropelDatabaseはキー自体をセットしないでCreoleを呼びます。
その結果、serializeの種が変わってしまいオブジェクトが複製されてしまいます。
これをパラメータを受けるCreoleの問題と見るか、sfPropelDatabase側の問題と見るかは微妙です。
いずれにせよトランザクション制御などに支障を来たすのでこの問題にはなんらかの形で対処する必要があります。
考えられる選択肢は
- Propel::getConnectionだけを使う
- sfContext::getInstance()->getDatabaseConnection()だけを使う
- propelを改造する
- creoleを改造する
- 設定が同一になるようにする
まず2については難しいです。
なぜかというとPropelが生成するPeerクラス郡の中ではPropel::getConnectionが使われています。よって実装で調整する場合はsfContextからの接続の取得を禁止する事になります。
3についてはsfPropelDatabaseの派生クラスを作成し、connectメソッドをコピーして下記のコードで対応しました。
これをdatabase.php内でsfPropelDatabaseの代わりに指定して処理を差し替えます。
// add sta $dsn['compat_assoc_lower'] = NULL; $dsn['compat_rtrim_string'] = NULL; $dsn['persistent'] = $persistent; $dsn['port'] = NULL; $dsn['protocol'] = NULL; $dsn['socket'] = NULL; // add end // do the duuuurtay work, right thurr if ($flags > 0) { $this->connection = Creole::getConnection($dsn, $flags); } else { $this->connection = Creole::getConnection($dsn); }
4と5については実施していないのでなんともいえません。
いずれにせよトランザクション制御がおかしくなったりしてしまうとダメージが大きいのでsfContextからデータベースのコネクションを取得している場合は挙動を確認したほうがいいかもしれません。
見落としや、「sfContextを避けるのはsymfony使いの常識!」とかあればブクマコメなど頂けると幸いです。