8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

YiiAdvent Calendar 2012

Day 16

グッバイアクティブレコード

Last updated at Posted at 2012-12-16

師曰く、

アクティブレコード を使いすぎないでください。 アクティブレコード は OOP 流にデータをモデリングするには便利ですが、クエリ結果に対して一つまたは複数のオブジェクトを作る必要があるため、パフォーマンスを低下させます。 膨大なデータを扱うアプリケーションには、DAO を使うか、直接データベース API を使うのが賢明な選択でしょう。

AR使っちゃダメなんですか!やだー!
まあでも極端な話、ソーシャルゲーとか大規模高負荷なプロジェクトだとDBの水平分割でJOINができないとかあるのでARの恩恵を受け切れないケースもあるんですよね。あとAR嫌いな人たくさん居ますよね。
そしたら極端な話、AR使うなってことはCActiveRecordも継承する必要がないですよね。えっ、じゃあCModelですか…?

というわけでActiveRecordを一切使わない方法をちょっと模索してみました。

CPassiveRecord.php
<?php
class CPassiveRecord extends CModel
{
    /**
     * @var array
     */
    private $attributes;

    /**
     * @param string $className
     * @return CPassiveRecord
     */
    public static function model($className=__CLASS__)
    {
        return new $className;
    }

    /**
     * カラムから取得
     * @param string $name
     * @return mixed
     */
    public function __get($name)
    {
        if(isset($this->attributes[$name])){
            $method='get'.$name;
            if(method_exists($this,$method))
                return $this->$method();
            return $this->attributes[$name];
        }else
            return parent::__get($name);
    }

    /**
     * @param string $name
     * @param mixed $value
     * @return mixed|void
     */
    public function __set($name,$value)
    {
        if($this->setAttribute($name,$value)===false)
        {
            parent::__set($name,$value);
        }
    }

    /**
     * カラムを格納する
     * @param string $name
     * @param mixed $value
     * @return bool
     */
    public function setAttribute($name,$value)
    {
        if(property_exists($this,$name))
            $this->$name=$value;
        elseif(in_array($name, $this->attributeNames())){
            $method='set'.$name;
            if(method_exists($this,$method))
                $this->$method($value);
            else
                $this->attributes[$name]=$value;
        }else
            return false;
        return true;
    }

    /**
     * @return array
     */
    public function attributeNames(){
        parent::attributeNames();
    }

}

こいつをモデルで継承します。

models/Hoge.php
<?php
Yii::import('application.models.CPassiveRecord');
class Hoge extends CPassiveRecord
{
    /**
     * @param string $className
     * @return Hoge
     */
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }

    /**
     * いい加減なバリデーション
     * @return array
     */
    public function rules()
    {
        return array(
            array('id, title, publisher, createdAt', 'safe'),
        );
    }

    /**
     * findByPkを見た目だけ真似する
     * @param int $id
     * @return Hoge
     */
    public function findByPk($id){
        $hoge = Yii::app()->db->createCommand('
            SELECT * FROM hoge WHERE id = :id
        ')->
        bindParam(':id',$id)->
        query();

        $this->attributes = $hoge->read();
        return $this;
    }

    /**
     * save()っぽい何か
     * @return mixed
     */
    public function save(){
        return Yii::app()->db->createCommand('
            INSERT INTO hoge(title, publisher, createdAt)
            VALUES(
              ":title",
              ":publisher",
              ":createdAt"
            )ON DUPLICATE KEY UPDATE
              title = ":title",
              publisher = ":publisher",
              createdAt = ":createdAt"
        ')->
        bindParam(':title',$this->title)->
        bindParam(':publisher',$this->publisher)->
        bindParam(':createdAt',$this->createdAt)->
        execute();
    }

    /**
     * CModelを直接継承する際に必須。
     * @return array
     */
    public function attributeNames(){
        return array(
            'id',
            'title',
            'publisher',
            'createdAt',
        );
    }

    /**
     * getterも定義できるよ
     * @return string
     */
    public function getCreatedAt(){
        return $this->createdAt;
    }

    /**
     * setterもていぎできるよ
     * @param $value
     */
    public function setCreatedAt($value){
        return $this->createdAt = $value;
    }
}

するとこんなふうに使える

controller
<?php
$hoge = Hoge::model()->findByPk(100);
$hoge->createdAt = date('Y-m-d H:i:s');
$hoge->save();

ちょいちょいパクりつつ1時間ほどかけてできたのがコレ。名付けてCPassiveRocord! Activeの反対がPassiveって割とネトゲ脳ですよね。意味は特にありません。

ActiveRecordは使えなくてもなるべくYiiの記法を殺さないようなアレ。処理としては

  • DBのカラムはCPassiveRecord::$attributesに放り込む
  • attributeNames()を参考に__get,__setで出し入れ
  • あとは継承したモデルでActiveRecordっぽいメソッドを勝手に再現しろっていう丸投げ

最後のコード部分だけ見れば完璧にYiiのARですよね!というかここまでしか再現できてないのですが。Relation?甘えんなJOINしたら負け。Scope?SQL書けよ馬鹿野郎。

もう頭おかしいのでやっぱりCActiveRecord継承してやばいとこだけSQL書けばいいと思います。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?