composite primary keys

I've got a cache model that holds caches of parsed values from other tables (for the instance of the blog tutorial it might hold "contentDisplay".  I am making a behavior to do this).

The model of the cache is named TextCache

It has a composite primary key:

table, id, column

The table column holds the name of the foreign table that it is caching

The id is the id of the entry in that table that it is caching

The column is the column in the entry that it is caching

So I went about setting the relations up in my User model:

<?php


public function relations() {


	return array(


		'textcache' => array(self::HAS_MANY, 'TextCache', 'table, id'),


	);


}


But the 'ON' generated is

Quote

ON t1.`table`=`user`.`id` AND t1.`id`=`user`.`id`

How can I get it to generate:

Quote

ON t1.`table`='user' AND t1.`id`=`user`.`id`

So instead of user.id it is the name of the table? Is this possible? any suggestions?

Maybe it should be a 'condition' instead?  But how would I go about getting this 'condition' injected automatically?

I think your composite relationship is not well defined because user is not a PK in your User table. You have to explicitly specify an 'on' option in your relation declaration for the 'user' column.

So like

'textcache' => array(self::HAS_MANY, 'TextCache', 'id', 'on'=>'table=&#039;'.$this->tableName().'&#039;'),

?

Not sure if the option should be 'on' or 'condition'. I'll try both after school.  It was midnight last night when I wrote the above, so my mind was not working.  Should have thought of this.

I'm really excited about how nicely this behavior is coming together though…

It should be 'on' because this will appear as the join condition, not where clause.

Working great, but there seems to be a bug:

<?php


$user = User::model()->with('textcache')->together->findbyPk($id);


print_r($user->textcache);


The above works great.  It creates only one query  as expected, and prints out the related textcache data.

However, in a loaded behavior I have this code:

<?php


public function afterFind() {


	//print_r($this->Owner->textcache);


}


When I remove the ‘//’, it prints the related data as expected, but it creates another query for it, thus having a redundant query.

I have narrowed it down and found that it only does not within afterFind().  Because this works fine:

<?php


$user = User::model()->with('textcache')->together->findbyPk($id);


$user->test(); //from behavior below





//in behavior


public function test() {


	print_r($this->Owner->textcache);	


}


The above correctly only produces one query.  So maybe afterFind() is internally executed too soon by Yii (eg before the related data is parsed)?

Found the bug (or maybe this is a different one).

<?php


//in controller action





$A = User::model()->with('textcache')->together()->findbyPk($id);


$B = new User();


$A->parseCacheRelation(); // THROWS ERROR


$B->parseCacheRelation(); // works





//where parseCacheRelation() is supposed to be inherited from a behavior


Could you please create a ticket for the first issue? That is, accessing a related object in afterFind() causes a duplicated SQL query.

For the parseCacheRelation() problem, it is because your afterFind() didn't call the parent implementation.

Sure… And some other things I noticed:

<?php


//parseCacheRelation() is from a behavior





//in model


//throws error


public function afterFind() {


	$this->parseCacheRelation();


	parent::afterFind();


}





//works


public function afterFind() {


	parent::afterFind();


	$this->parseCacheRelation();


}





	public function relations() {


		return array(


			'group' => array(self::BELONGS_TO, 'Group', 'group_id'),


			'parsecache' => $this->parseCacheRelation(), //throws error


		);


	}





As for the error in relations() i'm not sure if it's another bug or by design, or I'm doing something wrong

parent::afterFind() should be called first so that behaviors are registered.

You found another bug (calling behavior method in relations())

Could you please create a ticket again? ;) Thanks.

Sure…

BTW the fact that you need to call parent::afterFind() first seems a little confusing and I believe it could easily confuse others.  Do you think there is any way to get around this so you don't have to?

The reason that you need to call parent implementation is similar to beforeSave/afterSave. I understand that you might not feel comfortable at first, but it is fairly standard practice and it also offers you additional flexibility.

Yeah, I suppose it is more flexible anyways…

One more thing:

I would like to check in beforeFind if there is some related data loaded.  I have not tested it but this is what I plan to do:

<?php


public function afterFind($event) {


	if (isset($this->Owner->parsecache)) {


		//stuff


	}


}

However, I don't think it will work by looking at CActiveRecord::__isset

lines 424-425

<?php


	else if(isset($this->getMetaData()->relations[$name]))


		return $this->getRelated($name)!==null;


It looks like by calling isset($this->Owner->parsecache) will actually load the data and then see if it is set.  I just want to see if it is already loaded.

Is this by design, and if so, how else could I check if the related data is already loaded?

Added CActiveRecord::hasRelated().

Also fixed the two issues you logged. Could you help me test them? Thanks!

I got the fix but it still doesn't seem to work:

<?php


//model


	public function relations() {


		return array(


			'group' => array(self::BELONGS_TO, 'Group', 'group_id'),


			'parsecache' => $this->parseCacheRelation(),


		);


	}





//behavior


	/**


	 * Returns relation needed for model


	 */


	public function parseCacheRelation() {


		return array(CActiveRecord::HAS_MANY, 'ParseCache', 'id', 'on'=>'`table`=''.$this->Owner->tableName().''');	


	}





afterFind() is not even defined in the model either

and thanks for these timely patches

btw isn't

return isset($this->_related[$name]) || array_key_exists($name,$this->_related);

redundant?  Is there really any difference between

isset($this->_related[$name])

and



array_key_exists($name,$this->_related)

?

btw this is the skeleton app I am working on so if you need the code for testing and figuring out what's wrong I can update the SVN

svn update and try again?

If an array element is null, isset() will return false. That's why the additional check with array_key_exists(). Because the latter is much slower than isset(), we use both here.

Ah, I see.

Yeah, it's not throwing an error anymore and seems to be working! Thanks!

I have to leave now though for class & won't be active for awhile fyi