Search code examples
phpdependency-injectionauraphp

AuraPHP DI dynamic class or decision based injection


I'm new to modern method of dependency injection and I'm trying to figure out how to have a method pick which class to use based on a condition. I bet I've got my design structure off but I'm also not seeing how to do this in Aura DI through the config.

This is my Aura Config

<?php
namespace Aura\Cli_Project\_Config;

use Aura\Di\Config;
use Aura\Di\Container;

class Common extends Config {
    public function define(Container $di) {
        // utilities
        $di->set(
            'App\Inventory\Utilities\EmailParser',
            $di->newInstance('App\Inventory\Utilities\PlancakeParser')
        );

        // commands
        $di->params['App\Inventory\Command\IncomingOrder'] = array(
            'stdio' => $di->lazyGet('aura/cli-kernel:stdio'),
            'parser' => $di->get('App\Inventory\Utilities\EmailParser')
        );
    }
    // ...
}

And this is the class in question that needs to use different classes depending on the "source" it finds.

<?php

namespace App\Inventory\Command;

use Aura\Cli\Stdio;
use App\Inventory\Utilities\EmailParser;
use App\Inventory\Sources\Etsy;
use App\Inventory\Sources\Amazon;
use App\Inventory\Sources\Ebay;

class IncomingOrder {

    public function __construct(
        Stdio $stdio,
        EmailParser $parser) {
        $this->stdio = $stdio;
        $this->parser = $parser;
    }

    public function process() {
        // other code to parse message
        // source is set by determining where it came from
        $source = 'Etsy';

        switch($source) {
            case 'Etsy' :
                // This bit seems really wrong
                $sourceParser = new Etsy\OrderParser();
                break;
            case 'Amazon' :
                $sourceParser = new Amazon\OrderParser();
                break;
            case 'Ebay' :
                $sourceParser = new Ebay\OrderParser();
                break;
            default :
                $sourceParser = null;
        }

        // Do source specific processing
    }
}

Is it that I need to split my processing at the point right after the source is determined so a new class can be initialized with that source as a parameter?

The only way I can see to do this in the config is to do a lazy anonymous function to return the proper source class but this also feels against modern design principles.


Solution

  • Something I would like to clarify is you don't need to use set method like many of the di containers out here. You can modify the code to

    <?php
    namespace Aura\Cli_Project\_Config;
    
    use Aura\Di\Config;
    use Aura\Di\Container;
    
    class Common extends Config 
    {
        public function define(Container $di) 
        {
            // commands
            $di->params['App\Inventory\Command\IncomingOrder'] = array(
                'stdio' => $di->lazyGet('aura/cli-kernel:stdio'),
                'parser' => $di->lazyNew('App\Inventory\Utilities\EmailParser')
            );
        }
        // ...
    }
    

    You can make use of set when you want to pass the same object to many other objects. Don't use newInstance for it will create the object on calling the same. You probably may need to make use of lazyNew or lazyGet functionalities.

    Regarding your question about dynamic decision making. Here is my thoughts, I did have came across this question some time before. But didn't see what I did so iirc what I did was injected a factory to IncomingOrder class which can create object. The good thing about it is if your source parse needs some sort of dependency you can make use of di inside the factory.

    Eg :

    <?php
    namespace Something;
    
    use Aura\Di\Container;
    
    class SourceFactory
    {
        public function __construct(Container $di) 
        {   
            $this->di = $di;
        }
    
        public function newInstance($source)
        {
            if ($di->has($source)) {
                return $di->get($source);
            }
            // or alternatively create with new as done in switch
        }
    }
    

    Hope that helps.

    Thank you