Hi, I’ve read about dependency injection recently and I was wondering about how it works in some scenarios.
Thus far I’m using kind of DI container that passes dependencies in constructors, BUT I often pass the container itself too, e.g. when the requirement for a given object is only optional. After reading numerous posts, the conclusion is I should probably pass this object in the constructor anyway?
Let me ask you about some real-life examples.
How do you handle logging? Do you pass Logger object as a dependency everywhere where it MIGHT get used (on some rare PDOException etc.)? I think even Misko Hevery hinted, that using some sort of singleton for action like logging would make more sense (1->way operation, that does not change the state etc.)
What about observer pattern? Let me show some code
class UserManager implements SplSubject {
public function save() {
// do something useful
$this->notify();
}
}
class Notify implements SplObserver {}
class Log implements SplObserver {}
class Controller {
public function __construct(UserManager $user) {
$this->user = $user;
}
public function execute() {
// do something
// [...]
if ($success) {
$this->user->attach(new Notify);
$this->user->attach(new Log);
$this->user->save();
}
}
}
Most of the articles about DI I’ve read basically say “don’t you dare use ‘new’ in your code”. How would you handle the example above? I know I could pass both observers in the constructor, but they are not a dependency IMHO. I could simply comment them out and the data would be saved. UserManager dont’ have to know anything about them either (so they are not passed in its constructor).
Such design allows me also to easily add another observer or change observers, or simply disable them temporarily by simply commenting them out.
Testing the execute method wouldn’t be a problem too - I would simply mock UserManager object.
So what’s the DI answer to that?
If your constructor has e.g 10 arguments (dependency objects) and you want to unit test a method that uses the object from the 10th argument, you have to write 10 mocks, right? OK, maybe 10 objects hints a need for a little refactoring, but I’ve seen examples of 5 and more arguments around the forums.
OK, I haven’t thought about introspection - that would definitely help - phemto uses it?
Do you add null defaults for your constructor args to make mocking easier or you just mock everything in unit tests? I find those null defaults a bit ugly - especially that they are not needed in the app at all.
Moving up through the layers, one other thing strikes me - what’s your entry point in the app? Do you use some kind of front controller - but it would probably need access to the container, or maybe you just use the container directly in the index.php to run the app, or maybe something else (multiple entry points etc.)? I’m curious.
I’m sure one of my problems is moving from the established way of coding to the new way of coding. Although pure DI is not THAT different in my case, I find myself a bit stucked in the middle - between starting from scratch and extreme patching.
At this point I’m leaning toward scrapping my front controller (class) altogether and just moving its logic (nothing complicated) to index.php - which will also build a DI container and therefore have direct access to it. Any thoughts?
I’m still trying to wrap my head around it. I think it’s a great concept for small/uncomplicated apps, but I’m not as certain when it comes to more complicated ones.
I decided to try it anyway and started to refactor my base classes, which use both constructor-injected dependencies and the container (aka service locator) as the last dependency - “just in case”
So I ditched the container and left only the objects I actually need in THIS class. I have to admit - re-unit testing it was a breeze and kind of a joy. I still scratch my head, why the hell I used the container in my tests as well…
Back to the topic - I don’t do any extensive logging, but I actually log PDOExceptions in my Models (I save them in the db for easy fetching/analyzing later) after I rollBack the transactions. It’s handy, because it’s per application log, no need to parse text files…
But that could be reconsidered, maybe moved to the higher layer…
My other concerns after a bit of using it:
a) larger applications with more sophisticated layers = a lot of dependency configuration (e.g. Controllers use different models, so each controller has to be separately configured, as it has unique dependencies) - passing container (as a last arg) was easier
b) dependencies may become pretty long - especially when aggregating data from many models (on either controller or model level), classes would have to be even more specialized to minimize waste/overhead
I’m in the midst of refactoring a working e-shop application and I will certainly use what I learned.
For now, I’m leaning toward dependency injection for shared or explicit dependencies and DI+passing container (service locator etc) for more complicated scenarios (like conditional stuff).
After reading Fowler on the topic, I will probably consider using the service locator with a segregated interface - after all, I know which dependencies are optional.
But who knows, maybe after refactoring the service locator will not be needed anymore.
I find the bigger the app., the more DI kicks in. If you are using a tool that uses introspection, then you could just type hint to get the model you want…
class MySpecificController {
function __construct(..., MySpecificModel $model) { ... }
...
}
Logging is not normally handled by DI. It’s usually machine specific and so the (only) use for configuration. Your Environment or Configuration class will be injected from the top, but that’s about it.
Logging for security and monitoring is usually specific to the task at hand. You often want to turn it off in test environments and it comes in many forms. You also don’t want to duplicate logging mechanisms that already exist. By all mean instantiate loggers tactically.
Are you logging too much? I find it’s better to audit your logging decisions very carefully.
If a log does not have a clear goal, nuke it. Would you consider placing an SMS alert on a log message? If the answer is no, then you are probably generating work. Can you send an exception and deal with the error on the spot? The do that. Do customer services want to know when someone could not log in? Your data model should hold this information. If you don’t have the resources to build a UI, an ad hoc SQL query is still way faster than log trawling.
Dont log unless there is no other way. Scanning logs is skilled, labour intensive work.
Regarding observers, I don’t use a lot of them. Observers are for dynamically attaching objects in real time. PHP scripts aren’t real time. Each page is a batch job where you instantiate the whole lot on each request each time. Just pass the object you need and save yourself 50+ lines of code.
Regarding the long constructor, yes you have to mock the lot. If you now an object is not used in a test, then just pass null. Also, for this rather extreme case, you can factor your test case a little.
Mocking all those objects is still a good thing. You won’t just have control of the object under test, you’ll know you have control of the object. The feeling of confidence is really nice, especially with things like controllers which have been historically difficult to test.