This seems to be completely unrelated The article is about searching and sorting by field from related model (the relation is 1:1), while what I’m looking for is to to have CActiveDataProvider filled with model’s HAS_MANY related data
Refering to the example of Author having MANY Posts:
class Author extends CActiveRecord {
...
function relations() {
return array(
'posts'=>array( self::HAS_MANY, 'Post', 'author_id' ),
);
}
Having single Author model instance ( $author = Author::model()->findByPk($id); ) I’d like to obtain CActiveDataProvider filled with $author->posts
Doing this manually looks like this:
$author = Author::model()->findByPk($id);
$cdb = new CDbCriteria();
$cdb->compare('author_id', $author->id);
return new CActiveDataProvider($cdb);
however this sucks a bit, as I need to duplicate both the code for each case I’m using it, and the relation definition.
I was wondering if that is possible using any Yii tricks, at least to obtain CDbCriteria which is used for finding related models ?
I don’t quite understand what you mean by “fill CActiveDataProvider with related models”.
If you just want to retrieve "all" posts that belong to an author, then there should be no need to do something tricky. "$author->posts" will do the job. All that you want is just a CActiveDataProvider for authors.
class Post extends CActiveRecord {
...
function relations() {
return array(
'author'=>array( self::BELONGS_TO, 'Author', 'author_id' ),
);
}
You can get a provider for Post and filter the result by author name for example.
Doesn’t it fit your needs?
[EDIT]
Well, now I think I got it.
Yours is not a question but rather a request about CActiveDataProvider or CArrayDataProvider.
CArrayDataProvider can receive array of model objects, for example $model->posts as rawData. In that case it should be able to tell what model it is dealing with. And CGridView should be able to replace the header labels according to the model.
well, I’m not quite sure if that is a feature request or is there some feature I don’t know of
there are several drawbacks of using CArrayDataProvider with related models array:
it does not allow for sorting, filtering etc
it doesn’t use model’s attributeLabels
all the related models are obtained at once, they’re paginated just before rendering. for huge sets of data it is unnaceptable.
Currently my ActiveRecord class has the following method
/**
* Return the CActiveDataProvider of related models,
* according to relations() definition
* @returns CActiveDataProvider
* @author Michał "migajek" Gajek
*/
public function getRelatedProvider($relation_name)
{
$rels = $this->relations();
if (!is_array($rels) || !isset($rels[$relation_name]))
throw new CException(get_class($this). " has no relation named \"{$relation_name}\" defined!");
$relation = $rels[$relation_name];
if ($relation[0] !== self::HAS_MANY)
throw new CException("getRelatedProvider works with HAS_MANY relations only");
$cdb = new CDbCriteria();
$cdb->compare($relation[2], $this->primaryKey);
return new CActiveDataProvider($relation[1], array(
'criteria' => $cdb,
));
}
It’s just an Author’s view page combined with Post’s admin page.
There’s nothing fantastic in it. It’s boring simple and it works.
I’m afraid that with your getRelatedProvider() we would not be able to filter the output by any attribute other than the FK to the parent model. For example, how do you filter the posts by1) that belong to the author and 2) that has a ‘post_date’ that is later than yesterday? 1) is OK but 2) is not. A provider that getRelatedProvider() returns can’t have a flexible filter.
But how do you construct the additional criteria outside of the function?
You have to create some model instance as the holder of the search parameters if you want some interaction with the user. And you also need some function to construct the criteria based on the user input.
Gii-generated admin action uses a CActiveRecord model instance (Post model instance in this example) for the search parameters. It is used in ‘Advanced Search Form’ and set as the ‘filter’ attribute of the grid. And gii has also generated “search()” method to construct the criteria from the user input.
I thought you were talking about defining criteria in view code
right, my solution is not capable of filtering related data by user-provided filters, but neither is ActiveRecorc relations capable of that.
It’d be easy to modify the method I provided to create instance of model (Post) by its name defined in relation, than use search() method to return CActiveDataProvider, but obtaining data directly from $_POST in model code would be against MVC pattern.
Passing $_POST data to the method should be done in controller, but all I wanted was not to modify the controller
What I proposed and implemented is just a simple way to display model’s related record in CGridView
I didn’t said that is complete solution for obtaining ActiveDataProvider with full filtering capabilities etc
<?php
class ActiveRecord extends CActiveRecord
{
/**
* Returns a model used to populate a filterable, searchable
* and sortable CGridView with the records found by a model relation.
*
* Usage:
* $relatedSearchModel = $model->getRelatedSearchModel('relationName');
*
* Then, when invoking CGridView:
* ...
* 'dataProvider' => $relatedSearchModel->search(),
* 'filter' => $relatedSearchModel,
* ...
* @returns CActiveRecord
*/
public function getRelatedSearchModel($name)
{
$md = $this->getMetaData();
if (!isset($md->relations[$name]))
throw new CDbException(Yii::t('yii', '{class} does not have relation "{name}".', array('{class}' => get_class($this), '{name}' => $name)));
$relation = $md->relations[$name];
if (!($relation instanceof CHasManyRelation))
throw new CException("Currently works with HAS_MANY relations only");
$className = $relation->className;
$related = new $className('search');
$related->unsetAttributes();
$related->{$relation->foreignKey} = $this->primaryKey;
if (isset($_GET[$className]))
{
$related->attributes = $_GET[$className];
}
return $related;
}
}