Yii Framework Forum: CActiveDataProvider - fill up with model related items - Yii Framework Forum

Jump to content

Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

CActiveDataProvider - fill up with model related items Rate Topic: -----

#1 User is offline   migajek 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 78
  • Joined: 15-November 09

Posted 26 May 2012 - 07:37 AM

Hi,
I'd like to fill CActiveDataProvider with model's related items.
Lets say I have an "Order" model, which has many "Item" models related (HAS_MANY).
I'd like to do something like new CActiveDataProvider($model->items).
Using CArrayDataProvider is not quite the same, as using model attributeLabels for grid headers is much more convenient.

Currently I'm setting up CDbCriteria manually, however I'd like to use the relation definition (following DRY ;) )
0

#2 User is offline   bennouna 

  • Master Member
  • PipPipPipPip
  • Yii
  • Group: Members
  • Posts: 1,157
  • Joined: 05-January 12
  • Location:Morocco

Posted 26 May 2012 - 07:46 AM

Maybe you missed this wiki? Searching and sorting by related model in CGridView
0

#3 User is offline   migajek 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 78
  • Joined: 15-November 09

Posted 26 May 2012 - 04:32 PM

View Postbennouna, on 26 May 2012 - 07:46 AM, said:


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 ?
0

#4 User is offline   softark 

  • Keep It Simple
  • Yii
  • Group: Moderators
  • Posts: 1,611
  • Joined: 16-February 11
  • Location:Japan

Posted 26 May 2012 - 06:24 PM

Hi,

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.

View Postmigajek, on 26 May 2012 - 04:32 PM, said:

Doing this manually looks like this:
$author = Author::model()->findByPk($id);
$cdb = new CDbCriteria();
$cdb->compare('author_id', $author->id);
return new CActiveDataProvider($cdb);



The last line should be either
return new CActiveDataProvider('Post', $cdb);

Or
return new CActiveDataProvider('Author', $cdb);

Which do you want?

And what use case do you have in mind?
0

#5 User is offline   migajek 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 78
  • Joined: 15-November 09

Posted 27 May 2012 - 10:17 AM

View Postsoftark, on 26 May 2012 - 06:24 PM, said:

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.

Not really. $author->posts returns array of retrieved Posts models, assigned to the Author.
For some cases, like using CGridView, CActiveDataProvider is required.

Quote

The last line should be either ...

You're right, my bad. It should be
return new CActiveDataProvider('Post', $cdb);

0

#6 User is offline   softark 

  • Keep It Simple
  • Yii
  • Group: Moderators
  • Posts: 1,611
  • Joined: 16-February 11
  • Location:Japan

Posted 27 May 2012 - 10:33 AM

I see.

Then what about using 'author' relation of Post?
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.
0

#7 User is offline   migajek 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 78
  • Joined: 15-November 09

Posted 27 May 2012 - 01:09 PM

View Postsoftark, on 27 May 2012 - 10:33 AM, said:

Yours is not a question but rather a request about CActiveDataProvider or CArrayDataProvider.

well, I'm not quite sure if that is a feature request or is there some feature I don't know of ;)

Quote

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.

there are several drawbacks of using CArrayDataProvider with related models array:
1. it does not allow for sorting, filtering etc
2. it doesn't use model's attributeLabels
3. 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,
        ));
    }


which is used as following:

<?php $this->widget('bootstrap.widgets.BootGridView', array(
    'dataProvider'=> $model->getRelatedProvider('posts'),
)); ?>


This method should be extended to handle all the relation parameters, currently it just takes the model name and foreign key.

I was wondering if there's any ready to use method in Yii core.
If not, I believe there should be one ;)
0

#8 User is offline   softark 

  • Keep It Simple
  • Yii
  • Group: Moderators
  • Posts: 1,611
  • Joined: 16-February 11
  • Location:Japan

Posted 27 May 2012 - 08:35 PM

Um, interesting, but ...

I'd rather keep things simple as they are.
For example, when Author HAS_MANY Posts, I would write a view page for Author like this:
// controller
public function actionView($id)
{
	$author = $this->loadModel($id);

	$post = new Post('search');
	$post->unsetAttributes();
	$post->author_id = $author->id;
	if (isset($_GET['Post']))
	{
		$model->attributes = $_GET['Post'];
	}

	$this->render('view',array(
		'author' => $author,
		'post' => $post,
	));
}
...
// view
$this->widget('zii.widgets.CDetailView', array(
	'data'=>$author,
	...
));
$this->widget('zii.widgets.grid.CGridView', array(
	'dataProvider' => $post->search(),
	'filter' => $post,
	...
));


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.
0

#9 User is offline   migajek 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 78
  • Joined: 15-November 09

Posted 28 May 2012 - 03:02 AM

View Postsoftark, on 27 May 2012 - 08:35 PM, said:

I'd rather keep things simple as they are.
For example, when Author HAS_MANY Posts, I would write a view page for Author like this:
...
There's nothing fantastic in it. It's boring simple and it works.

that would be so much against DRY principle ;) Following that way, one can easily say that using relations doesn't make sense at all.
In the application I'm building I'm using grids and detailviews extensively, and repeating so much code would really be boring as you mentioned ;)

Quote

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. [...] A provider that getRelatedProvider() returns can't have a flexible filter.

You are right. I haven't been using it, so didn't implemented, but that could be easily extended, with a simple modification:

public function getRelatedProvider($relation_name, $criteria = array())
...
$cdb->compare($relation[2], $this->primaryKey);
$cdb->mergeWith($criteria);
...


how about that? ;)
0

#10 User is offline   softark 

  • Keep It Simple
  • Yii
  • Group: Moderators
  • Posts: 1,611
  • Joined: 16-February 11
  • Location:Japan

Posted 28 May 2012 - 06:16 AM

View Postmigajek, on 28 May 2012 - 03:02 AM, said:

public function getRelatedProvider($relation_name, $criteria = array())
...
$cdb->compare($relation[2], $this->primaryKey);
$cdb->mergeWith($criteria);
...



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.
0

#11 User is offline   migajek 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 78
  • Joined: 15-November 09

Posted 28 May 2012 - 06:31 AM

View Postsoftark, on 28 May 2012 - 06:16 AM, said:

But how do you construct the additional criteria outside of the function?

I thought you were talking about defining criteria in view code ;)

Quote

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.

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 ;)
0

#12 User is offline   softark 

  • Keep It Simple
  • Yii
  • Group: Moderators
  • Posts: 1,611
  • Joined: 16-February 11
  • Location:Japan

Posted 28 May 2012 - 06:52 AM

Yeah, I think I've got your points in discussion. And hopefully you got mine, too. :)
0

#13 User is offline   cappadochian 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 113
  • Joined: 02-January 11

Posted 03 December 2012 - 08:09 PM

hi guys!

so what is final conclusion? is there a way, or is there no way?
I think I need the same thing, but so far I don't think I have found the right solution.
0

#14 User is offline   Motin 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 5
  • Joined: 17-May 10

Posted 14 June 2013 - 09:52 PM

Helpful thread, thanks!
I ended up with a hybrid of the above approaches:

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

}

0

Share this topic:


Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users