2

In the process of learning test-driven development, I've been introduced to dependency injection and the use of interfaces, and have started using these concepts in my own PHP code in order to make it more testable.

There have been times when I've needed to test code that was doing things like calling the PHP time() function. In order to make these tests predictable, it seemed logical to create an interface to the standard PHP functions I use so that I can mock them out in my tests.

Is this good software design? What are the pros and cons of doing this? I've found myself groaning at how quickly my PHP interface can stick its fingers into everything I do. Is there a better way to make code that relies on PHP-accessed state and functions more testable?

5

You should abstract away calls to time() like you should abstract away calls to rand() and its ilk. The reason for doing that is because functions like that are forms of global, non-deterministic state. Global, non-deterministic state makes testing much more difficult because you cannot isolate your tests and test all conditions at once. Imagine if I had to wait until after noon to see if my code said "Good afternoon" instead of "Good morning" or "Good evening"!

If you see your interface starting to permeate your application, this may not be a bad thing. If anything, it is telling you how much you are using these built-in functions. You can depend on you to maintain your code and your interfaces within your code base, so you should not have as many worries about a shifting interface causing shotgun surgery. Abstracting away time() should be a pretty stable interface and probably should not see any changes for the most part.

The alternative to an interface is to simply inject the result of a call to time() rather than calling it in the method. So write getGreeting(time()); rather than writing getGreeting($timeUtil); (which in the method does $time = $timeUtil->getTime()). This adheres more closely to the Law of Demeter as you are not reaching for one thing through another, but it also means that the time you want must be the time at which the method is invoked.

An alternative alternative is to pass in a function reference rather than generating a separate interface. In PHP, you could do something like:

function getTime($timeFunc) {
    return call_user_func($timeFunc);
}

echo getTime('time')."\n"; # Call the built-in time function
echo getTime(function() { # Call a custom function
    return 1;
})."\n";

This has less overhead than the full-blown interface while still allowing the function to be lazily called and allowing dependency injection.

  • Very clear explanation, +1. I only have a general knowledge of the Law of Demeter. If wrapping time() violates the Law of Demeter, wouldn't every interface do the same? – Nathan GoFundMonica Arthur Jun 11 '14 at 18:31
  • Wrapping time() (or generally using an interface) doesn't necessarily violate the Law of Demeter. Demeter is about getting what you need while avoiding unnecessary indirection (which creates dependencies). If I need the time at the moment getTime is called, then it makes the most sense just pass in the actual time. No additional dependencies. If I need multiple times, or times requested lazily, I really need the interface (or function reference) to call at my leisure. I have a dependency on the functionality I need, but nothing more. – cbojar Jun 11 '14 at 18:41
  • 1
    Thanks, again for the great answer! Accepted. I ended up taking a more critical look at my PHP interface and removing all the PHP function-wrapping methods that weren't contributing to testability. This had the net effect of both reducing my dependence on the interface while maintaining the ability to control non-deterministic global state during testing. – Nathan GoFundMonica Arthur Jun 12 '14 at 13:25
2

You can either

  • create interfaces to system state methods like date or the content of session/cookie as you suggested or,
  • put these infos into business-method as additional parameters.

Example:

  • the invoice module can get the invoice date itself through an interface or,
  • the caller of the invoice module can provide the invoice date.

I prefer the 2nd method although this means that every time the module needs more state infos you have to change the method signature. On the other side in testing it is much easier to provide parameter than to mock methods.

I've found myself groaning at how quickly my PHP interface can stick its fingers into everything I do.

If your module needs too many parameters or too many interfaces, this might be an indicator that your module is doing too much and violates "separations of concerns."

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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