Magento 2 – Best Practices for Dynamically Instantiating Classes with Arguments

best practiceclassmagento2object-manager

I am actually trying to instanciate a class dynamically with these arguments.
Here is the code I use in class A:

/** @var string $source */
$source = '\Foo\Bar\My\Class'; // Dynamic value that I am not aware because it is from other extensions

/** @var bool $isClassLoadable */
$isClassLoadable = $this->definedClasses->isClassLoadable($source);
if (!$isClassLoadable) {
    return false;
}

/** @var Model $model */
$model = $this->getModel(); // loaded model with data

/** @var \Magento\Framework\Simplexml\Config $config */
$config = $this->getConfig(); // loaded configuration with data

/** @var ProcessorInterface $processorInstance */
$processorInstance = $this->objectManager->create(
    $source,
    [
        $model,
        $config,
    ]
);

The \Foo\Bar\My\Class class code:

public function __construct(
    Model $model,
    Config $config
) {
    $this->model = $model;
    $this->config = $config;

    \Zend_Debug::dump($this->model->debug());
    \Zend_Debug::dump($this->config);
}

Here my two parameters are an empty model and an empty configuration.

Questions
– Is it a good practice to use ObjectManager to create class from a "string" ?
– If it is a "good practice" what is the way to keep my data between my class A and the \Foo\Bar\My\Class instanciation ?


EDIT
I am aware that using ObjectManager is a bad practice and Factory are here to avoid that but the $source is from a configuration and maybe third part extensions, I don't know his value. I hardcoded it for the purpose of my example.

Best Answer

No, it's not a good practice to use the object manager.
Use a factory instead.
To get an instance of \Foo\Bar\My\Class you will need to use an instance of \Foo\Bar\My\ClassFactory that will be automatically generated.

I assume that the code you listed in the question is part of a class.
you should have in your class this:

protected $fooBarFactory;
public function __construct(
    ....
    \Foo\Bar\My\ClassFactory $fooBarFactory,
    ....
) {
   .... 
   $this->fooBarFactory = $fooBarFactory;
    ....
}

Then you can instantiate your \Foo\Bar\My\Class like this:

$processorInstance = $this->fooBarFactory->create([
    'model' => $model,
    'config' => $config,
]);

The createmethod takes as parameter an array, and the keys in this array must match the constructor argument names in the class you are instantiating.
Your class accepts 2 parameters in the constructor called $model and $config hence the array keys model and config.
[EDIT]
In case your class acts as a factory you can use ObjectManager.
I think you should move the code that actually instantiates $source in a separate factory (that's not autogenerated) and you are allowed there to use the OM.
And you can inject that general factory in your class and have something like this

$processorInstance = $this->customProcessorFactory->create(
    $source, 
    [
        'model' => $model,
        'config' => $config,
    ]
);
Related Topic