ActiveDataProvider Help

So, once again Ive burnt an afternoon away trying to figure out how to perform a simple operation in Yii2. Again, with ActiveDataProvider. Trying to "guess" the proper syntax/idiom usually results in failure (and I even used Yii 1.x for 3 years). Then I turn to the guides.

Unfortunately, like many critical guides Id need to finish my test application in Yii2 that I started last July, the ActiveDataProvider guide still states this at the top:

Not to mention the "TBD" that has been there for "Implementing Your Own Custom DataProvider" since the guide was launched.

I find this quite disconcerting since Ive been struggling with many similar issues since beta, and its months after the Yii2 launch, and the guides are still incomplete. So then I turn to the forums, and burn a few more hours scouring through seemingly related posts that dont reveal much to me, or asking my own question wich either gets ignored, or someone copy&pastes the section of the guide that doesnt cover the case Im asking about.

So, Im trying to make an ActiveDataProvider that loads all records of a ModelA that are in its published() scope through a join table (from ModelB), using the ID of relational ModelC.

In SQL, it would look like this:




// please disregard efficiency, bind params, etc - just focusing on the fact that there are relational tables

SELECT * FROM table_a

    JOIN table_b ON table_a.id = table_b.a_id

    JOIN table_c ON table_c.id = table_b.c_id

    WHERE table_c.id = {$some_ID}

        AND table_a.status = 1

        AND table_a.publish_datetime <= NOW()

    ORDER BY table_a.publish_datetime DESC

    LIMIT 0,20

;

For ModelA, I have the appropriate relational definitions for getModelBs as a hasMany(), and getModelCs as a hasMany() with a via(‘modelBs’) exactly as is stated in the guides. The inverses are defined for ModelC. However, Im not sure if these even play a role in an ActiveDataProvider. Im sure I could utilize these callbacks, but Im not even sure if that follows the expected idioms of Yii2.

Ive tried a number of ways with the various Query / ActiveQuery / ActiveRecord callbacks for producing query clauses, and they all feel awkward and cumbersome. Most importantly, it feels very non intuitive.

Ive even tried to use the closures within a with() function, and I must say, that feels most strange of all. I also have no idea how to properly address particular tables using this technique, dynamically. For example:




// within MyActiveDataProvider which extends ActiveDataProvider, overriding public function init()

public function init()

{

    parent::init();

    

    $this->pagination->defaultPageSize = 10;

    $this->pagination->pageSizeParam   = false;


    $this->query = ModelA::find()->published()->with(

        [

            'modelCs' => function($query) {

                $query->andWhere('table_b.id' => $this->idVar);

            }

        ]

    );

}

Is something along these lines expected? :


$this->query = ModelC::getModelAs()->via('ModelB')->where('table_b.c_id = ?', $this->c_id);

Would I need to write a callback defined in ModelC, which accepts an ID as an argument, then forms an ActiveQuery which uses this ID argument to select all of the ModelA records through ModelB, and finally set $this->query to this new callback within my DataProvider?




$this->query = ModelC::getAllModelARecordsByID($c_id);



How should I arrange this query to perform the basic JOIN through a relational Model/table, in the expected Yii2 idiom?

Im about to abandon it and just use ModelA::findBySQL(), which I feel abandons whatever idiom is expected here, which then makes me wonder why Im using this framework at all. I am a bit frustrated as Ive spent a cumulative total of several days worth of work banging my head against figuring out Yii2s idiomatic ways, and feeling like its easier to just forgo whatever the framework wants me to do and just write some procedural code (yuk, but hey gets the work done).

Guides and robust examples FTW?

Hi,

If I got your point, you are trying get a model C, using a junction model B but conditioning the query using some attribute of model A. If i am right (and I WILL use guide example), you should read this: http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#joining-with-relations.

Look this example (it seems exactly what you want):




// join with multiple relations

// find the orders that contain books and were placed by customers who registered within the past 24 hours

$orders = Order::find()->innerJoinWith([

    'books',

    'customer' => function ($query) {

        $query->where('customer.created_at > ' . (time() - 24 * 3600));

    }

])->all();

// join with sub-relations: join with books and books' authors

$orders = Order::find()->joinWith('books.author')->all();

Hope it help you.

Sidney Lins

Thanks @sidtj, but like many examples in the guides, its similar to what I want, but not exactly what I want. Also I am trying to assign the ActiveQuery instance to the ActiveDataProvider::$query property, and it seems to throw errors when you use that closure style in an ActiveDataProvider. Your example would likely work if just written inline in my controller action, but Im trying to keep a consistent pattern of using an extension of ActiveDataProvider in this case.

I did, however, scour through more guides, and revisited the ActiveRecord guide, and remembered the innerJoinWith() function (as youve used in your example).

I have something working, though I dont have anymore time today to test it for efficiency (I suspect the query isnt optimized). So it seems like this is what I want:


$this->query = ModelA::find()->published()->innerJoinWith('modelCs')->where(

    ModelC::tableName() . '.id = :c_id',

    ['c_id' => $this->c_id]

);

I also feel thats kind of clunky, but I wanted to make it as dynamic as possible. Im also not sure if im breaking away from expected Yii2 idioms, or if this the ‘default’ way. If I have time this week, I may examine the resulting query, and if its suboptimal, Im just going to use findBySQL() and type the query in literal fashion, and pretend today never happend :lol: .