问题描述:

Table.linkedIndex is related to LinkedIndex.ID. The value of the field LinkedIndex.TableName is either Linked1 or Linked2 and defines which of these tables is related to a row in Table.

Now i want to make a dynamical link with Yii models so that i can easily get from a Table row to the corresponding Linked1 or Linked2 row:

Table.linkedID = [LinkedIndex.TableName].ID


Example

Table values:

LinkedIndex values:

Now I should get the row from Linked2 where ID=2:

$model = Table::model()->findByPk(0);

$row = $model->linked;


Model

In the model Table, I tried to make the relation to the table with the name of the value of linkedIndex.TableName:

public function relations()

{

return array(

'linkedIndex' => array(self::HAS_ONE, 'LinkedIndex', array('ID' => 'linkedIndex')),

'linked' => array(

self::HAS_ONE,

'linkedIndex.TableName',

array('ID' => 'linkedID'),

)

)

}

But then I get the error:

include(linkedIndex.TableName.php) [function.include]: failed to open stream: No such file or directory


Is there any way to make a dynamic relation Table.linkedID -> [LinkedIndex.TableName].ID with Yii Models?

网友答案:

Per the Yii docs here:

http://www.yiiframework.com/doc/api/1.1/CActiveRecord#relations-detail

I'd suggest using self::HAS_ONE instead (unless there can be multiple rows in LinkedIndex with the same ID - although from the looks of above, I doubt that's the case).

You can link tables together that have different keys by following the schema:

foreign_key => primary_key

In case you need to specify custom PK->FK association you can define it as array('fk'=>'pk'). For composite keys it will be array('fk_c1'=>'pk_с1','fk_c2'=>'pk_c2').

so in your case:

public function relations(){
    return array(
        'linkedIndex' => array(self::HAS_ONE, 'LinkedIndex', array('ID' => 'linkedIndex')),
    );
}

where LinkedIndex is the class name for the LinkedIndex model (relative to your Table model - i.e. same folder. You could change that, of course) and array('ID' => 'linkedIndex') specifies the relationship as LinkedIndex.ID = Table.linkedIndex.

Edit

Looking at your updated example, I think you're misunderstanding how the relations function works. You're getting the error

include(linkedIndex.TableName.php) [function.include]: failed to open stream: No such file or directory

because you're trying to create another relation here:

'linked' => array(
    self::BELONGS_TO,
    'linkedIndex.TableName',
    array('ID' => 'linkedID'),
)

This part: linkedIndex.TableName refers to a new model class linkedIndex.TableName, so Yii attempts to load that class' file linkedIndex.TableName.php and throws an error since it doesn't exist.

I think what you're looking for is to be able to access the value TableName within the table LinkedIndex, correct? If so, that's accessible from within the Table model via:

$this->linkedIndex->TableName

This is made possible by the relation we set up above. $this refers to the Table model, linkedIndex refers to the LinkedIndex relation we made above, and TableName is an attribute of that LinkedIndex model.

Edit 2

Per your comments, it looks like you're trying to make a more complex relationship. I'll be honest that this isn't really the way you should be using linking tables (ideally you should have a linking table between two tables, not a linking table that says which 3rd table to link to) but I'll try and answer your question as best as possible within Yii.

Ideally, this relationship should be made from within the LinkedIndex model, since that's where the relationship lies.

Since you're using the table name as the linking factor, you'll need to create a way to dynamically pass in the table you want to use after the record is found.

You can use the LinkedIndex model's afterFind function to create the secondary link after the model is created within Yii, and instantiate the new linked model there.

Something like this for your LinkedIndex model:

class LinkedIndex extends CActiveRecord{
    public $linked;

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

    public function tableName(){
        return 'LinkedIndex';
    }

    public function afterFind(){
        $this->linked = new Linked($this->TableName);

        parent::afterFind();
    }

    //...etc.
}

The afterFind instantiates a new Linked model, and passes in the table name to use. That allows us to do something like this from within the Linked model:

class Linked extends CActiveRecord{
    private $table_name;

    public function __construct($table_name){
        $this->table_name = $table_name;
    }

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

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

    //...etc.
}

which is how we dynamically create a class with interchangeable table names. Of course, this fails of the classes need to have separate operations done per-method, but you could check what the table_name is and act accordingly (that's pretty janky, but would work).

All of this would result in being to access a property of the linked table via (from within the Table model):

$this->linkedIndex->linked->foo;
网友答案:

Because the value of LinkedIndex.TableName and Table.linkedID is needed to get the values, I moved the afterFind, suggested by M Sost, directly into the Table-Class and changed its content accordingly. No more need for a virtual model.

class Table extends CActiveRecord {
    public $linked; // Needs to be public, to be accessible
    // ...etc.
    public function afterFind() {
        $model = new $this->linkedIndex->TableName;
        $this->linked = $model::model()->findByPk( $this->linkedID );
        parent::afterFind();
    }
    // ...
}

Now I get the row from Linked2 where ID=2:

$model = Table::model()->findByPk(0);
$row = $model->linked;
相关阅读:
Top