FuelPHP+PostgreSQLでORMを使うとINSERT時にIDが取得できない

最終更新日時:2017-04-28 14:55:40
FuelPHP PostgreSQL

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

お問い合わせは 掲示板 にて。