Default "filter" on CActiveRecord (Model)

Hi

I have a table of objects/entities that are of different kinds as defined in a field of that table.

With Gii, I generated a model for this:


class Entity extends CActiveRecord

{

}

I would like to create derived classes where the derived class would behave for a specific kind. So basically, I would like to limit search operations to kinds of a certain index (for instance: 2, 3, …) and upon create automatically set the kind to the given type.

Example:


class User extends Entity

{

}


class Group extends Entity

{

}

Where I would ensure the ‘kind’ is 1 for User and ‘kind’ is 2 for Group.

It doesn’t look like it is intrinsically supported, but maybe I am missing something and this can be done through a ‘simple’ configuration (modifying ‘rules’, or ‘scopes’, or …).

I am looking forward towards your feedback ;-).

I found part of the answer - overwrite ‘defaultScope’ for the search criteria.

Part of the CActiveRecord code (including the comment):




	/**

	 * Returns the default named scope that should be implicitly applied to all queries for this model.

	 * Note, default scope only applies to SELECT queries. It is ignored for INSERT, UPDATE and DELETE queries.

	 * The default implementation simply returns an empty array. You may override this method

	 * if the model needs to be queried with some default criteria (e.g. only active records should be returned).

	 * @return array the query criteria. This will be used as the parameter to the constructor

	 * of {@link CDbCriteria}.

	 * @since 1.0.5

	 */

	public function defaultScope()

	{

		return array();

	}

Use defaultScope and beforeValidate (or beforeSave) handlers, you can also use "instantiate" handler on Entity model:





class Entity extends CActiveRecord {

  protected function instantiate( $attributes ) {

    switch( $attributes['kind'] ) {

      case 1: return new User( null );

      case 2: return new Group( null );

      ///...

      default: throw new CException( 'Unknown entity kind!' );

    }

  }

}


class User extends Entity {


  public function defaultScope() {

    return array( 'condition'=>'t.kind = :kind', 'params'=>array( ':kind'=>1 ) );

  }


  public function beforeValidate() {

    $this->kind = 1;

    return parent::beforeValidate();

  }




}



you can also use beforeSave instead of beforeValidate if you want to ensure that “kind” attribute won’t be changed by rules() during validation.

Now - the interesting thing is "instantiate" method in Entity model - it will cause Yii to instantiate proper class when fetching data from DB, so even when you do "findAll" returned array of objects will contain "User" and "Group" objects depending on "kind" attribute.

Hope this helps…

Hi

Thank you for the feedback & the ‘instantiate’ tip is an interesting extension to the initial request.

I’ve implemented this - I’ll see how it goes - I think it is perfect.

Hi

I finally got an issue when using relations. It could be a Yii bug.




class Entity {

public relations() {

return array(

		'parentChildren' => array(self::HAS_MANY, 'ParentChild', 'parent_id'),

		'groups'=> array(self::HAS_MANY,'Group','parent_id',

						'alias'=>'g',

						'through'=>'parentChildren',

						'condition'=>'g.type_id=2'),

	);

}

}


class Group {

	const TYPEID = 2;


	public function defaultScope() {

		return array (

				'condition'=>'t.type_id='.self::TYPEID

		);

	}

}



That fails - the SQL query looks like this:


[font=Verdana][size=2]SELECT `g`.`entity_id` AS `t1_c0`, `g`.`context_id` AS `t1_c1`, `g`.`type_id` AS `t1_c2`, `g`.`is_active` AS `t1_c3` AS `t1_c4` FROM `entity` `g` LEFT OUTER JOIN `parent_child` `parentChildren` ON (`parentChildren`.`parent_id`=`g`.`entity_id`) WHERE (g.type_id=2) AND (t.type_id=2) AND (`parentChildren`.`parent_id`=:ypl0)[/size][/font]

The issue is with ‘[font=Verdana][size=2] (t.type_id=2)’ where ‘t’ should refer to ‘g’ which is not available as a reference in the query. [/size][/font]

[font=Verdana][size=2]Referring to Entity in the scope instead of Group, the query issue disappears because the Entity does not have defaultScope.[/size][/font]

[font=Verdana][size=2]

[/size][/font]

[font=Verdana][size=2]This looks like a ‘Yii’ problem because I think that using defaultScope in a target of a HAS_MANY relation (here further down when using ‘through’) gives trouble with the table alias.[/size][/font]

First, the “t” alias refers to the model when it’s used as primary in the query (not as related model).

Try getTableAlias() instead (in the defaultScope).

/Tommy

Well, not a Yii bug, but apparently bad usage of ‘t’.

This solves it:


class Entity {

public relations() {

return array(

                'parentChildren' => array(self::HAS_MANY, 'ParentChild', 'parent_id'),

 		'groups'=> array(self::HAS_MANY,'Group','parent_id',

						'through'=>'parentChildren'),

       );

}

}


class Group {

        const TYPEID = 2;


        public function defaultScope() {

		return array (

				'condition'=>$this->getTableAlias(false, false) .'.type_id='.self::TYPEID

		);        }

}

and results in a SQL request like this:


[font=Consolas,]SELECT `groups`.`entity_id` AS `t1_c0`, `groups`.`context_id` AS `t1_c1`, `groups`.`type_id` AS `t1_c2`, `groups`.`is_active` AS `t1_c3` FROM `entity` `groups`  LEFT OUTER JOIN `parent_child` `parentChildren` ON (`parentChildren`.`parent_id`=`groups`.`entity_id`) WHERE (groups.type_id=2) AND (`parentChildren`.`parent_id`=:ypl0)[/font]

So the check of ‘type_id’=2 gets added automatically and is no longer needed explicitly in the relation. Courtisy of

another forum entry