Scopes

ActiveRecords (models) could have groups named SCOPES. Such CActiveRecord::scopes() are very easy to apply to relational queries and usually used as modifiers for find(). A scope consists of the property values for a CDbCriteria. Consider it as search filters.

1. Add scopes to the Model (e.g. Post)

Array will be used to build CDbCriteria joining several criterias to the search

class Post extends CActiveRecord
{
    …
    public function scopes()
    {
        return array(
            'published'=>array(
                'condition'=>'status=1',
            ),
            'recently'=>array(
                'order'=>'create_time DESC',
                'limit'=>5,
            ),
            'today'=>array(
                'condition'=>'date >= NOW()',
            ),
        );
    }
    …
}

2. Place anywhere in the code (e.g. PostController)
Scopes could only be used with class metods such as ClassName::model()

…
$posts=Post::model()->published()->recently()->findAll();
…

3. Scopes with parameters defined in the separate function in the model

class Post extends CActiveRecord
{
    …
    public function recently($limit=5)
    {
        $this->getDbCriteria()->mergeWith(array(
            'order'=>'create_time DESC',
            'limit'=>$limit,
        ));
        return $this;
    }
    …
}

//For example, to receive last five records:
$posts=Post::model()->published()->recently(3)->findAll();

Since 1.1.7 it’s possible to pass parameters for relational named scopes

…
$users=User::model()->findAll(array(
    'with'=>array(
        'posts'=>array(
            'scopes'=>array(
                // passing only one parameter
                'rated'=>5,
                // passing multiple parameters
                // must be in the same order as function declaration
                'anotherScope'=>array(5, true),
            ),
        ),
    ),
));
 
class Post extends CActiveRecord
{
    …
    public function rated($rating)
    {
        $this->getDbCriteria()->mergeWith(array(
            'condition'=>'rating=:rating',
            'params'=>array(':rating'=>$rating),
        ));
        return $this;
    }
    …
}
…

4. Relations could be defined with scopes (from v. 1.1.9)

class Post extends CActiveRecord
{
    …
    public function relations()
    {
        return array(
	…
             'recentPublishedComments'=>array(self::BELONGS_TO, 'Post', 'post_id', 
                  'scopes' => array('published', 'recent')),
        …
        );
    }
    …
}
class User extends CActiveRecord
{
    …
    public function relations()
    {
        return array(
	…
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
                'with'=>'comments:approved'),
	…
        );
    }
    …
}
// or since 1.1.7
class User extends CActiveRecord
{
    public function relations()
    {
        return array(
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
                'with'=>array(
                    'comments'=>array(
                        'scopes'=>'approved'
                    ),
                ),
            ),
        );
    }
}

5. Default scope applied to all seaches in the model
defaultScope not honored when other scopes are applied
defaultScope should not be applied to UPDATE

class Post extends CActiveRecord
{
    …
    public function defaultScope()
    {
        return array(
            'condition'=>"language='".Yii::app()->language."'",
        );
    }
    …
}

DefaultScope with dynamic table alias

public function defaultScope()
{
    return array(
            'order'=>$this->getTableAlias(false, false).'.update_time DESC',
            'condition'=>$this->getTableAlias(false, false).'.actif =1',
    );
}

6. Scopes could also be used with CActiveRecord::with()

…
$posts=Post::model()->published()->with('comments')->findAll();
…

Multiple scopes get AND combined

…
$posts=Post::model()->with('comments:published:today')->findAll();
…

More complex example:

$user = User::model()->with(array(
        'posts'=>array(
                'with'=>array(
                        'comments'=>array(
                                'scopes'=>array(
                                        'pendingInXWeeks' => 2
                                        )
                                )
                        )
                )
        ))->findByPk($userID);

// and then in my comments model, we're actually calling a named function
        public function pendingInXWeeks($numberOfWeeks="")
        {
                $this->getDbCriteria()->mergeWith(array(
                        'condition' => '', // conditions set here
                        'params' => array(':numberOfWeeks'=>(int)$numberOfWeeks),
            ));
            return $this;
        }

apply named scopes to the related model (eager loading method)

$posts=Post::model()->with('comments:recently:approved')->findAll();
// or since 1.1.7
$posts=Post::model()->with(array(
    'comments'=>array(
        //The relation name and the named scopes should be separated by colons
        'scopes'=>array('recently','approved')
    ),
))->findAll();
// or since 1.1.7
$posts=Post::model()->findAll(array(
    'with'=>array(
        'comments'=>array(
            'scopes'=>array('recently','approved')
        ),
    ),
));

Retrieve a scoped relationship using lazy-loading approach

// note the repetition of the relationship name, which is necessary 
$approvedComments = $post->comments('comments:approved');

7. Scopes are only applicable to SELECT requests only

Yii from Version 1.0.6 allows using named scopes with UPDATE and DELETE methods

HACK: Delete several records with scope

…
Post::model()->deleteAll(
  Post::model->disabled()->getDbCriteria()
);
…

OR

…
$scopes=Post::model()->scopes();
Post::model()->deleteAll($scopes['disabled']);
…

OR

/**
 * Deletes rows with the specified condition, after applying existing scopes
 * See {@link find()} for detailed explanation about $condition and $params.
 * @param mixed $condition query condition or criteria.
 * @param array $params parameters to be bound to an SQL statement.
 * @return integer the number of rows deleted
 */
 public function deleteAllWithScopes($condition='',$params=array())
 {
        Yii::trace(get_class($this).'.deleteAllWithScopes()',
                'system.db.ar.CustomCActiveRecord');
        $builder=$this->getCommandBuilder();
        $criteria=$builder->createCriteria($condition,$params);

        $this->applyScopes($criteria);

        $command=$builder->createDeleteCommand($this->getTableSchema(),$criteria);
        return $command->execute();
 }

8. Scopes are cashing results.
To set a new scope you have to apply resetScope()
resetScope() doesn’t work with default scope as it already included by default

…
$old = Post::model()->published()->recently();
$new = Blog::model()->resetScope()->today();
…

9. Column names cannot conflict with the main table whe you use scopes with relational queries

public function scopes()
{
    $t=$this->getTableAlias(false);
    return array(
        'published'=>array('condition'=>"$t.published=1"),
    );
}

10. Using scope on an instance
Apply a discount named scope on the relation which belong to the product instance

…
$this->price('price:discount');
…

Notes:

  • Ignore select given by criteria or scope on stat relation
  • Criteria of related AR finders was affected after performing find with relational scopes
  • Added scope support to Model::relations()
  • elated table alias set dynamically in relational query is now available in the scopes of related model
  • CDbCriteria can’t merge “with” anymore if a scope applied another “with” condition
  • Added support to call behavior scope through criteria ‘with’=>array(‘scopes’=>’behaviorScope’)
  • CActiveRecord::exists() now respects the scopes applied
  • ‘join’ in default scopes is now respected by STAT relations
  • Added parametrized named scopes, added scopes to criteria, implemented scope criteria merging
  • Table alias declared in scopes may be ignored when performing relational findByPk and findByAttributes queries
  • Conditions declared in scopes of the related AR classes will be put in the ON clause of the JOIN statement
One comment on “Scopes
  1. rashmani says:

    Hey! Big ups!
    This is a GREAT page, thanks a lot!

    I’ve particularly found useful the hack to get dbCriteria separately for deleteAll(). Cool!

    rash*

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Recent Posts
Recent Comments
Archives
Categories
%d bloggers like this: