1

I have a Yii model that will be using (later) multiple databases and the table prefix will be based on a code.

For example:

AMI_tablename, BMI_ AMI_tablename etc

These all tables are same but in different databases.

I want to know how could I provide the dynamic table name to Yii model at run time?

I tried to using a setter function but the parent class CActiveRecord gives an error as it does not get the value from the child model class.

so here is my model code (only the part I have problem)

class RevShareModel extends CActiveRecord
{

    public $prefix;

    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }

    /**
     * @return string the associated database table name
     */
    public function tableName()
    {
        return $this->prefix . '_revshare_model';
    }

now somewhere in my controller

$obj = RevShareModel::model();
$obj->prefix ="BMI";
$obj->tableName();
$obj->findByPk(1);

exit;

But what I get the error is:

CDbException

The table "_revshare_model" for active record class "RevShareModel" cannot be found in the database.

C:\wamp\www\framework\db\ar\CActiveRecord.php(2264)

seems like when tableName() method is called by CActiveRecord it does not get $prefix.

tereško
  • 58,060
  • 25
  • 98
  • 150
Arfeen
  • 2,553
  • 5
  • 29
  • 48

5 Answers5

5

You've got such error because the table name is actually stored in model's metadata. You can see that by checking contents of $model->getMetaData() which returns CActiveRecordMetaData object. To refresh metadata you should call $model->refreshMetaData() after changing the 'prefix' attribute of your model, e.g.:

...
$obj->prefix ="BMI";
$obj->refreshMetadata();
...

This will definitely do the trick.

katsanva
  • 115
  • 1
  • 7
  • I had already found out that I have to use refreshMetaData but I could not update this question somehow. I'm accepting your answer as I did the same and it worked. – Arfeen Nov 25 '13 at 08:09
2

You have to override the CActiveRecord::tableName method (probably in an abstract base class for your models). Here's what it does by default:

public function tableName()
{
    return get_class($this);
}

And here's what you could change it to:

abstract class MyActiveRecord extends CActiveRecord
{
    public $prefix; // should probably be private, your call

    public function tableName()
    {
        return $prefix.'_'.get_class($this);
    }
}
Jon
  • 428,835
  • 81
  • 738
  • 806
  • im already overriding it but how to provide the prefix code to this model ? – Arfeen Apr 17 '12 at 07:09
  • @Arfeen: Add a property or dynamically generate the prefix, depends on what you want to do. See the update. – Jon Apr 17 '12 at 07:12
  • but the thing is when CActiveRecord it self will call the tableName(), it wont get the value in $prefix ... right ? thats the problem. – Arfeen Apr 17 '12 at 07:20
  • @Arfeen: I 'm not sure I understand what you mean. Could you add some example code to the question? – Jon Apr 17 '12 at 07:22
  • @Arfeen: Are you sure that's exactly what your code looks like, with no typos (e.g. `$prefix.'...'` instead of `$this->prefix.'...'`)? It should be working as far as I can tell. Does it give any notices with `error_reporting(E_ALL)`? – Jon Apr 17 '12 at 07:43
  • yes.. exactly and even if I use $prefix (instead $this->prefix), i get the error "variable not defined $prefix". – Arfeen Apr 17 '12 at 07:55
  • I think you may need to make your prefix variable a static variable, as you're using a Singleton to access it – acorncom Apr 17 '12 at 13:10
  • @acorncom: It's not a singleton, and even if it were the property would still not need to be static *because it would be a singleton* instead of a static class. – Jon Apr 17 '12 at 13:13
1

This is how I've solved that problem.

private static $_tableName;

public function __construct($tableName)
{
    if(strlen($tableName) == 0)
    {
        return false;
    }

    if(strlen($tableName)>0){
        self::$_tableName = $tableName;
    }

    self::setIsNewRecord(true);
}

public static function model($tableName)
{
    if(strlen($tableName) == 0)
    {
        return false;
    }

    $className=__CLASS__;

    if(strlen($tableName)>0){
        self::$_tableName = $tableName;
    }

    return parent::model($className);
}

public function tableName()
{
    return '{{'.self::$_tableName.'}}';
}

public function setTableName($tableName)
{
    self::$_tableName = $tableName;
}

...

When I use this model, I simply put the name of the table in brackets:

$model = new ClassName($tableName);
Orange
  • 105
  • 2
  • 9
  • When providing code that solves the problem, it is best to also give at least a short explanation of how it works so that folks reading won't have to mentally parse it line by line to understand the differences. – Fluffeh Sep 27 '12 at 11:48
  • why do you have a private static property? – Ionut Flavius Pogacian Nov 20 '13 at 20:01
1

I have same problem. I have a model, but I want the data to be saved in different table .So I change the table name before save the data.

public $tbl_name = 'tbl_user_1';//default table
public function tableName()
{
return $this->tbl_name;
}
public function saveDataReg(){
    $mKey = $this->selfMHash($this->username);
    $modKey = $mKey % 2;
    $this->tbl_name = 'tbl_user_' . $modKey;

    $this->refreshMetadata();//change the default Metadata

    $this->save();
}
public function selfMHash($key){
    $md5 = substr(md5($key), 0, 8);
    $seed = 31;
    $hash = 0;

    for($i=0; $i<8; $i++){
        $hash = $hash * $seed + ord($md5{$i});
        $i++;
    }

    return $hash & 0x7FFFFFFF;
}

Now I find the Metadata not be changed when the function of refreshMetadata be used. Last I change the original code

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

    //return var_dump($this);
}

public function new_refreshMetaData()
{
    $finder=self::model(get_class($this));
    $finder->_md=new CActiveRecordMetaData($this);
    if($this!==$finder)
        $this->_md=$finder->_md;

    //return var_dump($this);
}

I override the function of refreshMetaData and chage the param of CActiveRecordMetaData.

$this->new_refreshMetadata();//use new function

Then it worked. I don't understand the reason of problem.

0

You can use with MyModel::model()->tableName(), but please don't forget write {{table}}.

Yakir Sitbon
  • 472
  • 2
  • 8