yii2
yii2 copied to clipboard
Error in yii\db\ActiveQuery::populateInverseRelation() when primary model is an object and relation is an array
When primary model has a relation that uses inverseOf
, "finding" primary model as an object with relation as array produces PHP error. All other combinations of object/array for primary model and relation work fine.
What steps will reproduce the problem?
class Customer extends \yii\db\ActiveRecord
{
public function getOrders()
{
return $this->hasMany(Order::class, ['customer_id' => 'id'])->inverseOf('customer');
}
}
class Order extends \yii\db\ActiveRecord
{
public function getCustomer()
{
return $this->hasOne(Customer::class, ['id' => 'customer_id']);
}
}
$c = Customer::find()->where(['id' => 1])->asArray(false)->with(['orders' => function ($query) { $query->asArray(false); }])->one(); // object->object: OK
$c = Customer::find()->where(['id' => 1])->asArray(true)->with(['orders' => function ($query) { $query->asArray(true); }])->one(); // array->array: OK
$c = Customer::find()->where(['id' => 1])->asArray(true)->with(['orders' => function ($query) { $query->asArray(false); }])->one(); // array->object: OK
$c = Customer::find()->where(['id' => 1])->asArray(false)->with(['orders' => function ($query) { $query->asArray(true); }])->one(); // object->array: ERROR
What is the expected result?
The last line executes without errors.
What do you get instead?
PHP error: "Indirect modification of overloaded element of Customer has no effect" in yii\db\ActiveQuery::populateInverseRelation()
.
Additional info
Q | A |
---|---|
Yii version | 2.0.48.1 |
PHP version | 8.2.8 |
The problem arises in yii\db\ActiveQuery::populateInverseRelation()
:
$primaryModels[$i][$primaryName][$j][$name] = $primaryModel;
If translated to the example above:
$customers[$i]['orders'][$j]['customer'] = $customer;
$customers[$i]['orders']
calls class method returning a simple PHP array (no objects). Modifying that array will create a copy of an array and have no effect on the value stored in $customers[$i]
object. This is what PHP complains about.
The easy solution would have been:
// Access array directly (not through some hidden class method):
$primaryModels[$i][_related'][$primaryName][$j][$name] = $primaryModel;
// $customers[$i][_related']['orders'][$j]['customer'] = $customer;
But that solution won't work because ActiveRecord::_related
is a private property.
The bad solution with lots of array copying would be something like:
if($primaryModels[$i] instanceof ActiveRecordInterface) {
$relatedModels = $primaryModels[$i][$primaryName];
$relatedModels[$j][$name] = $primaryModel;
$primaryModels[$i]->populateRelation($primaryName, $relatedModels);
}
The better solution might be to fill in inverseOf
related data BEFORE calling populateRelation()
on primary models in ActiveRelationTrait::populateRelation()
.
Or change _related
property scope from private to protected.
Or change
_related
property scope from private to protected.
Won't help because this property needs to be accessed in a different class (_related
is a property of \yii\db\BaseActiveRecord
and access needs to be in yii\db\ActiveQuery::populateInverseRelation()
.