You are viewing revision #14 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version.
This example includes a composite condition as well as an empty condition - as if you bypass or disable defaultScope without using resetScope().
If you want to apply constant filter(s) to all your tables - even to related tables when using Relational Query - then currently (Yii 1.1.12) defaultScope() is your best option.
First, create your own base model which will hold the defaultscope. Lets call it myBaseModel.
All your other models that should use defaultScope should extend from myBaseModel. (Note that we can also temporarily "bypass" defaultScope for these models if needed.)
Models that should never use this partiqular defaultScope can still extend from CActiveRecord.
Your individual models that should be subjected to defaultScope():
class tbl1_model extends myBaseModel
{
public $modelName = __CLASS__;
public $rstatus_fieldname = 'tbl1_rstatus_nr';
...
}
class tbl2_model extends myBaseModel
{
public $modelName = __CLASS__;
public $rstatus_fieldname = 'tbl2_rstatus_nr';
...
}
Your individual models that should NOT be subjected to defaultScope():
class tbl32_model extends CActiveRecord
{
...
}
I added two variables to each model that extends myBaseModel. Both these variables are used by defaultScope(). $modelName is used to include the model's class as alias in the defaultScope's condition. $rstatus_fieldname is used to filter the records.
Record filtering: Each table has a field that indicates a record's status:
Record Status = 1 : record is inactive.
Record Status = 2 : record is active.
The defaultScope must be able to access these record status fields and that is why we store their names in $rstatus_fieldname. This enables defaultScope to re-use the same name for all tables. But in the db, the field's name in tbl1 is tbl1_rstatus_nr. In tbl2 it is called tbl2_rstatus_nr.
Obviously you could give these fields the same name in all tables and then reference them directly in defaultScope (without using $rstatus_fieldname), but my experience is that defaultScope sometimes have trouble disambiguating the fields if you tunnel through tables via Relational Query. So I prefer disambiguating them in the db with tbl1_rstatus_nr, tbl2..., tbl3... etc.
Here is the base class with the defaultScope():
class myBaseModel extends CActiveRecord
{
public function defaultScope()
{
$condition = $this->testStatus();
return array(
'alias' => $this->modelName,
'condition' => $condition,
);
}
public function testStatus()
{
/* Default condition: tblX_rstatus_nr = 0
(return no records unless further tests in this function are
successful)
(0 is an invalid option, thus no records are returned) */
$condition = $this->modelName . "." . $this->rstatus_fieldname . " = 0";
/* Bypass defaultScope().
At the start of code sections in your controller where you
do not want to apply defaultScope, do this:
Yii::app()->user->setState('skipDefaultScope', True);.
REMEMBER to unset(Yii::app()->user->skipDefaultScope) at
the end of these sections, AND before any 'return'
statement in these sections, AND in
siteController/actionError so that it is unset after
unexpected exceptions. */
if(isset(Yii::app()->user->skipDefaultScope))
{
if(Yii::app()->user->skipDefaultScope === TRUE)
{ //Return all records
$condition = "";
}
}
/* If user is GUEST:
In some controllers, the user might not be login yet, so
you can't use Yii::app()->user->skipDefaultScope.
You therefor have to stipulate these conditions, under
which defaultScope must only return active records - or
whatever you want. */
elseif(Yii::app()->user->isGuest)
{
/* Test if the defaultScope should not be applied to
the current controller.action.model combination */
$controller = Yii::app()->controller->id;
$action = Yii::app()->controller->action->id;
if(in_array($controller . '/' . $action . '/' . $this->modelName,
array(
'site/login/tbl7_users',
'site/login/tbl8_user_roles',
)
))
{ //Return all active records.
$condition =
$this->modelName . "." . $this->rstatus_fieldname . " = 2";
}
}
/* Logged in Users. */
else
{
if(user may view only active records)
{ //Return all active records.
$condition =
$this->modelName . "." . $this->rstatus_fieldname . " = 2";
}
elseif(user may view active and inactive records)
{ //Return all active and inactive records.
$condition =
$this->modelName . "." . $this->rstatus_fieldname . " > 0";
/* You can also build a composite condition:
$condition =
$this->modelName . "." . $this->field1 . " <= 8"
. " AND " .
$this->modelName . "." . $this->field2 . " = " . $this->field3
. " AND " .
$this->modelName . "." . $this->field4 . " = 1"; */
}
}
return $condition;
}
?>
Tip: defaultScope() only checks records read from the db. If you want similar control over records being written to db, then do something similar in beforeValidate() or beforeSave() and beforeDelete().
table alias
You do know that CActiveRecord.tableAlias could be used in defaultScope() method to make it usable in relation queries (eager/lazy loading using with)?
table alias
Hi nineinchnick
I tried $this->getTableAlias(), but I ran into the same memory problems as described in this topic.
I also tried $this->tableName(), which is what was used at the end of that topic, but that also did not work - I think because the defaultScope's condition is set on the model's name, not the table's name.
table alias
Which Yii version are you using? The topic you referenced is 3 years old. I've checked current api docs and sources and there should not be any endless loop.
Check the docs for arguments to getTableAlias().
I'm using tableAlias in defaultScope() in many projects without problems.
table alias
Okay, in the test where we determined what records the defaultScope should return for GUESTS, we stipulated the controller, action and model.
However, if you don't need that much control; if you only need to stipulate the controller and action (not the model as well), then you can replace all these statements:
$this->modelName
with this statement:
$this->getTableAlias(false, false)
You can then also remove $modelName from your models and amend the test to only check the controller and action.
But I think in beforeSave(), you will still need the extra control to restrict the tables that guests may write data to. In that case, you will still have to be able to get the model's CLASS . So maybe don't remove all the $modelName properties just yet.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.