Sunday, July 29, 2012

PHP Dependency Injection, Part 2


A few months ago I blogged about Diesel, a dependency injection framework for PHP. Since then, it's gotten a lot of use in our internal PHP code as well as our open source project, Bart.

It's also gotten a good deal of feedback from the rest of the team and been the subject of many a debate. The product of all that feedback and debate is a new version of Diesel that is easier to use and integrates more seamlessly into your existing class structure.

One of my initial goals with Diesel was that it would provide non-singleton nature, both from a per-class perspective and from the dependency registry point of view. Such that users would be able to define custom dependencies, and chains of these, when creating dependent classes. However, in practice, we observed that this was pretty much never necessary outside of unit tests.

Since we're able to reset pretty much any state we want in between tests, we concluded that the added complexity, not to mention burden on class signatures, was unnecessary and the Diesel interface could be simplified to static methods. The result is below,

 /**
  * Create an instance of class
  * @param string $className Name of the class
  * @param array $arguments Any arguments needed by the class
  * @return $className New instance of $className($arguments)
  */
 public static function create()
 {
 }

 /**
  * Get singleton instance of this class
  * @param type $className
  * @return $className Singleton instance of class
  */
 public static function singleton($className)
 {
 }


So users of Diesel, now only need to call either Diesel::create($className) or Diesel::singleton($className). If nothing is registered (which should be the case for production code), then Diesel will use reflection to create a new instance of the class with the supplied arguments.  If a singleton is desired, a new instance is created and cached for any future requests.

This is much simpler than previously, where all dependent classes had to define the dieselify() method and needed to accept Diesel instances in their constructors.

For tests, stubs or mocks can be registered via anonymous functions that can verify the arguments and then return the stub. Another improvement is that Diesel now supports enforcing during test time that a method be registered for any requested class. I.e. if a new instance or singleton is requested during tests for which no instantiation method exists, an exception is raised. This will prevent accidental creation of real classes during tests.

No comments:

Post a Comment