Yii 1.1: defaultScope

6 followers

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 in this wiki we regard them as being disambiguated in the db like 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().

Total 4 comments

#13027 report it
Gerhard Liebenberg at 2013/04/29 05:49am
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.

#13026 report it
nineinchnick at 2013/04/29 05:04am
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.

#13024 report it
Gerhard Liebenberg at 2013/04/29 04:34am
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.

#13016 report it
nineinchnick at 2013/04/29 01:03am
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)?

Leave a comment

Please to leave your comment.

Write new article