defaultScope

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.

next (#15) »

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().

3 0
5 followers
Viewed: 33 012 times
Version: Unknown (update)
Category: How-tos
Written by: Gerhard Liebenberg
Last updated by: Gerhard Liebenberg
Created on: Apr 27, 2013
Last updated: 10 years ago
Update Article

Revisions

View all history