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.
3
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.
2
> 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
}
}
3
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
2
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.
1