1

Based on runtime set variables, I want a given factory to create an instance of a class. However, I find that the only way the auto-generated factories get the concrete class is via xml <preference>.

namespace Vendor\Module\Api;
interface CustomInterface {}

namespace Vendor\Module\Model;
class First implements CustomInterface {}

namespace Vendor\Module\Model;
class Second implements CustomInterface {}

namespace Vendor\Module\Model;
class Thing {

  public function __construct(
    \Vendor\Module\Api\CustomInterfaceFactory $customObjectFactory
  ){
    $this->customObjectFactory = $customObjectFactory;
  }

  public function getCustomObject($thing){
    if($thing){
      // should return First instance
      return $this->customObjectFactory->create(); 
    }else{
      // should return Secong instance
      return $this->customObjectFactory->create();
    }
  }
}

The above will fail, trying to instantiate interface CustomInterface.

Am I stuck creating identical interfaces (duplicates in content, but with different names) in order to get multiple (auto-generated) factories, that will return the specific class? I mean, calling a different factory based on the above if($thing){}

I want to keep using the ObjectManager DI chain, since the objects First and Second make use of DI.

Or is there a canonical way to build a CustomFactory class? Something like:

namespace Vendor\Module\Model;
class CustomObjectFactory {

  public function __construct(
    \Magento\Framework\ObjectManagerInterface $objectManager
  ){
    $this->objectManager = $objectManager;
  }

  public function create($className, array $data = [])
  {
    return $this->_objectManager->create($className, $data);
  }
}

What would be the Magento2 approach?

Thanks!

PS: I get the feeling this daisy chaining of DI will turn into a Mocking nightmare...

2 Answers 2

5

Better to incapsulate logic of object creation in custom factory:

namespace Vendor\Module\Model;
class CustomObjectFactory {

  public function __construct(
    \Magento\Framework\ObjectManagerInterface $objectManager
  ){
    $this->objectManager = $objectManager;
  }

  public function create(array $data = [])
  {
      if ($thing){
          $className = 'Class1';
      } else {
          $className = 'Class2';
      }

    return $this->objectManager->create($className, $data);
  }
}

but you factory is applicable too. Just add verification of interface.

namespace Vendor\Module\Model;
class CustomObjectFactory {
  public function __construct(
    \Magento\Framework\ObjectManagerInterface $objectManager
  ){
    $this->objectManager = $objectManager;
  }
  public function create($className, array $data = [])
  {
      $instance = $this->objectManager->create($className, $data);
      if (!($instance instanceof \Vendor\Module\SomeInterface)) {
            throw new \InvalidArgumentException(
                'Class must implement \Vendor\Module\SomeInterface'
            );
      }
   }
}
2

Agree with @KAndy

The only remark from my side - it's better to move logic

if($thing){
  // should return First instance
  return $this->customObjectFactory->create(); 
}else{
  // should return Secong instance
  return $this->customObjectFactory->create();
}

down to the factory, because it is factory's responsibility to decide which entity should be created based on passed parameters, doing so you will get your business logic clean and easy to read.

$this->customObjectFactory->create($thing);

Think, after that, method getCustomObject($thing) will look excessive, because it doesn't introduce any logic and you will eliminate one as well.

And Unit tests wouldn't be a nightmare as well, because you will have dedicated unit tests for Factory (check what type of entity created based on input data), and dedicated test for Business logic

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.