Create a ActiveDataProvider based on ActiveRecord Relation in Yii2 - yii

I have a many-to-many relation setup using a junction table in MySQL. Table Article is related to Activity via table Article_Activity.
In model Article I have a relation setup like this
public function getActivities()
{
return $this->hasMany(Activity::className(), ['id' => 'activity_id'])
->viaTable('article_activity', ['article_id' => 'id']);
}
When rendering a view for one Article I would like to display a GridView of all Activities related to that Article.
The way most people seem to this is to create a ActiveDataProvider and insert a query into it that fetches related data but that feel a bit redundant since I have the relation setup in the model and there should be a way to get a dataprovider from that.
My question is: Is there a way to get a yii\data\ActiveDataProvider or yii\db\Query based on a instantiated models relation that can be used to display all related records in a GridView?

You can actually call it like this:
$dataProvider = new ActiveDataProvider([
'query' => $article->getActivities(),
]);
If you call the get method directly you get a yii\db\ActiveQueryInterface which is what you need to provide as query to the ActiveDataProvider.
When you call the activities attribute like $article->activities the ActiveQueryInterface is executed and you get the records from the query results.

Based on you Article model you have the activities relation that get multiple Activity related to you Article
a normal dataProvider like
$dataProvider = new ActiveDataProvider([
'query' => Article::find()->joinWith(['activities'])->
where(['your_column'=> $your_value]),
]);
return activeRecord whit also the related activities
you can refer to the related models inside the dataProvider
$models = $dataProvider->models; // this is a collection of model related to the dataProvider query
// so the firts model is $model=models[0]
then for each of this you can obtain acyivities
$activities = $model->activities;
or value
$my_activity_field = $model->activities->my_activity_field

Related

Displaying data from a normalised m-m relationship

I have have 2 tables, ServiceProvider , Entity. There is a m-m relationship between them so I created a link table spEntity which contains the foreign keys from both the other tables.
I have backpack working fine on the 3 tables separately, I use the relationship type on the spEntity CRUD to show the names from the other 2 tables and this all works fine.
However, what I would like to do is when the ServiceProvider record is being created
show a list of all the entities
allow the user to select one and then when the save button is pressed
create the spEntity record.
I have tried
protected function setupCreateOperation()
{
CRUD::setValidation(ServiceProviderRequest::class);
CRUD::field('ServiceProviderName');
CRUD::field('ServiceProviderEmailAddress');
CRUD::field('ServiceProviderDescription');
CRUD::addfield(['name'=>'Entity',
'type'=>'relationship',
'attribute' => 'EntityName',
'pivot'=>true,
'relation_type'=>'belongstomany'
]);
}
but I get the error :
BadMethodCallException PHP 8.1.7 9.19.0
Method Backpack\CRUD\app\Library\CrudPanel\CrudPanel::relationAllowsMultiple does not exist.
Table relations
Is this possible?
Try updating laravel-backpack/CRUD because seems to be an old bug.
Partial solution.
The problem was in my model definition. I need to add in the m-m relationship there.
Model
public function entities()
{
return $this->belongsToMany(Entity::class,'SPEntity','Entity_idEntity','ServiceProvider_idServiceProvider')
->withPivot('idSPEntity')
->withTimestamps();
}
then in the Crud controller
CRUD::addfield(['name'=>'entities',
'type'=>'select2_multiple',
'attribute' => 'EntityName',
'pivot'=>true,
'relation_type'=>'belongstomany'
]);
this now displays the field correctly and I can add further entities to the Service Provider.
However the information is not saved.

Get all data using CActiveDataProvider

I have a model InboxMessageHelper with relations like
'message', 'sender' and 'receiver' and I am using the following criteria to query data:
$model = new CActiveDataProvider('IndividualMessageHelper', array(
'criteria'=>array(
'condition'=>'receiver_id = '.Yii::app()->user->id,
'order'=>'message.created_at DESC',
'with'=>array('message', 'sender', 'receiver'),
'together'=>true,
),
));
I want to get all data (i.e. including the relations data) inside the controller and form a JSON, but the problem is that i cannot access related fields data. I can see that the data is available when I use
CVarDumper::dump()
when I try to encode $model->data then only data from current table gets encoded. How should I go about it?
I don't think CActiveDataProvider can be used in this way. You need to be working with the model. So you'll need something like this in your controller.
$models = IndividualMessageHelper::model()->findAll('receiver_id = '.Yii::app()->user->id);
foreach($models as $model){
$json[] = $model->getAttributes; //This won't get any model properties you've declared
yourself, only database columns
}
//Now get the related records and add them to the array.
array_push($json, $model->getRelated('message')->getAttributes());
array_push($json, $model->getRelated('sender')->getAttributes());
array_push($json, $model->getRelated('receiver')->getAttributes());
echo json_encode($json);

Yii. Change URL based on model parameter value

I would like to use the same model "Work" for handling similar CRUD operations for "Services" and "Markets" pages. There is a column in the Work mysql table called "category". If category is "service", then I would like the Index to show list of Services that are stored in the Work table. Similarly for the "Markets" page.
I would need two urls for the same Model (in the menu and create/ update operations etc.). How can I set this up in the URL Manager?
'services/create/' => 'work/create?&category="services"',
You would do it like this:
array(
//put it first so it has highest priority
'services/create' => 'work/create/category/services',
//other rules follow
)
Then your function in your controller would look like this:
public function actionCreate($category) {
//your code
}

Yii CGridView - Display and paginate from two sources

I have two data sources
1) Database
2) Memcached or Totally different database
From 1st database I am getting the group members list IDs ( has more than 10 thousand rows) and after getting the list I query second database or hit Memcached to get the actual details
$connection = Yii::app()->db;
$sql = 'select user_id,joined_date from group_data where group_id=:group_id ';
$dataProvider = new CSqlDataProvider($sql, array(
'keyField' => 'user_id',
'sort' => array(
'attributes' => array(
'user_id',
'joined_date'
),
'defaultOrder'=>array(
'user_id ASC',
)
),
'pagination' => array(
'pageSize' => 30,
),
'totalItemCount' => $count,
));
$connection_master = Yii::app()->masterdb;
In the above data provider , I am not sure how to include my second database and get the actual user name since it is in other database .No direct connection from one database to other.
Here the problem is pagination is based on the first database table ( 30 records per page) and the actual data is from second table .
Any idea on how to implement this ?
Thank You
You could build your own custom data provider class. You can extend it from CDataProvider and implement the missing abstract methods:
abstract protected function fetchData();
abstract protected function fetchKeys();
abstract protected function calculateTotalItemCount();
Notice, that your storage model already comes very close to these methods: You store the IDs in one database and the actual data in another. So you can focus on one DB at a time in each of these methods.
You'd start with fetchKeys(). There you need to query the keys (=IDs) from your second database. Have a look at CSqlDataProvider::fetchData() to see how to use the CPagination object from $this->getPagination() to find out the current limit and offset. If you need sorting you'd also inspect the CSort object from $this->getSort() for current sort settings. With all that data available you should be able to build the query for the IDs on the currently requested result page.
Then in fetchData() you can obtain those keys through $this->getKeys(). You should not call fetchKeys() directly, because with getKeys() the result from fetchKeys() is "cached", so if it's called again later in the same request, there won't be another query. With that keys you now can easily query your main database. The result will represent the rows on the current page.
Finally you have to implement calculateTotalItemCount(). This is very easy again: Just query your second DB for the total number of items.
That's all you need to do - the code in the base CDataProvider class will take care of the rest and call your methods on demand.

One table two model

i have posts table
id
type enum(A,Q)
title
content
and i need controllers questions_controller and answer_controller to use
please lead the right way to
1-
make 2 models (question and answer) and use 'posts' as model's table
2-
use post model in questions_controller and answer_controller
For me, it should be one table has one model
So better use
$uses = array('Post'); in your controllers.
Update:
As far I understood you want to filter the data depending from the type column. Actually your db model is wrong, because except if they are totally unrelated your answers need to belong to the question. This mean you need an extra column parent_id which will determine what is the parent node (question) and child nodes (answers).
If we have this assumption, then you can set a relation in the model like:
class Post extends AppModel {
...
var $belongsTo = array(
'Parent' => array('className' => 'Post','foreignKey' => 'parent_id', 'conditions' => 'Parent.parent_id IS NOT NULL')
);
...
}
So you automatically have Post and Post->Parent. But I think that it would be better to have 2 tables - one which holds the questions and another the answers.