Yii2: Some problem with activeRecord relation and indexBy

Created on 21 Apr 2014  路  13Comments  路  Source: yiisoft/yii2

I have some model with relation.
For example:

class Order extends \yii\db\ActiveRecord
{
    public function getOrderItems()
    {
        return $this->hasMany(OrderItem::className(), ['order_id' => 'id'])->indexBy('item_id');
    }
}

if use:

$model = new Order();
$model->orderItems;

work fine.

But if try use:

$models = Order::find()->with('orderItems')->all();

foreach ($models as $model)
    $model->orderItems;

Where many orders have the same item, only first order have full item list.

important bug

All 13 comments

Looked ActiveRelationTrait method populateRelation, line 230:
https://github.com/yiisoft/yii2/blob/76d8e2b5b3e921ef4380731ebb89f74bae4ad47e/framework/db/ActiveRelationTrait.php#L230

$models = $this->all();

if use indexBy some rows lost, for:

$models = Order::find()->with('orderItems')->all();

foreach ($models as $model)
    $model->orderItems;

This is expected. If you index by a column that is not unique you limit the result to only one item per distinct value. What else did you expect?

I expect the same behavior in both cases.

Write:

    public function getOrderItems()
    {
        return $this->hasMany(OrderItem::className(), ['order_id' => 'id'])->indexBy('item_id');
    }

I want to get a list of items by index regardless how they are selected.

I would not have asked the question, if indexBy was not used in Active Relation, current indexBy support is there, but it turns out depends on the case.

https://github.com/yiisoft/yii2/blob/76d8e2b5b3e921ef4380731ebb89f74bae4ad47e/framework/db/ActiveRelationTrait.php#L353

I still think it mistake.

Will check if we can adjust implementation somehow. However I do not think it is a case we should support if it is not easy.
Can you explain why you want to index by a non-unique column?

Thank you.
I have 2 models like as Order and OrderItem, where [order_id and item_id] - a unique key for OrderItem. The logic of the application requires processing the group items , it would be desirable for this to related models were with indexBy.

It should be noted that it is possible to do without it, but I think this problem can be corrected.

I note that the query is executed correctly and all the data is returned.
If corrected code in the 230 line, save and zeroing indexBy and corrected code buildBuckets, we get exactly what we need.

Sorry for my english ((

I am unsure about this. Of course, we can adjust the current code: we can check if 'indexBy' value key already present in the result set and convert it to array. But the result of such code will be a mess.
For example: assume we have an items records, like:

id = 1, group_id = 1,
id = 2, group_id = 2,
id = 3, group_id = 3,

If I use indexBy('group_id') in case 'group_id' is unique I get following result:

[
    1 => ['id' => 1, 'group_id' => 1],
    2 => ['id' => 2, 'group_id' => 2],
    3 => ['id' => 3, 'group_id' => 3],
]

But if 'group_id' is not unique:

id = 1, group_id = 1,
id = 2, group_id = 2,
id = 3, group_id = 2,

I will get following:

[
    1 => ['id' => 1, 'group_id' => 1],
    2 => [
        ['id' => 2, 'group_id' => 2],
        ['id' => 3, 'group_id' => 2],
    ]
]

Processing such result will be a real headache: both $result[1] and $result[2] are arrays, but $result[1] contains a row data, while $result[2] contains llist of row data.
This would not be such a problem in AR scope, where row is an object of course, but still.

Possible solution would be separated indexBy and indexByUnique options.

The issue is valid. It's not trivial to fix though.

One more thing: if we allow groupping for indexBy as this issue suggest. The next requirement, which will appear is a nested indexBy support. For example: developer may want to index rows at first level by 'group_id' and in sub-set by 'id':

$rows = Item::find()->indexBy(['group_id', 'id'])->all();

output:

[
    1 => [
        1 => ['id' => 1, 'group_id' => 1],
    ],
    2 => [
        2 => ['id' => 2, 'group_id' => 2],
        3 => ['id' => 3, 'group_id' => 2],
    ]
]
$rows = Item::find()->indexBy(['group_id', 'id'])->all();

It's not quite what I meant.
The current implementation indexBy completely satisfied.
This question is probably more to the relation.
I think that it needs to teach buildBuckets receive value by indexBy

:+1:

One more thing: if we allow groupping for indexBy as this issue suggest. The next requirement, which will appear is a nested indexBy support. For example: developer may want to index rows at first level by 'group_id' and in sub-set by 'id':

$rows = Item::find()->indexBy(['group_id', 'id'])->all();

output:

[
    1 => [
        1 => ['id' => 1, 'group_id' => 1],
    ],
    2 => [
        2 => ['id' => 2, 'group_id' => 2],
        3 => ['id' => 3, 'group_id' => 2],
    ]
]

in fact I'm looking for a function exactly like that in yii, if someone knows this, i will thank you

Was this page helpful?
0 / 5 - 0 ratings