2

I am learning Yii2. Here is one situation I have not googled the answer.

I register a component called scraper in config/console.php's $config['components'] array,

this scraper class has a public property $_client which is a Goutte\Client class.

I tried to use the following way to set up scraper component, but it is not working, Yii2 did not instantiate $_client as a Goutte\Client object.

$config = [
   'scraper' => [
        'class' => 'app\models\Scraper',
        '_pageSize' => 10,
        '_client' =>  [  //not working. can not instantiate this property as an object
            'class' => 'Goutte\Client'
        ],
   ],
   //... 
]

Question: What would be working way to inject the dependency in the configuration?

robsch
  • 9,358
  • 9
  • 63
  • 104
Jinsong Li
  • 6,347
  • 1
  • 23
  • 20
  • Is Scraper derived from Model? Is `_client` a public member? Goutte\Client is the fully qualified class name (not app\goutte\Client)? And Goutte\Client constructor has no parameters? – robsch Nov 22 '15 at 12:25
  • Scraper is derived from `Component`, `$_client` is a public member. When I init $_client in `Scraper` class's init() function, while all other public primitive type properties are set in this config file, it works fine. If I `var_dump($this->_client)` in Scraper function, it shows `$this-_client` is an array of array(1) { ["class"]=> string(13) "Goutte\Client" }. – Jinsong Li Nov 22 '15 at 12:59
  • `Goutte\Client` is the full qualified class name, it needs no parameters. I can import this class by `use Goutte\Client` in Scraper class. – Jinsong Li Nov 22 '15 at 13:02
  • Could you provide more code (classes, namespaces, namespace usages, configuration,...), please? And in $config I don't see the `components` key.I'm not so experienced with configuration and so not sure if I can help you at all. – robsch Nov 22 '15 at 22:13
  • @robsch, yes I skipped the `components` part for this code snippet, the reason is that, every other properties are instantiated fine, except this `$_client` property, so I just want to highlight this part. And thank you for trying to help me out :) – Jinsong Li Nov 23 '15 at 17:12

2 Answers2

2

Yii2 will not instantiate objects beyond the first level in your config array. In other words, scraper will get instantiated as an object, but it's property _client will be instantiated as an array ['class' => 'Goutte\Client'].

You should implement this logic yourself:

class Service extends Component
{
    private $_client = null;

    public $clientClass;

    public function getClient()
    {
        if (null !== $this->_client) {
            return $this->_client;
        }

        $this->_client = new $clientClass;

        return $this->_client;
    }
}

Alternatively, you can register Goutte\Client as a separate component, then Yii will properly instantiate it.

UPDATE: To clarify, instantiating objects from config is done with yii\base\Configurable interface which is implemented in yii\base\Object class. Eventually, this implementation executes Yii::configure:

public static function configure($object, $properties)
{
    foreach ($properties as $name => $value) {
        $object->$name = $value;
    }

    return $object;
}

As you see, all properties will be assigned their respective values, so _client will become an array, not an object.

Beowulfenator
  • 2,262
  • 16
  • 26
0

Found another approach in the guide itself: The property targets of the class yii\log\Dispatcher can be initialized with a class names or an objects. To make it working as one expects the init method is overwritten:

/**
 * {@inheritdoc}
 */
public function init()
{
    parent::init();

    foreach ($this->targets as $name => $target) {
        if (!$target instanceof Target) {
            $this->targets[$name] = Yii::createObject($target);
        }
    }
}

This allows configuration/initialization of the log component like this:

'log' => [
    'class' => 'yii\log\Dispatcher',
    'targets' => [
        [
            'class' => 'yii\log\FileTarget',
        ],
    ],
],

Note: targets is an array here. But it can be done with a single class/object as well.

So in your case this should be a solution:

namespace app\models;

class Scraper extends ActiveRecord // or extends from anything that actually implements yii\base\Configurable
{
    public $_client;

    /**
     * {@inheritdoc}
     */
    public function init()
    {
        parent::init();

        if (!$this->_client instanceof Goutte\Client) {
            $this->_client = Yii::createObject($this->_client);
        }
    }
}

btw: usually underscore prefix in variable names is used for private properties.

robsch
  • 9,358
  • 9
  • 63
  • 104