FuelPHP+PostgreSQLでORMを使うとINSERT時にIDが取得できない
FuelPHP の ORM を継承した Model クラスを作成します。
通常、このクラスを使って new して save() しますが、PostgreSQL を使っていると、この時に Insert して登録された ID がオブジェクトに登録されません。
例えば、new して save() して、その後そのオブジェクトをさらに変更を加えたりした場合、
Undefined index: id in /data/project/lightly/fuel/packages/orm/classes/model.php on line 1441
こんなエラーが出たりします。
また、ID が登録されないという現象なので、has_many 等でリレーション設定している場合には、以下のようなエラーが出たりもします。
Invalid Model instance added to relations in this model. in /data/project/lightly/fuel/packages/orm/classes/hasmany.php on line 141
要するに、ほとんど PostgreSQL 環境下では FuelPHP の ORM は使いものになりません。
というか、根本は DB::query にあるようなので、 Crud でも同様の現象が起きると思いますし、DB::query で INSERT 時に ID を取得しようとしても取得できないと思います。
問題の原因
INSERT 時の挙動を調べてみると、INSERT 直後に PDO の lastInsertId() を使用して該当 ID を取得し、それをオブジェクトに登録するような仕掛けとなっていました。
ところが、lastInsertId() は PostgreSQL では正常に機能しません。
PostgreSQL で使用するには、lastInsertId() のパラメータとしてシーケンス名を指定する必要があるようです。
FuelPHP では、この対応が行われていないため、PostgreSQL を使うと冒頭の問題が発生します。
対策
Web 上で調べてみるといろいろと対策が記載されているのですが、意外と ORM でのスマートな対応が見つからなかったので、私が行った対策を紹介しておきます。
概要としては以下となります。
・ID の取得は PDO の lastInsertId() をやめて、PostgreSQL の LASTVAL() で取得するようにする
・上記の対応のため、fuel/core/classes/database/pdo/connection.php を修正する必要があるので、拡張クラスを作成する
以下、詳細です。
まずは拡張クラスを作成します。
path は適当で良いですが、例えば fuel/app/classes/core/connection.php とします。
内容ですが、この拡張クラスは以下のように \Fuel\Core\Database_PDO_Connection を継承するようなクラスとします。
class Database_PDO_Connection extends \Fuel\Core\Database_PDO_Connection {
public function query($type, $sql, $as_object) {
...
次に、修正箇所は query() メソッドとなるので、query() メソッドの内容を fuel/app/classes/core/connection.php からごっそりコピペします。
その上で、以下のように LASTVAL() を使うように修正します。
elseif ($type === \DB::INSERT) {
// Return a list of insert id and rows created
$r = $this->_connection->query('SELECT LASTVAL()')->fetch(PDO::FETCH_NUM);
return array(
//$this->_connection->lastInsertId(),
$r[0],
$result->rowCount(),
);
}
最後に、この拡張クラスを bootstrap.php でオートローダーに設定します。
Autoloader::add_classes(array(
// Add classes you want to override here
// Example: 'View' => APPPATH.'classes/view.php',
'Database_PDO_Connection' => APPPATH.'classes/core/connection.php',
));
これで正常に ID が取得できるようになります。