1

I've been doing a lot of reading on dependency injection over the last few days and have been using Aura.DI to have a play with IoC principles. I've come up against a few issues I've heard are common when implementing this pattern, but I'm still not quite sure on some of the details.

If I want to inject a logger (say Monolog) into a controller class (just an easy example), I would like to use the class name as the logging channel (basically new Logger('somecontroller')), as far as I can tell, this would mean manually defining that 'injection' for each and every class, am I right in thinking this?

This:

$controller = new PageController(new Logger('PageController'));

in Aura.DI, would be:

$di->params['PageController'] = [
    'logger' => $di->lazyNew('Logger', ['name' => 'PageController'])
];

but I'd have to do that for every controller that I want a different logging channel for?! I've heard that you can use the Abstract Factory Pattern here, but I'm not sure how this would work? (I understand the Factory pattern, but have never been in a situation where I thought it would help me. The Abstract Factory pattern is new to me though)

I know I could write a simple method to iterate over an array of controllers and do this in a less verbose way, but it does feel kind of wrong to have to do that.

I also came across this post on reddit and thought it made a bit of sense. If I'm going to have to manually configure all of these dependencies anyway, why not just make my configuration the actual implementation? Maybe this falls apart when the dependency tree gets a bit larger? I thought it was worth asking what peoples' opinions were though.

EDIT: Just mocked up an example of how the DIY approach could work and it seems pretty alright actually... thoughts? (probably should have made a lot of those methods private, but you get the picture).

Any light that you can shed on any/all of my questions would be appreciated.

jdoe1
  • 11
  • 2
  • did you try using Symfony [DI component](https://symfony.com/doc/current/components/dependency_injection.html)? – genesst Oct 10 '17 at 18:55
  • I've not specifically tried that implementation, I did briefly read through though. It looks similar (functionality wise) to the Aura.DI implementation. So the points above are still questions I'd have with either framework. – jdoe1 Oct 10 '17 at 18:58
  • 1
    what you create `AbstractController`, add `Logger` as a dependency and derive all other controllers from that abstract one? – genesst Oct 10 '17 at 19:00
  • I could do, but that way I'd get the same `Logger` instance each time. I want a new logger instance so that I can have different 'channels' (as they're called in Monolog) to separate out my logs. That's the crux of the problem I'm facing. – jdoe1 Oct 10 '17 at 19:07
  • 1
    what if you add `Logger` in `AbstractController` and then configure it in each derived Controller constructor? – genesst Oct 10 '17 at 19:08
  • Yeah, the thought had crossed my mind. The issue with that is that the `Logger` needs it's name as a constructor argument, then I'd have to inject a reference to the `Logger` class, so I can instantiate it in the `AbstractController`, or I inject a factory. Neither of those felt ideal, but I'm new with these patterns, so I'm not entirely sure what's appropriate... – jdoe1 Oct 10 '17 at 19:13
  • Are you looking for an `IoC` Or `DI`? This example seems to be an `IoC` but rather `DI`. `new Logger('PageController')`. – Basheer Kharoti Oct 12 '17 at 10:40

1 Answers1

0

You could do this via a few different ways.

  1. Inject a LoggerFactory class to the PageController.

    class PageController
    {
    
        public function __construct(LoggerFactory $loggerFactory)
        {
            $this->loggerFactory = $loggerFactory;
        }
    
        public function someAction()
        {
            $this->loggerFactory->newInstance(__CLASS__);
        }
    }
    
    class LoggerFactory
    {
        public function newInstance($name)
        {
            return new Logger($name);
        }
    }
    
  2. Using withName() of the same Logger. May be this is easier one. You inject the logger and call $newlogger = $this->logger->withName(__CLASS__); and get the cloned new instance.

  3. Using the Instance Factories

Here ExampleNeedsFactory is PageController, ExampleStruct is Logger

$di->params['PageController']['loggerFactory'] = $di->newFactory('Logger');

    class PageController
    {

        public function __construct(LoggerFactory $loggerFactory)
        {
            $this->loggerFactory = $loggerFactory;
        }

        public function someAction()
        {
            $logger = $this->getLogger();
        }

        public function getLogger()
        {
            $this->loggerFactory->__invoke(__CLASS__);
        }
    }

Note : In this case getLogger always returns new logger. If you need same instance set the logger.

public function getLogger()
{
    if (! $this->logger) {
        $this->logger = $this->loggerFactory->__invoke(__CLASS__);
    }

    return $this->logger;
}

You can also move these methods to an AbstractController class so you don't need to write everytime the what you need to inject to the controller. (@camel_case did mentioned about this in his comments)

There is also auto resolution feature for Aura.Di which helps not to write lots of configuration.

Hope that helps!

Hari K T
  • 4,174
  • 3
  • 32
  • 51
  • Thanks for your answer Hari. I have up-voted it, but my low rep is getting in the way of it showing up properly. the `withName` method looks like a good way to go for that specific example, thanks for pointing it out. I have used the auto-wiring feature within Aura.DI and it is good, it's just when you run into issues like this that it doesn't help much more than the DIY approach I wrote about. If you could, would you be able to share your opinion on the main advantages of using a DI framework over the DIY version I showed with advantages or disadvantages for either approach? Thanks – jdoe1 Oct 12 '17 at 18:15