Hi guys
First the problem:
I have a field in my records called recordstatus. I use it as follows:
recordstatus = 0: Fully restricted. User may not CRUD. (Used for system records.)
recordstatus = 1: Semi restricted. User may read and use it's id as FK. User may not update/delete. Used for default system records that the user may use - such as 'roles' and 'tax percentages'.
recordstatus = 2: Normal user record. User may CRUD.
If I only want to display records with recordstatus >= 1 in my CGridview, then I can do this:
In the controller:
$model=new myTable('search'); $model->unsetAttributes(); $model->recordstatus = 1;
In the model's search function:
public function search() { $criteria=new CDbCriteria; If ($this->recordstatus == null){ // recordstatus was not set in the controller $criteria->condition = 't.recordstatus = :statusNR'; $criteria->params=array(':statusNR'=>100); /* this will return 0 records, because 100 is not a valid recordstatus. */ } else{ $criteria->condition = 't.recordstatus >= :statusNR'; /* note that operator is '>='. Return records with recordstatus >= 1 */ $criteria->params=array(':statusNR'=>$this->recordstatus); } /* your normal other filtering criteria here */ return new CActiveDataProvider($this, array( 'criteria'=>$criteria, )); }
But the problem with the above approach is that these filtering criteria are locked inside your search() function. So you can't use them in a direct find() statement like this:
$posts= myTable::model()->findAll();
What you can do is to repeat these filtering criteria in your controller and then incorporate them in a statement like this:
$posts= myTable::model()->findAll($criteria);
But that could result in having duplicate filtering criteria all over your code.
So how do you have only ONE set of filtering criteria that you can use in both the model's search() function - i.e. dataprovider generation - as well as in your controller (all find() functions)?
Now for the solution:
To the rescue came parameterized named scopes:
Here is an example of a parameterized named scope (pns) with 2 parameters. The first parameter is used to receive an operator - which can be any of these: =, >=, >, <, <=. The second parameter is used to receive the filtering value.
Now for the important bit: The pns works with two methods.
Method-1: You can pass the parameter values from the controller directly to the pns, via any find() function:
$posts= myTable::model()->pns_recordstatus('>=', 1)->findAll();
Method-2: You can store the parameter values in the empty model (let's call it the filtering model) that you create in the controller to:
store the filtering criteria that the user might have entered in the gridview; and
store your own filtering criteria that you want to incorporate into the gridview.
In the model's search() function, the DataProvider will invoke the pns, which will incorporate the filtering model's parameters, so that it will reflect in the CGridView's rows.
In the controller you have the following:
$model=new myTable('search'); $model->unsetAttributes(); $model->var_operator = '>='; $model->var_recordstatus = 1;
var_operator and var_recordstatus are separate fields/properties created in the model to store the operator and filtering value. Yes, create a separate $model->var_recordstatus. Do not use $model->recordstatus (which is part of the normal record) because that will permanently filter the CGridView on recordstatus = 1.
The model:
public $var_operator; public $var_recordstatus; public function search() { $criteria=new CDbCriteria; /* Put your other normal filtering criteria here */ /* Here the dataprovider invokes the pns */ return new CActiveDataProvider($this->pns_recordstatus(), array( 'criteria'=>$criteria, )); } public function pns_recordstatus($operator=null, $value=null) { /*Return no records if neither $operator nor $this->var_operator was set correctly. Method-1: $operator will be set. Method-2: $this->var_operator will be set.*/ if (!in_array($operator,array('=','>=','>','<','<=',)) && !in_array($this->var_operator, array('=','>=','>','<','<=',))){ $this->getDbCriteria()->mergeWith(array( 'condition'=>'t.recordstatus=100', /* this will return 0 records, because 100 is not a valid recordstatus. */ )); } /*Either $operator or $this->var_operator was set correctly. Now continue testing $value and $this->var_recordstatus */ else { /* Test if Method-1 must be used ($value must be between 0 and 2; and $operator must be set correctly) */ if($value >= 0 && $value <= 2 && in_array($operator,array('=','>=','>','<','<=',))){ $this->getDbCriteria()->mergeWith(array( 'condition'=>'t.recordstatus'.$operator.$value, )); } /* Test if Method-2 must be used ($this->var_recordstatus must be between 0 and 2; and $this->var_operator must be set correctly) */ elseif ($this->var_recordstatus >= 0 && $this->var_recordstatus <= 2 && in_array($this->var_operator,array('=','>=','>','<','<=',))){ $this->getDbCriteria()->mergeWith(array( 'condition'=> 't.recordstatus'. $this->var_operator. $this->var_recordstatus, )); } else{ /* Return no records if any other problems */ $this->getDbCriteria()->mergeWith(array( 'condition'=>'t.recordstatus=100', )); } } return $this; }
Tip1:
You can use multiple pns's at the same time; or set flags in your model to determine which pns's to use.
return new CActiveDataProvider($this->pns1()->pns2()->pns3(), array( 'criteria'=>$criteria, ));
Tip2:
Since all my tables have a recordscope field, I have put the pns in my own parent-model, so that all my models can use the same pns over and over again.
Use-Case:
Use method-1 whenever you directly load records e.g. actionUpdate and actionDelete - via loadModel($id).
Use method-2 when you need a dataprovider e.g. CGridView & CListView.
Any corrections or suggestions for improvements are welcome.
Thanx