Use of "scopes" in with() function between related models

Boy I seriously miss scopes from Yii 1.x right about now! I have "… scope" in quotations since Yii2 doesnt really have scopes, although its a concept we all discuss.

I currently have two models, PromotedPost and Post. Each PromotedPost hasOne Post. Both classes have their equivalent ModelQuery class defined, with some "scopes" for each.

I want to create a “published scope” for PromotedPost, and it has to include the criteria for the Post’s “published scope.” I dont want to repeat code, and I dont want the SQL for determining that a Post is published to exist in more than one place. I want to be able to apply the Post::find()->published() to the criteria that I am creating for PromotedPost::find()->published().

How can I call my Post model’s “published scope” while creating the criteria for my PromotedPost’s “published scope?” How do I define PromotedPostQuery::published() such that PostQuery::published() is added to it?

Post already has a "published scope" defined in its PostQuery model:




//Post::find()->published()

class PostQuery extends ActiveQuery

{

    /**

     * Base criteria for a Post to appear in user facing content.

     * @return self

     */

    public function published()

    {

        $this->andWhere(

            'published_at < ' . time() .

            ' AND status = '. Post::STATUS_PUBLISHED .

            ' AND deleted_at = 0'

        );

        return $this;

    }


    ...

}



I am beginning my PromotedPostQuery class, and have its published() scope defined:




//PromotedPost::find()->published()

class PromotedPostQuery extends ActiveQuery

{

    public function published()

    {

        $this->andWhere(

            'published_at < ' . time() .

            ' AND status = '. PromotedPost::STATUS_PUBLISHED .

            ' AND deleted_at = 0'

        );

        // HERE I WANT TO ADD SOME KIND OF ->with('post') SIGNIFYING ITS published() SCOPE


        return $this;

    }


    ...

}



Im aware that I could just add join() / innerJoin() function calls to PromotedPostQuery, and just type out all the same SQL, but that defeats the purpose of using an ActiveRecord style framework, and forces me to maintain conditions in multiple places.

You can create BaseQuery, define a method with some common code there and use it in PromotedPostQuery and PostQuery that are extended from BaseQuery.

Thanks for the reply, but I dont think that would work.

My example might be too simple. I think you jumped to the conclusion of abstraction as an alternative solution, since my two models are using properties which happen to be named the same in their conditions for being "published". What if none of the properties were named the same? What if they also had very different criteria for determining that they are "published" scope? I dont think that there would be any common code.

In any event, I still dont think that would solve the problem. Im trying to use this “ModelQuery” approach to get the Model’s relational instance to also include its “published” criteria, from its own ModelQuery class. Simply inheriting some “common code” doesnt clarify to me how this would be accomplished. If youre sure it does, could you provide an example? Im still finding much of the Yii2 idioms to be unwieldy and unintuitive.

I have a feeling like this isnt solveable with the current ModelQuery pattern, and that Ill have to write some JOIN SQL that effectively repeats the same criteria I have laid out in PostQuery::published().

Does anyone else feel that just having scopes would make situations like this far simpler?

In this case you can code to interface introducing abstract method in the base class:




abstract class BaseQuery extends \yii\db\ActiveQuery

{

    abstract public function published();


    public function latestPublished()

    {

        $this->published();

        $this->andOrderBy('id DESC');

    }

}


class PostQuery extends BaseQuery

{

    public function published()

    {

        $this->andWhere('status', 10);

    }

}


class PromotedPostQuery extends BaseQuery

{

    public function published()

    {

        $this->andWhere('is_published', true);

    }

}



1 Like

Thanks again, could you explain to me specifically where the above code demonstrates that I could call PromotedPost::find->published(), also including the relational Post instances based on PostQuery’s published() criteria?

i.e.,




class PromotedPostQuery extends BaseQuery

{

    public function published()

    {

        $this->andWhere(

            'published_at < ' . time() .

            ' AND status = '. PromotedPost::STATUS_PUBLISHED .

            ' AND deleted_at = 0'

        )->with('post')->scope('published');

        

        return $this;

    }


}

How do I express the “->scope(‘published’);” call in Yii2? How can I include Post::find()->published() in the criteria for PromotedPostQuery::published() ?




PromotedPost::find()

  ->published()

  ->with(['post' => function (PostQuery $query) { $query->published(); }])

  ->all();



1 Like

Thanks, this looks like what Im trying to accomplish.