7

The definition of DI quoted from Wikipedia states:

A. High-level modules should not depend on low-level modules. Both should depend on abstractions. B. Abstractions should not depend upon details. Details should depend upon abstractions.

I'm trying to apply that principle to my code:

class Printer{
        private $logger;    
        function __construct(Zend_Log $logger){
            $this->logger=$logger;
        }    
       function print(){
          //Some code
          $this->logger->log('Logger in action ;)');
       }        
    }

Now since why Printer class depends on Zend_Log which is neither an abstract class nor an interface then I'm violating the Dependency Inversion principle.

How can I fix that knowing that Zend_Log doesn't extend an abstract class nor implement an interface?

Gordon
  • 312,688
  • 75
  • 539
  • 559
Songo
  • 5,618
  • 8
  • 58
  • 96

2 Answers2

5

The easiest approach would be to use an Interface Adapter, e.g. create the API your Printer is supposed to use in an Interface and then implement that Interface in an Adapter for the Zend_Log component. Pass the concrete Adapter to the Printer. The Printer will then depend on the PrinterLog and not the concrete Zend_Log.

interface PrinterLog
{
    public function log($whatever);
}

class ZendPrinterLogAdapter implements PrinterLog
{
    private $logger;

    public function __construct(Zend_Log $logger)
    {
        $this->logger = $logger
    }

    public function log($whatever)
    {
        // delegate call to $this->logger
    }
}

class Printer
{ 
    private $logger; 

    function __construct(PrinterLog $logger)
    { 
        $this->logger = $logger; 
    }
} 

$printer = new Printer(new ZendPrinterLogAdapter(new Zend_Log));
Gordon
  • 312,688
  • 75
  • 539
  • 559
  • aah I see. So you wrapped `Zend_Log` and delegated all method calls to through the wrapper class. A question though if I'm building an application using Zend framework should I always wrap the library classes like that? classes such as `Zend_Mail`, `Zend_Session`, `Zend_DB`,...etc must all be wrapped first before using them in my code? – Songo Nov 07 '12 at 18:49
  • I realize it's essentially the same pattern, but isn't this a case where we'd want to call it a Proxy (instead of an Adapter) since we don't want to change the interface, just abstract it? Or am I off-base? re: http://stackoverflow.com/questions/350404/how-do-the-proxy-decorator-adaptor-and-bridge-patterns-differ – Matthemattics Nov 07 '12 at 18:51
  • @Songo If you want to follow DIP religiously, then yes. The benefit is that the lower layers can be more easily swapped out with different implementations, which benefits change and maintenance. However, it takes extra effort to write those adapters. It's a tradeoff and judgement you have to make yourself. Also see http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html – Gordon Nov 07 '12 at 19:13
  • @Gordon that's a good point, actually there was another problem that I was trying to solve by applying the DI which is unit testing. **Ignoring the fact that I can mock concrete classes in PHP**, if I wanted to test the `Printer` class in isolation from `Zend_Mail` then I needed to pass it a mock of an interface not a concretion. Looking at it this way is wrapping the zend framework library classes the only solution for unit testing to work in isolation? – Songo Nov 07 '12 at 19:22
  • @Lübnah Hmm, it's an Adapter to me because I do not want a placeholder here. The Interface defines a specific role at that specific dependency level. Anything that can fulfill this role from a lower dependency level needs to adapt to that interface, even if it is a 1:1 delegation like in this case. – Gordon Nov 07 '12 at 19:23
  • @Songo well, like you already said, you can mock concrete classes with PHPUnit, so it's not *the only way*, but yeah, the approach above would make it easy to stub/mock dependencies, too. – Gordon Nov 07 '12 at 19:26
2

Here is somewhat alternative take on same API .. so this would be you standard setup:

interface Printerish // couldn't think of a good ajective
{
    public function print();
}

interface CanLogStuff // couldn't think of a good ajective
{
    public function log( $text );
}


class Printer implements Printerish
{
    public function print()
    {
        // do something
    }
}

And this would be loggable printer:

class LoggedPrinter implements Printerish
{
    protected $logger;
    protected $pritner;

    public function __construct( Printerish $printer, CanLogStuff $logger )
    {
        $this->logger = $logger;
        $this->printer = $printer;
    }

    protected function print()
    {
        $this->logger( 'I can print, I can print !!' );
        $this->printer->print();
    }

}

Where this come from is the following use-case: if in real world you want to start controlling the use of a real printer (intern has been printing out the internet again). Then you would not make a different printer. You would be trying to add some external control.

In programming it kinda relates to Open/closed principle.

Keep in mind that this is just an idea and one should take a hard look at this before trying to use in production code.

tereško
  • 58,060
  • 25
  • 98
  • 150