Ever since PHP became an Object "Oriented" Language there has been a growing desire by the advancing and more professional sections of the community to address the problem of dependencies. Between Flow3's pseudo-compiled comment driven dependencies to Laravel's IoC container, the urge to address this problem is on the rise. Although the problem may appear to be a technical one, I would argue that the problem is actually much bigger. But more importantly, it's not a problem at all.
A week or so ago, some coworkers and I were addressing the recently ratified PSR-1 and PSR-2 coding standards from PHP Framework Interoperability Group. Having read both with some interest when first announced, I quickly made the point that coding standards (as in how code is written and presented) has little to do with interoperability. I could write a mass of PHP with little to no indentation, unclear variable names, mixed case conventions, etc, and it could be highly interoperable.
The question in my mind is not what has FIG done... but what should it do?
Standardize What PHP Refuses To
PHP has a somewhat limited number of standard objects and interfaces. The creation of DateTime was, in some sense, a defining point of both success and failure of PHP. In the exact same moment that it broke the ArrayObject curse, it also drew a line in the sand: "fundamental data types will never have object representation." This probably doesn't seem like that big of an issue, but it's indicative of the general problem with the PHP community to address interface dependencies along with implementation dependencies.
Let's review.
class Foo
{
public function __construct()
{
// Do some constructor Logic
}
public function method()
{
// Do something useful
}
}
The above example represents two concepts. It represents first and foremost an implementation. That is to say, Foo is an implementation of some sort of interface. Currently, what defines that interface is the implementation. Truly, the only thing that says method() should exist is the fact that the implementing class Foo defines it.
PHP has had legitimate interfaces for some time. It uses the implements keyword to indicate which interfaces a particular class (implementation) provides, and thus, what methods are available, what their arguments are, etc. If Foo were to provide interface Bar we would expect, indeed PHP would force, Foo to implement all methods defined in the Bar interface. In this sense, you can think of interfaces as the method names, arguments, and return types while the implementation is the class which... well... implements them.
Different Dependencies
There are two types of dependencies you can create in your code. The first is the implementation dependency.
class Foo
{
public function __construct()
{
$this->child = new FooChild();
}
}
Regardless of what the OOP geniuses of the PHP community tell you, using objects does not inherently prevent you from being implementation independent. That is to say, the above code obviously requires an implementation specifically called FooChild. This is not inherently different from calling a static method such as FooChild::method(), other than the fact that the static method call produces the other common type of dependence: interface dependence.
In short, not only do we, in such a circumstance, require the FooChild implementation, we also require the method function as a component of its interface.
Decoupling
The end goal of all this dependency injection drivel is simple. No class should ever be dependent on a specific implementation, i.e. no class should ever reference another class name directly. Furthermore, interfaces should never be implementation specific. These two principles combine to mean, quite simply, that we could hypothetically have 200 implementations (classes) which all provide the same interface (methods, arguments, etc), and it shouldn't matter which one Foo (in this case) uses.
How it determines which implementation to use is a problem worth addressing, however, the much larger problem in my mind is how it determines that the interface is provided at all.
Dependency Injection of Varied Implementations
A number of frameworks have attempted to solve the issue of dependent implementations (not interfaces) in a number of different ways. Flow3 and Recess, for example, uses annotations. These annotations provide information about which classes or instances of classes a particular method might depend on. The following example uses standard PHP doc blocks to reflect this concept (not the annotations of either previously mentioned framework).
class Foo
{
/**
* Create a new Foo
*
* @param IFooChild $foo_child Object implementing IFooChild
*/
public function __construct(IFooChild $foo_child)
{
$this->child = $foo_child;
}
}
class FooChild implements IFooChild
{
...
}
The above code basically says that our first and only parameter must be an implementation of the IFooChild interface. This means that if we do any future calls to particular methods defined by that interface, we can be assured that these methods and the arguments that we provide will work on that particular implementation.
It also gives us an example of FooChild implementing that interface. Now, let's say that the interface requires the class to implement a method called interfaceMethod which accepts two parameters.
class FooChild implements IFooChild
{
public function interfaceMethod($arg1, $arg2)
{
// Do interface Method stuff
}
}
We can now see pretty clearly that we're able to pass an instance of FooChild to the constructor of Foo, and, if in a method of Foo it decides to do the following, we know it will work.
public function method()
{
$this->child->interfaceMethod($arg1, $arg2);
}
Now let's assume that developer X thinks they can write FooChild better. If developer X wants to ensure that their new Bar implementation can be used wherever FooChild was, they can go ahead an implement the same interface.
class Bar implements IFooChild
{
public function interfaceMethod($arg1, $arg2)
{
// Handle the interfaceMethod logic
}
}
It is now possible to do the following
$foo_child = new FooChild();
$foo = new Foo($foo_child);
$bar = new Bar();
$foo = new Foo($bar);
In short, it no longer matters which class (implementation) we instantiate and pass to our Foo constructor, everything works as expected. How do we know which implementation we want to inject? We don't... yet.
Inversion of Control Containers and Registries
One of the ways to deal with which dependencies to inject is to use an IoC container or global registry of sorts. Basically, what we want these to do is to allow us to register the construction of a particular class and it's dependencies in some way. From the Laravel documentaiton we can get a basic idea of how this works.
IoC::register('mailer', function() {
$transport = Swift_MailTransport::newInstance();
return Swift_Mailer::newInstance($transport);
});
Let's break this down as simply as we can. If we want a mailer object of some sorts (which presumably has its own well defined interface, even though it doesn't and there's no way to ensure it with the IoC container), Laravel tells us that we should register its creation with the IoC container. We can then use the IoC::resolve method and pass in 'mailer' to get that instance. Our code doesn't give a damn what the implementation is, only what the interface is. That is to say we might want to do something like the following:
IoC::resolve('mailer')->send(
'info@dotink.org',
'This is the subject of the e-mail',
$body
);
Now, aside from assuming that the instance of mailer itself will have a send method, we can see that for this particular instance, we also require a transport object of some sorts. What the IoC container does is abstract the transport dependency, providing us the ability to change the implementation not only of our mailer, but perhaps also the mailer's transport mechanism. Problem solved!
Well, yes and no. We've basically centralized our dependency to a single point, however, we've not gotten rid of it. Furthermore, we've now also introduced the dependency of IoC into our code. "Well that's not too bad," you say? No, one dependency is certainly better than 2, 3, or even more. But the point is, even though we've centralized it, we've not really solved anything.
For starters, in no way have we ensured that the instance we will receive back from IoC::resolve implements an interface with a send method. Furthermore, even if we can swap out Swift_MailTransport for Custom_MailTransport when we register our dependency, we have done very little to ensure our $transport object will implement the required interface. Now, that of course may be done inside Swift_Mailer, but it's not in any way shape or form guaranteed.
All we've really done is made the problems associated with dependency injection more convoluted and hard to follow. What's worse, is we've broken our access modifiers.
Singletons in the IoC Container
Laravel offers an IoC::singleton method. This is a circumstance where presumably you want only a single instance of a class to exist, and no matter who tries to instantiate it, you want to return that instance. The problem here of course is that in order for the IoC container to instantiate the first instance of a singleton, the __construct method of that singleton must be public. You could say, "no it doesn't, can't you do this?"
IoC::singleton('foo', function() {
return Foo::getInstance();
});
Well yes, indeed you can... but then why on earth does IoC provide a separate singleton method? What is wrong with the following?
IoC::register('foo', function() {
return Foo::getInstance();
});
The implication of the singleton method is that the IoC container is going to handle your singleton "assurance". That is to say, if you register a singleton and return an instance, IoC will store in an internal $singleton_instances array, or something equivalent, the given instance under that key. So that when someone does the following, it checks first to see if that instance exists and returns it in place of a totally new one.
IoC::resolve('foo');
While this may be a nice shortcut, the fact that the original singleton resolution function is expected to actually instantiate the class, rather than a static method call to the class instantiating it is evidence that the common pattern is to make our __construct method on supposed singletons, public. In that case, what prevents someone from doing the following in some non-IoC-loving code and receiving the non-singleton instance?
$foo = new Foo()
Nothing. This begins to affect the authority of the class over itself. That is to say, we now have to concern ourselves with whether or not people are using the IoC container, and IoC has to concern itself with whether or not something is a singleton or not. Suddenly, the idea that our class is modular evaporates. It is no longer functionally useful as it is designed to be in an environment where IoC doesn't exist or isn't used.
Woe, is Me
If you ask me the community is fucking nuts. People from every corner are trying to define custom ways of doing dependency injection and either simply moving the issue or making it even more convoluted. The short fact is, PHP has no standard way of doing dependency injection... and it likely never will. What compounds this issue further is that even if PHP does standardize a method of doing implementation dependency injection, we will still suffer the pains of non-standard interfaces.
What good is your decoupling if the only implementation of the interface you expect is the one you're trying so hard to decouple yourself from?
The Solution
I started this article talking about FIG. The point I was making there is that the most important role for a group like FIG at this moment is to begin defining interfaces and, for that matter, good interfaces. The reason there's so many PHP frameworks and libraries that essentially do the same thing is that because people differ on interface, not implementation. Outside of the most fundamental interfaces like ArrayAccess and Iterator there is little to no standardization. What's more, is that if there was such an attempt, I'm quite certain it would be almost wholly rejected by the PHP community.
Interfaces are everything to PHP developers. If all that's left to us is to come up with how precisely one should implement a given method, in light of the fact that the method has clear arguments and a clearly desired return value, we lose much of the creativity of the community.
Professional "code monkey" types, particularly in Java, have been comfortable with such a state of affairs for a long time. The new crowd of PHP developers who hype dependency injection seem to want us to dive further and further into that puddle. Yet somehow, very few such developers actually bother even defining actual interfaces.
If you want to fix (finally) dependency injection in PHP and truly make it possible to decouple our frameworks and libraries, two things need to occur:
- PHP has to implement some native functionality for dependency injection and the community has to use it
- The PHP community has to settle on well defined interfaces for the most common aspects of development
Until both of these things happen, all of Flow3's annotated classes are going to depend on Flow3 to work as expected.... All of Laravel's bundles using IoC container are going to depend on Laravel to work as expected.
For those of you who say I'm missing the point, i.e. the point is not interoperability but being able to replace pieces of your own code in a modular fashion and/or create mocks for testing, I say to you... you're missing my point. Finally, if your notion of dependency only goes so far as to be defined by dependency on yourself, I dare say, you're missing the point of dependency injection entirely.
comments powered by Disqus