7

我有Yii应用程序和两个具有相同结构tbltbl_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 次调用,这对性能来说是否太糟糕了?

4

5 回答 5

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;
    }

}
于 2013-05-08T19:33:24.000 回答
2

也许更容易让 MyModelHistory 扩展 MyModel 并仅覆盖一种方法 - tableName()。

于 2013-05-08T13:40:46.193 回答
2

几个月前,我创建了一个解决方案来执行这个确切的操作。这是一个完全动态的解决方案,您只需将要查找的表名传递给模型。该解决方案最初设计用于跨多个数据库使用相同的数据库结构,但将其调整为在同一个数据库中工作是微不足道的。文档在这里。我建议阅读它,因为它有更多关于 CDynamicRecord 的详细信息

很容易适应使用多个表。您可以从 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 本身。然后 Bar 应该扩展 CActiveRecord,在 Foo 中你可以定义正常的关系。Yii 神奇地为您跨实例传递 CDbConnectionString。

否则,如果您打算访问同一个数据库中的模型,但又想保留自己调用它们的能力,那么 Bar 应该扩展 CDynamicModel,而 Foo 应该有一个如下定义的 getter。

public function getBar()
{
    return Bar::model($this->$dbConnectionString);
}
于 2013-05-08T21:40:39.877 回答
2

我建议实现单表继承。为了做到这一点,您需要将您的表与一个标志或类型列结合起来,以说明这是否是历史记录。我在底部粘贴了一些链接,这样您就可以看到这在 Yii 中是如何实现的,并在下面列出了一些好处。

好处

  1. 您不需要复制模型之间常用的代码

  2. 对该表的更改只需执行一次。

  3. 只需对父模型进行一次更改。

  4. 代码通常变得更具可维护性和可读性。

  5. 您将专门属于的代码tbltbl_history

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

于 2013-05-08T15:18:14.183 回答
1

一种小方法,但对我来说适用于任何数量的桌子

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');
于 2014-06-09T08:02:11.783 回答