Yii2. How to set scenario in dataProvider? - yii2

I want to return different fields depends on scenario. How can I set it in dataProvider?
$query = User::find();
$activeData = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 10,
],
]);
Fields in User model:
public function fields()
{
if ($this->scenario == 'statistics') {
return [
'id',
'email',
'count'
];
}
return [
'id',
'name'
];
}

What about using the $select property?
$query = User::find()->select(['id','email','count']);
$activeData = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 10,
],
]);
Or even better, create an ActiveQuery class for them:
class UserQuery extends ActiveQuery
{
public function statistics()
{
return $this->select(['id','email','count']);
}
/* add as many filtering functions as you need here */
}
Then override the find() method in the User class to use it:
public static function find()
{
return new \app\models\UserQuery(get_called_class());
}
Then do:
$activeData = new ActiveDataProvider([
'query' => User::find()->statistics(),
'pagination' => [
'pageSize' => 10,
],
]);
Note: In default implementation of Yii2 RESTful API you can also select the required fields within url by doing: GET /users?fields=id,email,count

Simply get all the models using getModels() method, set scenario for all of them in the loop and then return data provider. Your example will change into following code:
$query = User::find();
$activeData = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 10,
],
]);
foreach($activeData->getModels() as $model) {
$model->scenario = 'statistics';
}
return $activeData;

Related

Yii2, how to use or operator in rest request

I am trying to use or operator in my rest request Yii2 but I cannot succeed.
Everytime I have this error :
[
{
"field": "filter",
"message": "Operator \"or\" requires multiple operands."
}
]
I tested several things but nothing works.
I want to filter
statut=0 or statut=1
Do you know or I can do it ?
I tried to
http://url/api/tickets/gestions?filter[or][statut][statut]=[0,1]
But it does not work
Here's the method within controller that manage this request :
public function actionIndex()
{
return ActionsHelper::actionIndex(
$this->modelClass,
$this->modelClass . 'Search'
);
}
$this->modelClass is defined above and is equal to 'api\modules\tickets\models\TicketGestion';
Here is ActionsHelper::actionIndex
public function actionIndex($model, $searchModel = null, $moreFilter = null,
$pagination = false)
{
$filterCondition = null;
if ($searchModel) {
$filter = new ActiveDataFilter([
'searchModel' => $searchModel
]);
if ($filter->load(\Yii::$app->request->get())) {
$filterCondition = $filter->build();
if ($filterCondition === false) {
return $filter;
}
}
}
$query = $model::find();
if ($filterCondition !== null) {
$query->andWhere($filterCondition);
}
if ($moreFilter !== null) {
$query->andWhere($moreFilter);
}
if ($pagination !== false) {
$pagination = [
'pageSize' => 100
];
}
return new ActiveDataProvider([
'query' => $query,
'pagination' => $pagination
]);
}
Here's the search model, generated by Gii
<?php
namespace api\modules\tickets\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use api\modules\tickets\models\TicketGestion;
/**
* TicketGestionSearch represents the model behind the search form of `api\modules\tickets\models\TicketGestion`.
*/
class TicketGestionSearch extends TicketGestion
{
/**
* {#inheritdoc}
*/
public function rules()
{
return [
[['id', 'priorite', 'quicree', 'quirea', 'statut', 'recurrentid', 'typerea', 'client'], 'integer'],
[['dispatch', 'service', 'type', 'sujet', 'datecrea', 'dateecheance', 'daterea'], 'safe'],
[['duree'], 'number'],
];
}
/**
* {#inheritdoc}
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$query = TicketGestion::find();
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
if ($this->dispatch == 'null') {
$this->dispatch = 1;
}
// grid filtering conditions
$query->andFilterWhere([
'id' => $this->id,
'priorite' => $this->priorite,
'quicree' => $this->quicree,
'quirea' => $this->quirea,
'datecrea' => $this->datecrea,
'dateecheance' => $this->dateecheance,
'daterea' => $this->daterea,
'duree' => $this->duree,
'statut' => $this->statut,
'recurrentid' => $this->recurrentid,
'typerea' => $this->typerea,
'client' => $this->client,
]);
$query->andFilterWhere(['like', 'service', $this->service])
->andFilterWhere(['like', 'type', $this->type])
->andFilterWhere(['like', 'sujet', $this->sujet])
->andFilterWhere(['likes', 'dispatch', $this->dispatch]);
return $dataProvider;
}
}
You were on a right track, using ActiveDataFilter, but building arrays from get is done like this (example is from my controller):
http://localhost/ntb/web/index.php?r=assembly%2Findex&filter[or][0][status]=1004&filter[or][1][status]=1005&page=1&per-page=10
so for your example it should be like this:
http://url/api/tickets/gestions?filter[or][0][statut]=0&filter[or][1][statut]=1
This was the way to build a working 'or' filter for me.

Use closures in Yii2 ArrayDataProvider

In an ActiveDataProvider you can use closures as values, like:
$dataprovider = new ArrayDataProvider([
'allModels' => $array
]);
$gridColumns = [
'attrib_1',
[
'attribute' => 'attrib_2',
'label' => 'Label_2',
'value' => function($model) {
return Html::encode($model->value_2);
}
],
'attrib_3'
];
echo GridView::widget([
'dataProvider'=> $dataprovider,
'columns' => $gridColumns
]);
Is it possible to do the same or something like this, in an ArrayDataProvider?
Yes. Only difference is that $model is not an object but array so:
'value' => function($model) {
return Html::encode($model['value_2']);
}
For this purpose, I have created an extended version of ActiveDataProvider, that for each model got from provider I call a callback.
This is the custom ActiveDataProvider, put in common\components namespace in this case.
<?php
namespace common\components;
class CustomActiveDataProvider extends \yii\data\ActiveDataProvider
{
public $formatModelOutput = null;
public function getModels()
{
$inputModels = parent::getModels();
$outputModels = [];
if($this->formatModelOutput != null)
{
for($k=0;$k<count($inputModels);$k++)
{
$outputModels[] = call_user_func( $this->formatModelOutput, $k , $inputModels[$k]);
}
}
else
{
$outputModels = $inputModels;
}
return $outputModels;
}
}
This is the action in controller that uses it. For reusability, I call a model method instead calling a clousure, but you can call also a clousure.
public function actionIndex()
{
$query = Model::find();
$dataProvider = new \common\components\CustomActiveDataProvider([
'query' => $query,
'pagination' => ['pageSize' => null],
'formatModelOutput' => function($id, $model) {
return $model->dataModelPerActiveProvider;
}
]);
return $dataProvider;
}
At last, this is the method getDataModelPerActiveProvider in model:
public function getDataModelPerActiveProvider()
{
$this->id = 1;
// here you can customize other fields
// OR you can also return a custom array, for example:
// return ['field1' => 'test', 'field2' => 'foo', 'field3' => $this->id];
return $this;
}

Show only 5 item in yii2 List view

In my yii2 application list view need to show only 5 product limit.
This my View page
<?= ListView::widget( [
'dataProvider' => $dataProvider,
'itemView' => '_item',
'summary' => '',
] ); ?>
and this my controller
$searchModel = new HorseAdsSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
this my Model search Function
public function search($params)
{
$query = HorseAds::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$this->load($params);
if (!$this->validate()) {
return $dataProvider;
}
// grid filtering conditions
$query->andFilterWhere([
'product_id' => $this->product_id,
'producttype' => $this->producttype,
'productname' => $this->productname,
]);
return $dataProvider;
}
Please help me solve this, thanks
Try somthing like this: idea is that set limit with find and set pagination to false
public function search($params)
{
$query = HorseAds::find()->limit(5);
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => false
]);
$this->load($params);
if (!$this->validate()) {
return $dataProvider;
}
// grid filtering conditions
$query->andFilterWhere([
'product_id' => $this->product_id,
'producttype' => $this->producttype,
'productname' => $this->productname,
]);
return $dataProvider;
}

LIMIT is not working in ActiveDataProvider

I am using following code and the limit doesnt work. But if I see the command than it shows limit in that, but when I pass the query to ActiveDataProvider it fetch all the records:
$data= User::find()->where(['category_id'=> 5])->orderBy(['rand()' => SORT_DESC])->limit(4);
$command = $data->createCommand();
$data2 = $command->queryAll();// This works fine and fetch only 4 data
$dataProvider = new ActiveDataProvider([
'query' => $data,
]); // But this displays all data without limit
What is wrong I am doing here?
Here is what happens when preparing models in yii\data\ActiveDataProvider:
/**
* #inheritdoc
*/
protected function prepareModels()
{
if (!$this->query instanceof QueryInterface) {
throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.');
}
$query = clone $this->query;
if (($pagination = $this->getPagination()) !== false) {
$pagination->totalCount = $this->getTotalCount();
$query->limit($pagination->getLimit())->offset($pagination->getOffset());
}
if (($sort = $this->getSort()) !== false) {
$query->addOrderBy($sort->getOrders());
}
return $query->all($this->db);
}
We're interested in this part:
if (($pagination = $this->getPagination()) !== false) {
$pagination->totalCount = $this->getTotalCount();
$query->limit($pagination->getLimit())->offset($pagination->getOffset());
}
So as you can see if pagination is not false, limit is managed automatically.
You can just set pagination to false and then manual setting of limit will work:
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => false,
]);
You can use Limit in the ActiveDataProvider and keep Pagination like so:-
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 50,
],
]);
$dataProvider->setTotalCount(1000);
This will make $this->getTotalCount() = 1000 and Keep the pagination.
Go here and here for more reference.

How to connect 3 tables in yii2 and display in Gridview then make sorting work correctly

I have used the gii tool to create crud application. I have 3 tables the tbl_targetcities, lib_cities, and lib_provinces. I was able to connect lib_cities to tbl_targetciteis but not the lib_provinces. And also the sorting of city / Municipality does not work. It seems that it sorts according ti the ID.
tbl_target_cities
lib_cities
lib_provinces
sample View
So far here is my relation in the model.
public function getCityName()
{
return $this->hasOne(LibCities::className(),['city_code'=>'city_code']);
}
in my view file...
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
[
'attribute'=>'city_code',
'value'=>'cityName.city_name'
],
[
'attribute'=>'prov code',
'value'=>'cityName.city_name'
],
'kc_classification',
'cluster',
'grouping',
'priority',
'launch_year',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
How to display the prov_name from lib_provinces???
EDIT to answer user2839376 question in the comment box
IN THE SEARCH MODEL CLASS
$query = TblSpBub::find();
$query->joinWith('brgyCode')->joinWith(['cityCode'])->joinWith(['cityCode.provCode']);
$covered= LibAreas::find()->where(['user_id'=>yii::$app->user->identity->id])->all();
$query->all();
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort'=> ['defaultOrder' => ['id'=>SORT_DESC]],
]);
$dataProvider->sort->attributes['city'] = [
'asc' => ['lib_Cities.city_name' => SORT_ASC],
'desc' => ['lib_Cities.city_name' => SORT_DESC],
];
$dataProvider->sort->attributes['province'] = [
'asc' => ['lib_provinces.prov_name' => SORT_ASC],
'desc' => ['lib_provinces.prov_name' => SORT_DESC],
];
In LibCities model add new relation:
public function getProvince()
{
return $this->hasOne(LibProvince::className(),['prov_code'=>'prov_code']);
}
And change getCityName relation. You should add with() for relation:
public function getCityName()
{
return $this->hasOne(LibCities::className(),['city_code'=>'city_code'])->with(['province']);
}
And in view correct your columnto this:
[
'attribute'=>'prov code',
'value'=>'cityName.province.prov_name'
],
You have to use the function relations() in models.
In tbl_target_cities model:
public function relations()
{
return array(
'city' => array(self::HAS_ONE, 'LibCities', 'city_code'),
);
}
In LibCities model :
public function relations()
{
return array(
'province' => array(self::HAS_ONE, 'LibProvinces', 'prov_code'),
'targets' => array(self::HAS_MANY, 'TargetCity', 'city_code',
);
}
This will allowed you to jump throw the LibCities model,
now you can simply acces to prov name like this :
$model->city->province->prov name;
Note : You need to have the 3 models defined.
EDIT
array(
'name' => 'province name',
'value' => $data->city->province->prov_name;
),
Done it, Heres how.
in addition to the above code (original post)
// in Model I added an additional function
public function getTaskowner()
{
return $this->hasOne(Tasks::className(), ['id' => 'task_id'])
->with(
['location','taskowner']
);
}
and in view i did this
....
'columns' => [
....
[
'class' => 'kartik\grid\DataColumn',
'value'=> 'tasks.location.taskowner.name',
.....
],
.....
and it worked
key points. used an array with the 'with->(..)' to include both then in the view added 'tasks.location.taskowner.name', to join them all