Yii CGridView - Display and paginate from two sources - yii

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.

Related

Create a ActiveDataProvider based on ActiveRecord Relation in Yii2

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

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's CArrayDataProvider and new rows

Is there a way to show in a Grid that takes it data from CActiveDataProvider some new rows that are not actually yet into the database?
here's my scenario..
I have to fill by X times (the quantity of a product in a bill) and provide to each row the possibility to be edited and saved
Product QTY
product1 3
|
____ Edit item 1 (not yet in the db)
____ Edit item 2 (not yet in the db)
____ Edit item 3 (not yet in the db)
So it's kind of like a master detail grid (That I've sorted it out how to do it..)
but I can't display things on a grid that arren't yet in the database..
I know I have to create an array of temporary models like $model[]=new MODEL(); and push the somehow to the CActiveDataProvider but don't know the syntaxis...
You can't do this with CActiveDataProvider because as the documentation says
CActiveDataProvider provides data in terms of ActiveRecord objects which are of class modelClass. It uses the AR CActiveRecord::findAll method to retrieve the data from database.
However, there is CArrayDataProvider. You can put arbitrary data in it, ie. merge an array of real objects with an array of empty ones and use that.
$rawData=array(
array('id'=>1, 'username'=>'from', 'email'=>'array'),
array('id'=>2, 'username'=>'test 2', 'email'=>'hello#example.com'),
);
$arrayDataProvider=new CArrayDataProvider($rawData, array(
'id'=>'id',
'sort'=>array(
'attributes'=>array(
'username', 'email',
),
),
'pagination'=>array(
'pageSize'=>10,
),
));
As you can see, the data here is a simple array, but you can even sort the data.

Yii recognise timestamp column

I'm using giix to extend model (and crud) behavior. In this I would like to handle columns of type timestamp (that already exist in my model) specifically, rather like autoincrement fields are handled. (Ignored and not shown, that is.) However, there is no property $column->isTimestamp. I would like to add this somewhere, but I'm rather at loss what the best place for this would be. Do I put it in giix somewhere, or do I have to extend the column-baseclass?
Edit: I want to ignore them from every view, for every table. Since this is a lot of work, and it's something I always want, I'd like to automate it. Adapting the generators seems to make most sense, but I'm not sure what the best way to do it would be.
Here is the process:
Extend your database schema, if you are on MySQL it is CMysqlSchema.
Extend CMysqlColumnSchema and add a "isTimestamp" attribute.
In your CMysqlSchema sub-class extend createColumn and test for a timestamp, you'll see that Yii makes simple string comparisons here to set it's own flags. Set "isTimestamp" in your CMysqlColumnSchema here.
Tell Yii to use your schema driver like this in your components section in the config:
'db' => array(
'connectionString' => 'mysql:host=localhost;dbname=database',
'username' => '',
'password' => '',
'driverMap' => array('mysql' => 'CustomMysqlSchema'),
),
You will need to query the column schema, I've not used giix but find where it is generating the views, it should be looping through either the model attributes or the underlying table schema.
If it is looping through the schema:
//you can also ask Yii for the table schema with Yii::app()->db->schema->getTable[$tableName];
if ('timestamp' === $tableSchema->columns[$columnName]->dbType)
continue; //skip this loop iteration
If it loops over the attributes:
$dbType = Yii::app()->db->schema->getTable[$model->tableName]->columns[$modelAttribute]->dbType;
if ('timestamp' === $dbType)
continue; //skip this loop iteration

SUM query in a CGridView column

I'm playing around with the CGridView widget and I'm trying to find out if it would be appropriate to use it in the following context:
Lets say I have two database tables, Car and Accident. Each car may be associated to zero or more accidents. I would like to have a column in the grid view containing the number of accidents for each car, assuming that a row in the grid view represents a car.
Is this feasible using a GridView widget, or should I try some other approach?
You can define relation for your Car model as follows:
public function relations()
{
return array(
'accidents' => array(self::HAS_MANY, 'Accidents', 'Id'),
'accidentsCount' => array(self::STAT, 'Accidents', 'AccidentId'),
);
}
If you use count($model->accidents) then you will load all related models and then use PHP count function to get number of models, if you care about performance and memory you will use stat relation accidentCount to get number of accidents.
If you have the relation "cars have one to many accidents" a simple count on the relation can be used. $model is car here.
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'car-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
'name',
array(
'header'=>'Accidents',
'value'=>'count($data->accidents)',
),
),
));
The easiest way is to give the car model a "getNumberOfAccidents()" getter. You can then use that in the grid as a regular column (numberOfAccidents). Easy as pie :)