Sunday, November 7. 2010
Developers who are familiar with the topic of clean code know that static methods are a problem, especially if it comes to testability. Static makes the code global, and if there is some kind of state involved it is even more a problem because this is global state then - both a maintenance and testability nightmare.
Unfortunately, sometimes you don't get around of using such code. This could be due to a third party library you have to use, or even because of limitations of PHP itself. (Ever wanted to configure stream wrappers if stream context is not an option? Your only chance are static methods and therefore global state.) How can we keep our code clean and testable, at best without having a strongly coupled dependency to the class hosting the static method? Enter dependency injection for static methods.
Luckily PHP 5.3 comes with a new feature: dynamic access to static methods. Basically this means if you have a class with a static method you can call it like this:
Let's use this feature to get a more loose coupling between our own class and the static class. Suppose we have the following code:
For some reason the method call can not be moved to another place where we could get rid of the direct dependency. But if we change the code of the Bar class things look different:
The class still works as before, but we can change the name of the class the static method is called on. This is great, because in a test we can substitute the FooFromSomeLibrary class with our own mock implementation:
Our own Bar class is now only loosely coupled to FooFromSomeLibrary. While I don't suggest to use your own code in this way it is a helpful opportunity when working with code you don't have influence on.
Display comments as (Linear | Threaded)
Or better, just store a callback and use call_user_func(). Completely flexible and works back to PHP 4. I wish you could type hint callbacks.
Storing callback is nice when you have just one method to call on the 'injected class'. Not so nice when the number of different methods you need to call grows.
Do you mean "not so nice" because it takes more typing to create the callbacks? The way I see it, $className::method() seems just as much of a run-time hack as call_user_func(array($className, 'method')).
I'm not suggesting using callbacks for all dependencies, but in the case of static method calls, why not allow some flexibility? In the unit test you could then even pass in an anonymous function as a mock.
I mean the interface would be ugly:
'doSomething' => array('AnotherClass', 'doSomething'),
'doSomethingElse' => array('AnotherClass', 'doSomethingElse'),
OK, sure, but you're just making the class inflexible for the sake of a simpler interface. It might be better to just use an adapter than to go down either of these paths. Something like this: http://pastebin.com/YHLVv6mZ
Similar interface, yet you can override individual methods if you need to.
And here you violated the precondition Frank postulated in the original post: the call has to be static. If it wasn't for this precondition the post would be just reiterating the DI metapattern.
hehe i did not know about this trick as fortunately i dont have this problem at work now
i guess it is better than hardcoding class names everywhere .... at least you can unit test it.
thanks for a hint
Why don't you just pass an object in and call methods on it, making sure you don't make those methods static. If those methods are static and you can't change the source then wrap them in an object.
Surely that gives you a better interface.
Of course you could do that, the question is if it is worth the additional work if the only purpose is to decouple for better testability.
Syndicate This Blog