Yii одна модель для нескольких таблиц

Я Yii приложения и две таблицы с одинаковой структурой tbl и tbl_history:

теперь хотите создать модель, чтобы выбрать таблицу по параметру, который я отправляю при вызове модели. Например:

MyModel::model('tbl')->find();
//and
MyModel::model('tbl_history')->find();

найти статья С решением в Yii форуме. Сделал те же изменения и, наконец, получил это в MyModel:

private $tableName = 'tbl'; // <=default value
private static $_models=array();
private $_md;

public static function model($tableName = false, $className=__CLASS__)
{
    if($tableName === null) $className=null; // this string will save internal CActiveRecord functionality
    if(!$tableName)
        return parent::model($className);

    if(isset(self::$_models[$tableName.$className]))
        return self::$_models[$tableName.$className];
    else
    {
      $model=self::$_models[$tableName.$className]=new $className(null);
      $model->tableName = $tableName;

      $model->_md=new CActiveRecordMetaData($model);
      $model->attachBehaviors($model->behaviors());
      return $model;
    }
 }

теперь, когда я делаю:

echo MyModel::model('tbl_history')->tableName(); // Output: tbl_history

это возвращает правильное значение, но:

MyModel::model('tbl_history')->find();

по-прежнему возвращает значение tbl.

добавлено:

public function __construct($id=null,$scenario=null){
    var_dump($id);
    echo '<br/>';
    parent::__construct($scenario);
}

и у:

string(tbl_history)
string(tbl_history)
NULL

значит Yii делает вызов модели из другое место но не знаю откуда и как это предотвратить.

также он делает 2 вызова модели, это слишком плохо для производительности?

5 ответов


похоже CActiveRecord::getMetaData() метод должен быть переопределен, чтобы достичь того, чего вы ищете.

<?php
class TestActiveRecord extends CActiveRecord
{
    private $tableName = 'tbl'; // <=default value
    private static $_models=array();
    private $_md;

    public function __construct($scenario='insert', $tableName = null)
    {

        if($this->tableName === 'tbl' && $tableName !== null)
            $this->tableName = $tableName;
        parent::__construct($scenario);
    }

    public static function model($tableName = false, $className=__CLASS__)
    {
        if($tableName === null) $className=null; // this string will save internal CActiveRecord functionality
        if(!$tableName)
            return parent::model($className);

        if(isset(self::$_models[$tableName.$className]))
            return self::$_models[$tableName.$className];
        else
        {
            $model=self::$_models[$tableName.$className]=new $className(null);
            $model->tableName = $tableName;

            $model->_md=new CActiveRecordMetaData($model);
            $model->attachBehaviors($model->behaviors());

            return $model;
        }
    }

    public function tableName()
    {
        return $this->tableName;
    }

    /**
     * Returns the meta-data for this AR
     * @return CActiveRecordMetaData the meta for this AR class.
     */
    public function getMetaData()
    {
        if($this->_md!==null)
            return $this->_md;
        else
            return $this->_md=static::model($this->tableName())->_md;
    }

    public function refreshMetaData()
    {
        $finder=static::model($this->tableName());
        $finder->_md=new CActiveRecordMetaData($finder);
        if($this!==$finder)
            $this->_md=$finder->_md;
    }

}

возможно, проще сделать MyModelHistory, которая расширяет MyModel и переопределяет только один метод - tableName().


Я рекомендую реализовать наследование одной таблицы. Для этого вам нужно будет объединить таблицы с флагом или столбцом типа, который указывает, является ли это записью истории. Я вставил несколько ссылок внизу, так что вы можете увидеть, как это реализовано в Yii и перечислил некоторые преимущества.

преимущества:

  1. вам не нужно будет дублировать код, обычно используемый между моделями

  2. изменения в этой таблице нужно будет выполнить только один раз.

  3. изменения в родительской модели необходимо будет внести только один раз.

  4. код становится, как правило, более обслуживаемым и читаемым.

  5. вы отделяете код, который принадлежит конкретно tbl и tbl_history

http://www.yiiframework.com/wiki/198/single-table-inheritance/ http://en.wikipedia.org/wiki/Single_Table_Inheritance


Я создал решение для выполнения этого точные действия пару месяцев назад. Это полностью динамическое решение, вы просто передаете имя таблицы, как вы ищете для модели. Первоначально это решение было разработано для работы с одной и той же структурой баз данных в нескольких базах данных, но адаптировать его для работы в одной и той же базе данных было тривиально. Документация для этого здесь. Я бы рекомендовал прочитать его, поскольку он имеет более подробную информацию о CDynamicRecord

легко приспособиться к работе с множественными таблицами. Вы можете скачать адаптацию как gist из github.

использование

1) Загрузите Gist и поместите его в ext, сохраните как CDynamicRecordSDB.в PHP

2) Создайте свою модель в модели и настройте ее следующим образом:

В основном, вы хотите расширить CDynamicRecord и переопределить ваши model () и tableName (), чтобы они были совместимы с CDyanmicRecord.

<?php

Yii::import('ext.CDynamicRecordSDB');
class Test extends CDynamicRecordSDB
{
    public static function model($dbConnectionString = 0, $className=__CLASS__)
    {
        return parent::model($dbConnectionString, $className);
    }


    public function tableName()
    {
    return $this->dbConnectionString;
    }

     [... Do everything else after this ...]
}

3) настройте свою модель, как обычно.

использование

использование идентично CActiveRecord, и вы можете выполнять все действия. Никакие сюрпризы. Всего несколько примеров ниже.

$data = Test::model('tbl')->findAll();
$data2 = new Test('tbl');
$data2->findAll();

foreach ($data as $row)
     print_r($row->attributes);

$data = Test::model('tbl_history')->findAll();
foreach ($data as $row)
     print_r($row->attributes);

ограничения

единственное ограничение при этом - вы должны изменить, как работают отношения. Если вы планируете получить доступ к связанной модели (Bar), и у вас нет намерения вызывать Bar самостоятельно. Тогда бар должен расширьте CActiveRecord, и в Foo вы можете определить нормальные отношения. Yii волшебным образом переносит CDbConnectionString через экземпляры для вас.

В противном случае, если вы намерены получить доступ к моделям в той же базе данных, но также хотите сохранить возможность вызывать их самостоятельно, то Bar должен расширить CDynamicModel, а Foo должен иметь геттер, определенный следующим образом.

public function getBar()
{
    return Bar::model($this->$dbConnectionString);
}

небольшой путь, но работа для меня для любого количества таблиц

public static $dynamic_table_name="main_table";

public static function setDynamicTable($param)
{
    self::$dynamic_table_name=self::$dynamic_table_name.$param;
}

/**
 * @return string the associated database table name
 */

public function tableName($param='')
{
    self::setDynamicTable($param);
    return self::$dynamic_table_name;
}


// to use it like 
 ModelName::model()->tableName('_one');
 ModelName::model()->tableName('_two');
 ModelName::model()->tableName('_three');