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 が取得できるようになります。