Yii2: joinWith() fails with relations provided by behaviors attached via DI configuration

Created on 15 Mar 2017  Â·  11Comments  Â·  Source: yiisoft/yii2

What steps will reproduce the problem?

  1. Create \yii\db\ActiveRecord, eg. Post, and override instantiate():
public static function instantiate($row) {
    return Yii::$container->get(static::class);
}
  1. Create \yii\base\Behavior, eg. PostBehavior, which contains a new user relation for Post:
public function getUser() {
    return $this->owner->hasOne(User::class, ['id' => 'user_id']);
}
  1. Attach PostBehavior to Post using DI container definitions, eg.:
'container' => [
    'definitions' => [
        'common\models\Post' => [
            'as myBehavior' => 'common\behaviors\PostBehavior',
        ],
    ],
],
  1. Execute Post::find()->joinWith('user')->all();

What is the expected result?

Everything works fine if PostBehavior is added directly inside Post::behaviors(), but fails when included through application's DI container definitions.

What do you get instead?

Invalid Parameter – yii\base\InvalidParamException
common\modelsPost has no relation named "user".

Additional info

The problem appears to lie in \yii\db\ActiveQuery::buildJoinWith(), where our ActiveRecord is being instantiated using new $this->modelClass rather than Yii::createObject($this->modelClass) thus causing the relations defined via DI-provided behaviors to be disregarded.

| Q | A
| ---------------- | ---
| Yii version | 2.0.11
| PHP version | 5.6
| Operating system | Ubuntu

db to be verified bug

All 11 comments

Looks valid at the first sight. Would you like to fix it with a pull request containing the fix and a unit test? If yes then it has a chance to be merged into 2.0.12.

need change AR::instantiate too

@samdark I'm working on the PR + unit tests as you requested.
@lynicidn Generally you should, yes, but it didn't seem relevant to this specific case so I omitted it. I'll edit it back just to be sure.

try
$post = Post::findOne();
$user = new User();
$post->link('user', $user);
you also get exception that user relation not found

Read my post again.

if you want change logic for create models - it need do for all cases

best way:

Event::on(Post::class, 'init', function(ModelEvent $e) {
$e->sender->attachBehavior($myBehavior); 
});

this global work for all cases and don't break active record design

@lynicidn This issue is about attaching behaviors via configuration, which is an officially supported feature in Yii2, so one would expect it to work consistently. The issue highlights a case in which the official feature breaks.

That said, your workaround sounds like it could work. By the way, where would you put your code to ensure it is always run before any controllers/components etc? bootstrap?

@lynicidn That does not answer my question at all.

Was this page helpful?
0 / 5 - 0 ratings