Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

Given the following code:

class ClientClass{

    public function print(){
        //some code to calculate $inputString
        $parser= new Parser($inputString);
        $result= $parser->parse();
    }
}

class Parser{
    private $inputString;

    public __construct($inputString){
        $this->inputString=$inputString;
    }
    public function parse(){
        //some code
    }
}

Now the ClientClass has dependency on class Parser. However, if I wanted to use Dependency Injection for unit testing it would cause a problem because now I can't send the input string to the parser constructor like before as its calculated inside ClientCalss itself:

class ClientClass{
        private $parser;
        public __construct(Parser $parser){
            $this->parser=$parser;
        }
        public function print(){
            //some code to calculate $inputString
            $result= $this->parser->parse(); //--> will throw an exception since no string was provided

        }
}

The only solution I found was to modify all my classes that took parameters in their constructors to utilize Setters instead (example: setInputString()).
However, I think there might be a better solution than this because sometimes modifying existing classes can cause much harm than benefit.

So,

  • Are injectable classes not allowed to have input parameters?
  • If a class must take input parameters in its constructor, what would be the way to inject it properly?

UPDATE
Just for clarification, the problem happens when in my production code I decide to do this:

$clientClass= new ClientClass(new Parser($inputString));//--->I have no way to predict $inputString as it is calculated inside `ClientClass` itself.

UPDATE 2
Again for clarification, I'm trying to find a general solution to the problem not for this example code only because some of my classes have 2, 3 or 4 parameters in their constructors not only one.

share|improve this question
2  
Can you change $inputString to be a parameter to Parser::parse? –  Mike Partridge Oct 24 '12 at 19:19
    
@MikePartridge well sadly No. I have little control on the current code as it is part of some pile of big old legacy code I inhereted. BTW this is a sample code only. plz see my second update. –  Songo Oct 24 '12 at 19:29
    
Beware of logic in your constructors. You will be haunted by code which does this, and the developers who have to maintain it if you wrote it. Sounds like you didn't write this, so I suggest you haunt the developer who did. :) –  Jimmy Hoffa Oct 24 '12 at 19:45
add comment

4 Answers 4

up vote 10 down vote accepted

In the old code, the ClientClass constructs a Parser. So minimal change would be to inject a parser factory, not an instance. The factory can have any arguments you need and obviously can pass them to the constructor of created class.

To give an example, the code in question should be modified along the lines of:

class ClientClass{
    private $parserFactory;
    public __construct($parserFactory){
        $this->parserFactory=$parserFactory;
    }
    public function print(){
        $parser = $this->parserFactory($inputString)
        $result= $parser->parse();
    }
}

and called like:

new ClientClass(function($inputString) { return new Parser($inputString) });

(of course a named function may be used too). This is perfectly generic; you wrap the construction of the object you need in a function that will pass through any arguments you need, provide that to the dependent class and inside that class you'll call the function instead of constructing the dependence directly. Of course the function you pass can create mock object instead or it can modify the arguments or hardcode some extra arguments or whatever you need.

If your language does not have function objects and lambda expressions, you can instead create a factory class. Where function objects are available, they are easier to write.

PS: I am not proficient in PHP, so I am not absolutely sure I don't have syntax error in the example.

share|improve this answer
1  
This answer is the only one which indicates that the answerer understands the code, the question, and the solution! –  Tom Anderson Oct 25 '12 at 10:24
    
+1000 although a bit late :) I agree with @TomAnderson this answer is exactly what I was looking for. Answer accepted ;) –  Songo Nov 1 '12 at 21:46
add comment
 > Are injectable classes allowed to have constructor parameters in DI?

In java/spring it is possible to have classes that require paramaters in the constructor as stated by @Jeff Vanzella.

However in you example it is more difficuilt to use di because the creation of the parser is not fixed- it depends on a non static context ($inputString).

One possible general soultion to this is: the parser class constructor gets a contextlocator parameter that knows how to get the context.

class Parser{
    private $contextLocator;

    public __construct($contextLocator){
        $this->contextLocator=$contextLocator;
    }
    public function parse(){
        $inputString = $contextLocator->getContext()
        //some code
    }
}   

But this aproach adds unneccessary complexity to your soulotion. The use of a init-method that defines the context (or functionlly equivalent the use of a property to set the context as you suggested) is much easier.

in you special example this is not necessary. Move the parameter from the constructor to the parse method.

class Parser{
    private $inputString;

    public __construct(){
    }
    public function parse($inputString){
        $this->inputString=$inputString;
        //some code
    }
}
share|improve this answer
    
Sorry, but what is $contextLocator is exactly? another class maybe? –  Songo Oct 24 '12 at 20:51
    
correct: $contextLocator is a class or a closure or function –  k3b Oct 24 '12 at 21:04
    
I see, but how should I use it inside ClientClass then? –  Songo Oct 24 '12 at 22:00
add comment

Simple answer: Yes, they can have input parameters. That is how I usually use DI and IoC.

The injection is done as soon as you can in your program. As for testing, in your case, just inject a known string. For other classes, you would have to actually make the parameter an Interface and either inject a mock class that does something known, or a mock object created from a mocking framework, which there are numerous open source and paid frameworks.

There is a wealth of information here. Most of the code is in C#, but the ideas and theories behind it are well written

share|improve this answer
1  
Sorry, but I don't quite follow you there. The input to Parser is supposed to be determined inside ClientClass.print() if I decided to create a new object of Client class like this $clientClass= new ClientClass(new Parser($inputString));//--->I have no way to predict $inputString as it is calculated inside ClientClass itself. the problem isn't in testing it's in how to instantiate the class itself when one of its dependencies need an input calculated from that class! –  Songo Oct 24 '12 at 19:00
    
I did say as soon as you can... If that is inside the class, then that is where you do it. –  Jeff Vanzella Oct 24 '12 at 20:20
add comment

I realize this might be simplified example of what you deal with, but it seems that your print method (now assuming this is a code you work with and can change) does a little bit too much. To be precise, it calculates value it has to print. Moving that behaviour to separate construct (another dependency) will easily mitigate problem:

class ClientClass{
    private $parser;
    private $inputProvider;

    public __construct(InputProvider $inputProvider, Parser $parser){
        $this->inputProvider = $inputProvider;
        $this->parser = $parser;
    }

    public function print(){
        $input = $this->inputProvider->calculate();
        $result = $this->parser->parse($input);
    }
}

This way, not only you don't have to change any existing code (Parser), but you gain little bit more in terms of lose coupling/separation of concerns. Printing does print, and input calculation logic (which can be unit tested separately, too) is delegated outside.

share|improve this answer
    
@Downvoter: adventurous enough to share knowledge which led you to decision this answer is incorrect? –  jimmy_keen Oct 25 '12 at 18:16
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.