2015.09.18

YiiでOracleを利用する

はじめに

こんにちは。次世代システム研究室のT.Tです。
最近PHPでOracleを利用するプロジェクトに携わる機会があり、OracleをサポートしているPHPフレームワークを選定する必要があったため、いくつかのフレームワークを調査してみました。
Zend Framework、Yii、Laravel等いくつかサポートしているフレームワークはありましたが、それらを比較した結果Yii(バージョンは2.0.4)を採用することになりました。

Yiiを採用することにしたものの、調査段階でいくつか特有なコーディングをしないといけない箇所があることが分かっていました。
  • ActiveRecordのフィールド名をスキーマ定義に合わせないといけない(大文字は大文字にしないといけない)
  • ActiveRecordで使うテーブル名をスキーマ定義に合わせないといけない(大文字は大文字にしないといけない)
  • ActiveRecordでシーケンスを使う場合にExpressionで設定する
  • ActiveQueryのカラム名をスキーマ定義に合わせないといけない(大文字は大文字にしないといけない)
今回は、こうした特有な事情を回避してOracleを意識せずにコーディングするための施策についてご紹介したいと思います。

標準のコーディングスタイル

ActiveRecordの標準仕様ではOracleのスキーマ定義をそのまま利用してマッピングルールを構築するため、カラム名やテーブル名が大文字でマッピングされます。

そのため、標準では以下のようなコーディングスタイルになります。
// テーブル定義
CREATE TABLE USER
(
    USER_ID NUMBER(10,0) NOT NULL,
    PRIMARY KEY ( USER_ID )
);
// コーディングスタイル
// get時にフィールド名が大文字
$user_id = $user->USER_ID;

// set時にフィールド名が大文字
$user->USER_ID = $user_id;

class User extends ActiveRecord
{
    // 明示的にテーブル名を返す必要あり
    public static function tableName()
    {
        return 'USER';
    }
}

// yii¥db¥Expressionでシーケンスを割り当てる必要あり
$user->USER_ID = new Expression("SEQ_USER.nextval");

// クエリのカラム名が大文字
$user = User::find()->where(['USER_ID' => $user_id]);
 

変更後のコーディングスタイル

フィールド名を小文字でアクセスできるようにしたり、テーブル名を標準のルールで自動で取得できるようにしてOracleを透過的に扱えるようにします。
// コーディングスタイル
// get時にフィールド名が小文字
$user_id = $user->user_id;

// set時にフィールド名が小文字
$user->user_id = $user_id;

// ActiveRecordを継承したクラス
class User extends BaseActiveRecord
{
    // シーケンスを使うカラムとシーケンスを対応付けると保存時にシーケンスを利用する
    protected static $sequencer = ['user_id' => 'SEQ_USER'];

    // オーバーライドする必要なし
    //public static function tableName() 
    //{
    //    return 'USER';
    //}
}

// クエリのカラム名が小文字
$user = User::find()->where(['user_id' => $user_id]);

ActiveRecordの拡張

yii\db\ActiveRecordを拡張したBaseActiveRecordクラスを作成して、そのクラスを継承することで以下の機能が利用できるようになります。
  • getとsetのアクセサを小文字にできる
  • テーブル名を明示的に指定する必要がなくなる(標準の命名規約に準拠する)
  • シーケンスのマップを指定することで保存時にシーケンスで値を割り当てることができる
// BaseActiveRecordの実装
<?php

namespace app\models\common;

use Yii;
use yii\db\Expression;
use yii\db\ActiveRecord;

class BaseActiveRecord extends ActiveRecord
{
    // シーケンスを利用するプロパティ名とDBのシーケンス名のマップ
    protected static $sequencer = [];

    // 参照時にプロパティを大文字に変換して小文字で参照できる
    public function __get($name)
    {
        return parent::__get(strtoupper($name));
    }

    // 更新時にプロパティを大文字に変換して小文字で参照できる
    public function __set($name, $value)
    {
        parent::__set(strtoupper($name));
    }

    // テーブル名を大文字に変換して標準の命名規約で参照できる
    public static function tableName()
    {
        return strtoupper(parent::tableName());
    }

    // 新規作成時にシーケンスで値を割り当てる
    public function beforeSave($insert)
    {
        if ($insert) {
            foreach (static::$sequencer as $fieldName => $sequencerName) {
                $this->{strtoupper($fieldName)} = new Expression("{$sequencerName}.nextval");
            }
        }

        return parent::beforeSave($insert);
    }
}

ActiveQueryの拡張

yii\db\ActiveQueryを拡張したBaseActiveQueryクラスを作成して、そのクラスをBaseActiveRecordのfindメソッドで返すようにすることでクエリでカラム名に小文字が利用できるようになります。
// BaseActiveRecordのBaseActiveQueryを返す実装
<?php

namespace app\models\common;

use Yii;
use yii\db\ActiveRecord;

class BaseActiveRecord extends ActiveRecord
{
    // クエリ生成用のクラスとしてBaseActiveQueryを使う
    public static function find()
    {
        return Yii::createObject(BaseActiveQuery::className(), [get_called_class()]);
    }
}

// BaseActiveQueryの実装
<?php

namespace app\models\common;

use yii\db\ActiveQuery;

class BaseActiveQuery extends ActiveQuery
{
    // クエリのカラム名を小文字で参照できる
    public function where($condition, $params = [])
    {
        return parent::where(empty($params) ? $this->createCanonicalCondition($condition) : $condition, $params);
    }

    // クエリのカラム名を小文字で参照できる
    public function andWhere($condition, $params = [])
    {
        return parent::andWhere(empty($params) ? $this->createCanonicalCondition($condition) : $condition, $params);
    }

    // クエリのカラム名を小文字で参照できる
    public function orWhere($condition, $params = [])
    {
        return parent::orWhere(empty($params) ? $this->createCanonicalCondition($condition) : $condition, $params);
    }

    // クエリの条件が配列のときは条件内のカラム名を大文字に変換
    private function createCanonicalCondition($condition)
    {
        if (is_array($condition)) {
            $canonical_condition = [];
            foreach ($condition as $key => $value) {
                $canonical_condition[strtoupper($key)] = $value;
            }

            return $canonical_condition;
        } else {
            return $condition;
        }
    }
}

Yii通になるための2つのトピック

Yiiを使い始めて一ヶ月くらい経ちました。利用が進むにつれ良く出来たフレームワークだと関心する一方、basicテンプレートでconfigを環境ごとに切り替える機能が標準ではない等、「おやっ」と思う箇所もままあったりします。そういった部分もありながらも機能としては充実していて、しっかり使いこなせるようになるにはまだまだ時間が掛かりそうです。

なかなか魅力的なYiiですが、まだ魅力をお伝えするには不勉強なので2つだけトピックを共有しておきたいと思います。
発音も某戦闘要員に負けないように頑張りたいものです。

まとめ

Yiiの標準クラスを拡張してOracleを透過的に扱うための仕組みを導入することができました。この仕組みを現在プロジェクトで利用していて、特有なコーディングをせずに標準的なコーディングスタイルで開発できています。

今回紹介した範囲ではActiveRecordとActiveQueryの全てのメソッドでOracleを透過的に扱えませんが、同じようなアプローチで必要に応じて拡張して適用できる範囲を随時拡げています。

次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。

皆さんのご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ