Reusing queries like Yii1 parameterized scope and criteria merging

I’ve been migrating from Yii1 to Yii2. One feature that I’ve been using in the Yii1 is parameterized scope and criteria merging.

For example I have a book with a field authorId, a flag isRemoved (I really hate deleting rows from database, I just like flagging it), and status whether a book is in the shelf or not.




/**

 * @property authorId

 * @property isRemoved

 * @property status

 */

class Book extends CActiveRecord {

   const STATUS_IS_NOT_REMOVED = 0;

   const STATUS_IS_IN_SHELF = 0;

   const STATUS_IS_BORROWED = 1;


   const SCOPE_FILTER_WHERE_IS_NOT_REMOVED = 'scopeIsNotRemoved';

   const SCOPE_FILTER_WHERE_IS_BORROWED = 'scopeIsBorrowed';

   const SCOPE_FILTER_BY_AUTHOR_ID = 'scopeFilterByAuthorId';


   public function scopes(){

       $t = $this->tableAlias(); //avoid when used in the JOIN

       return [

           self::SCOPE_FILTER_WHERE_IS_NOT_REMOVED => [

              'condition' => "`{$t}`.`isRemoved` = :notremoved",

              'params' => [

                 ':notremoved' => self::STATUS_IS_NOT_REMOVED,

              ]

           ],

           self::SCOPE_FILTER_WHERE_IS_BORROWED => [

              'condition' => "`{$t}`.`status` = :borrowed",

              'params' => [

                 ':borrowed' => self::STATUS_IS_BORROWED,

              ]

           ],

       ];

   }


   public function scopeFilterByAuthorId($authorId){

      $t = $this->tableAlias();

      $this->getCriteria()->mergeWith([

          'condition' => "`{$t}`.`authorId` = :authorId",

          'params' => [

              ':authorId' => $authorId

          ]

      ]);

      return $this;

   }

}



So let’s say I have a controller that display list of books that can be parameterized by $_GET params. The code above will be so useful.




     public function actionIndex($showBorrowed = null, $authorId = null){

         $criteria = new CDbCriteria([

             'scopes' => [

                 Book::SCOPE_FILTER_WHERE_IS_NOT_REMOVED,

             ]

         ]);


         if (!empty($showBorrowed)){

             $criteria->mergeWith(new CDbCriteria([

                 'scopes' => [

                     Book::SCOPE_FILTER_WHERE_IS_NOT_BORROWED,

                 ]

             ]));

         }

         if (!empty($authorId)){

             $criteria->mergeWith(new CDbCriteria([

                 'scopes' => [

                     Book::SCOPE_FILTER_BY_AUTHOR_ID => [$authorId],

                 ]

             ]));

         }


            

         $dataProvider = new CActiveDataProvider(Book::model(), [

             'criteria' => $criteria,

         ]);

         $this->render('index', [

             'dataProvider' => $dataProvider

         ]);

     }



or maybe in other controller for author panel, I can do this to show books from current logged user (i.e. the author herself).




     public function actionIndex(){

         $criteria = new CDbCriteria([

             'scopes' => [

                 Book::SCOPE_FILTER_WHERE_IS_NOT_REMOVED,

                 Book::SCOPE_FILTER_BY_AUTHOR_ID => [Yii::app()->user->id],

             ]

         ]);            

         $dataProvider = new CActiveDataProvider(Book::model(), [

             'criteria' => $criteria,

         ]);

         $this->render('index', [

             'dataProvider' => $dataProvider

         ]);

     }



Using the scope and parameterized scope, I can do this really easy. And in addition by declaring scope as const, it’s really easy using IDE autocompletion.

Is there any way to do this in Yii2?

The only way I can think of is by creating a new class from ActiveQuery

something like




class BookActiveQuery extends \yii\db\ActiveQuery {


     public function filterByAuthorId($authorId);

}



and in the Book class, I can override the find method.

Is it really intended that way?

Never mind, this works.




class Book extends \yii\db\ActiveRecord {

    public static function(){

        return new BookActiveQuery(get_called_class());

    }

}






class BookActiveQuery extends \yii\db\ActiveQuery {

    public function filterAuthorId($authorId){

        $this->andWhere('authorId = :authorId', ['authorId' => $authorId]);

        return $this;

    }


    public function whereIsNotRemoved(){

        $this->andWhere('isRemoved = :notRemoved', ['notRemoved' => Book::STATUS_NOT_REMOVED]);

        return $this;

    }

}






$dataProvider = new ActiveDataProvider([

            'query' => Book::find()

                    ->whereIsNotRemoved()

                    ->filterAuthorId($authorId),

        ]);