8
votes

We are just starting to make a concerted effort to use dependency injection uniformly in our project, and I've run into an issue.

I'm writing a class to handle our MongoDB queries. I pass in a MongoClient as a dependency on the constructor, with no problem. But how do I handle a dependency when the variable necessary to instantiate the object is not available at the time of instantiation?

In particular, we have a wrapper for the MongoCollection method, findOne, that, if you pass a string in, currently (in the old code) turns that string into a MongoId with "new MongoId($_id)", and uses that for the find function.

From what I've learned about dependency injection, having "new MongoId" is a bad idea, and I know already that it will make it harder to write test cases for the function that converts a string to a MongoId.

But how do I handle the injection, when the MongoId class takes the id string on the constructor?

The only thing I've thought of that would work is to pass in a closure on the class constructor that does something like:

$getMongoId = function( $id ){
    return new MongoId( $id );
};

with

class MyMongo
{
   function __construct( MongoClient $client, Closure $mongoIdGetter){...}
}

[edited to fix this last part]

But is this the right way to handle it? Of course, if we are using a DiC, it we can do it, but requiring a closure for the constructor seems a bit much. Am I just being too dogmatic about injecting my dependencies? I could fix this easily by using "new MongoId($_id)" in the new class, I suppose.

2

2 Answers

2
votes

Instead of a closure you could use a Factory:

class MongoFactory
{
    public function createMongoDb($id)
    {
        return new MongoId($id);
    }
}

In Factories it is considered okay to have "new something" hard coded dependencies because the creation of objects is their sole purpose and you could easily replace them with another Factory.

Your consumer class (MyMongo) now will have a dependency to MongoFactory (or its interface if you will), which you can easily "inject".

1
votes

But how do I handle a dependency when the variable necessary to instantiate the object is not available at the time of instantiation?

PHP will fatal error before you have a chance to handle it yourself. If you use type parameters and/or don't define them as, by default, null PHP will fatal error when that parameter is not passed to any function.

From what I've learned about dependency injection, having "new MongoId" is a bad idea, and I know already that it will make it harder to write test cases for the function that converts a string to a MongoId.

Will it (in PHPUnit)?

$this->assertInstanceOf('\MongoId', $getMongoId($id_string));

But how do I handle the injection, when the MongoId class takes the id string on the constructor?

Dunno what you mean by that but you should only be testing the result of the MongoIds processing.

The last bit of your question looses me a bit, I think it is because it is not true PHP (i.e. $__construct).

I am unsure why you need to shove the function into the class like that. I mean what I have most times is:

function findById($id){
    if(!$id instanceof \MongoId) $id = new MongoId($id);
    return $this->getCollection()->findOne($id);
}

You don't need anything more than that and you don't need to test the constructor of MongoId since that has already been unit tested, you should be instead unit testing YOUR public API not some one else's.