Yii2 Dependency Injection With ActiveRecord?

It’s unclear to me how to inject dependencies in ActiveRecord.

\common\models\Page




class Page extends ActiveRecord

{

...

 public function __construct(Translator $translator, User $user,  Tenant $tenant, array $config = [])

    {

        $this->translator           = $translator;

        $this->user                 = $user;

        $this->tenant               = $tenant;

        $this->language             = $user->getIdentity()->language;        

        parent::__construct($config);

    }



[color="#2E8B57"]This work[/color] \MyController




$page              = \Yii::createObject('common\models\Page');

$page->attributes  = \Yii::$app->request->post();

$page->save();



[color="#FF0000"]This DOES NOT work [/color] \MyController




$page              = \Yii::createObject('common\models\Page');

$page->findOne(\Yii::$app->request->get('id'));

$page->attributes  = \Yii::$app->request->post();

$page->save();



[color="#FF0000"]"Argument 1 passed to common\\models\\Page::__construct() must be an instance of common\\components\\Translator, none given, called in /var/www/html/gtbcloud/vendor/yiisoft/yii2/db/BaseActiveRecord.php on line 1163"[/color]

Question:

[size="5"][color="#0000FF"]How do I automaticaly inject my dependencies to my constructor using DI???[/color][/size]

Thank you for your time

In your model you need to override instantiate() method and call \Yii::createObject() there.

1 Like

Awesome it works.

\common\models\Page




public static function instantiate($row)

{

    return \Yii::createObject('common\models\Page');

}



[i]

common\modules\api2\controllers\PageController[/i]




public function actionUpdate()

{

    $page              = Page::findOne(\Yii::$app->request->get('id'));

    $page->attributes  = \Yii::$app->request->post();

    $page->save();

    return $page->getResponse();

}



I have a question…

In my rest controller, I can now just delete my custom actionUpdate() and the yii\rest\ActiveController will [color="#FF8C00"]handle the Update[/color].

[color="#FF0000"]But not the Create.[/color]

I can see the yii\rest\CreateAction creates a new model like this:




/* @var $model \yii\db\ActiveRecord */

$model = new $this->modelClass([

    'scenario' => $this->scenario,

]);



Using ‘[color="#000080"]new[/color]’…

[size="5"][color="#0000FF"]Is there another way for yii\rest\CreateAction to use instantiate()?[/color][/size]

Thank you for your time :)

I guess by defining your own controller extending from default one.

Right, I actually overridden

yii\rest\CreateAction and

yii\rest\UpdateAction

using \Yii::createObject()

But I fall again in the "[color="#000080"]new[/color]" trap. ActiveDataProvider use "[color="#000080"]new[/color]" in setSort()

yii\data\ActiveDataProvider




...

public function setSort($value)

    {

        parent::setSort($value);

        if (($sort = $this->getSort()) !== false && $this->query instanceof ActiveQueryInterface) {

            /* @var $model Model */

            $model = new $this->query->modelClass; //<--- here

...



It makes me rethink my entire approach. Why Yii2 use "[color="#000080"]new[/color]" everywhere while pushing their "Dependency Injection Container" feature that is not compatible with "new"?

That means the only way I can interact with my model is through my own custom code … I can’t even use a basic feature like “ActiveDataProvider”.

[size="5"][color="#0000FF"]How can I setup my model to use DI and still be compatible with "new"??

Is there something I’m missing? …I just don’t get it :huh: [/color][/size]

It was made for performance reasons initially. The case with ActiveDataProvider is wrong and should be fixed:

I have make the change in my code. Now it works.

\app\base\ActiveDataProvider




...

    public function setSort($value)

    {

        parent::setSort($value);

        if (($sort = $this->getSort()) !== false && $this->query instanceof ActiveQueryInterface) {

            /* @var $model Model */

            $model = \Yii::createObject($this->query->modelClass);

...



Btw, for those searching how to configure their ActiveRecord dependencies.

On a simple REST list action … I’ve passed from 9 seconds to 322ms doing this simple tweak.




<?php

namespace app\filters;


use yii\base\ActionFilter;

use Yii;


class ContainerDefinitionActionFilter extends ActionFilter

{

    /**

     * Set all dependencies for every models

     * @param \yii\base\Action $action

     * @return bool

     */

    public function beforeAction($action)

    {

        $ActiveRecordConfig =  [

            'translator'=> \yii::$app->t,

            'user'      => \yii::$app->user,

            'lang'      => \yii::$app->user->getIdentity()->language,

            'tenant'    => \yii::$app->tenant,

        ];


Yii::$container->set('common\models\MyModel1'     , $ActiveRecordConfig);

Yii::$container->set('common\models\MyModel2'     , $ActiveRecordConfig);

Yii::$container->set('common\models\MyModel3'     , $ActiveRecordConfig);

Yii::$container->set('common\models\MyModel4'     , $ActiveRecordConfig);


        return parent::beforeAction($action);

    }

}



Then add this behavior to your controller. This way your constructor won’t have to instantiate everything all the time…