Magento2 – Check if Module Exists After Being Injected via Class Constructor

dependency-injectionmagento2moduleobject-manager

I know I can achieve this with object Manager – but just wanted to see if there is a better way. Perhaps this case is a good candidate for using the object Manager; however, I would like to do things properly so it'd be good to get some feedback from more experienced developers.

I want to inject an object via a class constructor – and then within the body of the constructor decide what type of object it is.

I will use the Module Manager to check if a module exists – if so then I'd like to use one of its classes. Otherwise, I'd like to use another class in core.

The reason it is possible is because both the classes (the one that might not be available and the one in the core) have the same class name and method that I need to use).

Obviously I can't inject the class that might not exist directly. Here is some code as an example:

/**
 * MyClass constructor.
 *
 * @param {unsure how to define it} $object
 * @param \Magento\Framework\Module\Manager $moduleManager
 */
public function __construct({unsure how to define it} $object,
                            \Magento\Framework\Module\Manager $moduleManager,
)
{
    $this->_moduleManager = $moduleManager;

    if ($this->_moduleManager->isEnabled('ThirdPartyExtension_PerhapsNotInstalled')) {
        $object =  $classThatMightNotExist;
    } else {
        $object = $coreClass;
    }

    $this->_object = $object;
}

Best Answer

First Approach:
The cleanest way is to not take decisions in the constructor.
Let the di.xml do that for you.
Let's say the core class you are using if a module is not present is Some\Core\ClassName.
And the class that should use this core class us My\Custom\ClassName.

And the class that might not exist is MyExtension\PerhapsNotInstalled\ClassThatMightNotExist

Make your class constructor like this:

public function __construct(
    \Some\Core\ClassName $object 
) {
    $this->_object = $object;
}

Now, in your module MyExtension_PerhapsNotInstalled di.xml file add this:

<type name="My\Custom\ClassName">
    <arguments>
        <argument name="object" xsi:type="object">MyExtension\PerhapsNotInstalled\ClassThatMightNotExist</argument>
    </arguments>
</type>

just make sure that MyExtension\PerhapsNotInstalled\ClassThatMightNotExist extends the core class Some\Core\ClassName.

This way, if the module MyExtension_PerhapsNotInstalled is not installed, the original core class Some\Core\ClassName will be used, otherwise MyExtension\PerhapsNotInstalled\ClassThatMightNotExist will be used.

Second Approach:
If your extension that might not exist, is a third party extension that you cannot control, and you want your code to work with it and without it, you can do this (using the same notations as above):

Create your own class that acts as a factory:
Let's call it My\Module\Factory

<?php

namespace My\Module;

class Factory
{
    protected $moduleManager;
    protected $objectManager;

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

    public function create(array $data = array())
    {
        if ($this->moduleManager->isEnabled('ThirdPartyExtension_PerhapsNotInstalled')) {
            $instanceName =  'MyExtension\PerhapsNotInstalled\ClassThatMightNotExist';
        } else {
            $instanceName = 'Some\Core\ClassName';
        }
        return $this->objectManager->create($instanceName, $data);
    }
}

You should inject this new class in the constructor of your My\Custom\ClassName class and call the create method from it to determine the class instance you need.

public function __construct(
    \My\Module\Factory $factory 
) {
    $this->_object = $factory->create();
}

Note: This second approach is not really clean, because in theory your dependencies should be explicit and the composition should be done via di.xml but I certainly understand the need.
Second Note: You are not violating any guidelines of not using the object manager, because OM is allowed in factories and you just created a factory.
And your class does not depend on the OM directly.
I didn't test the code, so watch out for syntax errors.