Question

I have recently started reading about dependency injection and it has made me rethink some of my designs.

The problem i have is something like this: Let's say i have two classes: Car and Passenger; For those two classes i have some data mappers to work with the database: CarDataMapper and PassengerDataMapper

I want to be able to do something like this in code:

$car = CarDataMapper->getCarById(23); // returns the car object
foreach($car->getPassengers() as $passenger){ // returns all passengers of that car
    $passenger->doSomething();
}

Before I knew anything about DI, I would build my classes like this:

class Car {
    private $_id;
    private $_passengers = null;

    public function getPassengers(){
        if($this->_passengers === null){
            $passengerDataMapper = new PassengerDataMapper;
            $passengers = $passengerDataMapper->getPassengersByCarId($this->getId());
            $this->setPassengers($passengers);
        }
        return $this->_passengers;  
    }

}

I would also have similar code in the Passenger->getCar() method to fetch the car the passenger is in.

I now understand that this creates dependencies (well, I understood it before too, but I wasn't aware that this is "wrong") between the Car and the Passenger objects and the data mapper objects.

While trying to think of the solution for this two options came to mind, but I don't really like any of them:

1: Doing something like this:

$car = $carDataMapper->getCarById(23);
$passengers = $passengerDataMapper->getPassengersByCarId($car->getId());
$car->setPassengers($passengers);

foreach($car->getPassengers() as $passenger){
    $passenger->doSomething();
}

But what if passengers have objects that they need injected, and what if the nesting goes to ten or twenty levels... I would wind up instantiating nearly every object in the start of my application, which would in turn query the entire database during the process. If i have to send the passenger to another object which has to do something with the objects that the passenger holds, I do not want to immediately instantiate these objects too.

2: Injecting the data mappers into the car and passenger objects and having something like this:

class Car {
    private $_id;
    private $_passengers = null;
    private $_dataMapper = null;

    public function __construct($dataMapper){
        $this->setDataMapper($dataMapper);
    }

    public function getPassengers(){
        if($this->_passengers === null && $this->_dataMapper instanceof PassengerDataMapper){
            $passengers = $this->_dataMapper->getPassengersByCarId($this->getId());
            $this->setPassengers($passengers);
        }
        return $this->_passengers;  
    }
}

I dont like this any better, because it's not like the Car is really unaware of the data mapper, and without the data mapper, the Car could behave unpredictably (not returning passengers, when it actually has them)

So my first question is: Am I taking a completely wrong approach here, because, the more I look at it, the more it looks like I'm building an ORM, instead of a business layer?

The second question is: is there a way of actually decoupling the objects and the data mappers in a way that would allow me to use the objects as described in the very first code block?

Third question: I've seen some answers for other languages (some version of C, I think) resolving this issue with something like this described here: What is the proper way to inject a data access dependency for lazy loading? As I haven't had time to play with other languages, this makes no sense to me, so I'd be grateful if someone would explain the examples in the link in PHP-ish.

I have also looked at some DI frameworks, and read about DI Containers and Inversion of Control, but from what I understood they are used to define and inject dependencies for 'non dynamic' classes, where for instance, the Car would depend on the Engine, but it would not need the engine to be loaded dynamically from the db, it would simply be instantiated and injected into the Car.

Sorry for the lengthy post and thanks in advance.

Was it helpful?

Solution

I was going to say "I know this is an old question but..." then I realized you posted it 9 hours ago, which is cool, because I just came to a satisfactory 'resolution' for myself. I thought of the implementation and then I realized it is what people were calling 'dependency injection'.

Here is an example:

class Ticket {

    private $__replies;
    private $__replyFetcher;
    private $__replyCallback;
    private $__replyArgs;

    public function setReplyFetcher(&$instance, $callback, array $args) {
        if (!is_object($instance))
            throw new Exception ('blah');
        if (!is_string($callback))
            throw new Exception ('blah');
        if (!is_array($args) || empty($args))
            throw new Exception ('blah');
        $this->__replyFetcher = $instance;
        $this->__replyCallback = $callback;
        $this->__replyArgs = $args;
        return $this;
    }

    public function getReplies () {
        if (!is_object($this->__replyFetcher)) throw new Exception ('Fetcher not set');
        return call_user_func_array(array($this->__replyFetcher,$this->__replyCallback),$this->__replyArgs);
    }

}

Then, in your service layer (where you 'coordinate' actions between multiple mappers and models) you can call the 'setReplyFetcher' method on all of the ticket objects before you return them to whatever is invoking the service layer -- OR -- you could do something very similar with each mapper, by giving the mapper a private 'fetcherInstance' and 'callback' property for each mapper the object is going to need, and then set THAT up in the service layer, then the mapper will take care of preparing the objects. I am still weighing the differences between the two approaches.

Example of coordinating in the service layer:

class Some_Service_Class {
    private $__mapper;
    private $__otherMapper;
    public function __construct() {
        $this->__mapper = new Some_Mapper();
        $this->__otherMapper = new Some_Other_Mapper();
    }
    public function getObjects() {
        $objects = $this->__mapper->fetchObjects();
        foreach ($objects as &$object) {
            $object->setDependentObjectFetcher($this->__otherMapper,'fetchDependents',array($object->getId()));
        }
        return $objects;
    }
}

Either way you go, the object classes are independent of mapper classes, and mapper classes are independent of each other.

EDIT: Here is an example of the other way to do it:

class Some_Service {
    private $__mapper;
    private $__otherMapper;
    public function __construct(){
        $this->__mapper = new Some_Mapper();
        $this->__otherMapper = new Some_Other_Mapper();
        $this->__mapper->setDependentFetcher($this->__otherMapper,'someCallback');
    }
    public function fetchObjects () {
        return $this->__mapper->fetchObjects();
    }        
}

class Some_Mapper {
    private $__dependentMapper;
    private $__dependentCallback;
    public function __construct ( $mapper, $callback ) {
        if (!is_object($mapper) || !is_string($callback)) throw new Exception ('message');
        $this->__dependentMapper = $mapper;
        $this->__dependentCallback = $callback;
        return $this;
    }
    public function fetchObjects() {
        //Some database logic here, returns $results
        $args[0] = &$this->__dependentMapper;
        $args[1] = &$this->__dependentCallback;
        foreach ($results as $result) {
            // Do your mapping logic here, assigning values to properties of $object
            $args[2] = $object->getId();
            $objects[] = call_user_func_array(array($object,'setDependentFetcher'),$args)
        }
    }
}

As you can see, the mapper requires the other resources to be available to even be instantiated. As you can also see, with this method you are kind of limited to calling mapper functions with object ids as parameters. I'm sure with some sitting down and thinking there is an elegant solution to incorporate other parameters, say fetching 'open' tickets versus 'closed' tickets belonging to a department object.

OTHER TIPS

Maybe off-topic, but I think that it will help you a bit:

I think that you try to achieve the perfect solution. But no matter what you come up with, in a couple of years, you will be more experienced and you'll definitely be able to improve your design.

Over the past years with my colleagues we had developed many ORMs / Business Models, but for almost every new project we were starting from scratch, since everyone was more experienced, everyone had learned from the previous mistakes and everyone had come across with new patterns and ideas. All that added an extra month or so in development, which increased the cost of the final product.

No matter how good the tools are, the key problem is that the final product must be as good as possible, at the minimum cost. The client won't care and won't pay for things that can't see or understand.

Unless, of course, you code for research or for fun.

TL;DR: Your future self will always outsmart your current self, so do not overthink about it. Just pick carefully a working solution, master it and stick with it until it won't solve your problems :D

To answer your questions:

  1. Your code is perfectly fine, but the more you will try to make it "clever" or "abstract" or "dependency-free", the more you will lean towards an ORM.

  2. What you want in the first code block is pretty feasible. Take a look at how the Doctrine ORM works, or this very simple ORM approach I did a few months ago for a weekend project:

https://github.com/aletzo/dweet/blob/master/app/models

Here is another approach I thought of. You can create a 'DAOInjection' object that acts as a container for the specific DAO, callback, and args needed to return the desired objects. The classes then only need to know about this DAOInjection class, so they are still decoupled from all of your DAOs/mappers/services/etc.

class DAOInjection {
    private $_DAO;
    private $_callback;
    private $_args;
    public function __construct($DAO, $callback, array $args){
        if (!is_object($DAO)) throw new Exception;
        if (!is_string($callback)) throw new Exception;
        $this->_DAO = $DAO;
        $this->_callback = $callback;
        $this->_args = $args;
    }
    public function execute( $objectInstance ) {
        if (!is_object($objectInstance)) throw new Exception;
        $args = $this->_prepareArgs($objectInstance);
        return call_user_func_array(array($this->_DAO,$this->_callback),$args);
    }
    private function _prepareArgs($instance) {
        $args = $this->_args;
        for($i=0; $i < count($args); $i++){
            if ($args[$i] instanceof InjectionHelper) {
                $helper = $args[$i];
                $args[$i] = $helper->prepareArg($instance);
            }
        }
        return $args;
    }
}

You can also pass an 'InjectionHelper' as an argument. The InjectionHelper acts as another callback container -- this way, if you need to pass any information about the lazy-loading object to its injected DAO, you won't have to hard-code it into the object. Plus, if you need to 'pipe' methods together -- say you need to pass $this->getDepartment()->getManager()->getId() to the injected DAO for whatever reason -- you can. Simply pass it like getDepartment|getManager|getId to the InjectionHelper's constructor.

class InjectionHelper {
    private $_callback;
    public function __construct( $callback ) {
        if (!is_string($callback)) throw new Exception;
        $this->_callback = $callback;
    }
    public function prepareArg( $instance ) {
        if (!is_object($instance)) throw new Exception;
        $callback = explode("|",$this->_callback);
        $firstCallback = $callback[0];
        $result = $instance->$firstCallback();
        array_shift($callback);
        if (!empty($callback) && is_object($result)) {
            for ($i=0; $i<count($callback); $i++) {
                $result = $result->$callback[$i];
                if (!is_object($result)) break;
            }
        }
        return $result;
    }
}

To implement this functionality in the object, you would require the injections at construction to ensure that the object has or can get all of the information it needs. Each method that uses an injection simply calls the execute() method of the respective DAOInjection.

class Some_Object {
    private $_childInjection;
    private $_parentInjection;
    public function __construct(DAOInjection $childInj, DAOInjection $parInj) {
        $this->_childInjection = $childInj;
        $this->_parentInjection = $parInj;
    }
    public function getChildObjects() {
        if ($this->_children == null)
            $this->_children = $this->_childInjection->execute($this);
        return $this->_children;
    }
    public function getParentObjects() {
        if ($this->_parent == null)
            $this->_parent = $this->_parentInjection->execute($this);
        return $this->_parent;
    }
}

I would then, in the constructor of my service class, instantiate the mappers relevant to that service using the relevant DAOInjection classes as arguments for the mappers' constructors. The mappers would then take care of making sure each object has its injections, because the mapper's job is to return complete objects and handle the saving/deleting of objects, while the service's job is to coordinate the relationships between various mappers, objects, and so on.

Ultimately you can use it to inject callbacks to services OR mappers, so say you want your 'Ticket' object to retrieve a parent user, which happens to be outside the realm of the 'Ticket Service' -- the ticket service can just inject a callback to the 'User Service', and it won't have to know a thing about how the DAL works for other objects.

Hope this helps!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top