OK, I think this is likely a stupid crazy idea, but it has so much practical benefits at first sight that I want to bring it up on the blog and not just twitter. As a rule of thumb when writing code one should always try to reference an interface in type hints, instanceof or is_a() checks. This in theory allows people to replace the implementation if only for unit tests. But more importantly it could also allow someone to switch to an entirely different lib for the given dependency. One practical issue here is that either both projects need to share a common interface library or forced the need to write adapters in order for a 3rd party lib to become compatible even if the relevant API methods have the exact same names and signatures. Now lets imagine a world of collaboration where some subset of the community (as big as possible, as small as necessary) agrees on some common interfaces, but they don't want to burden their code with one dependency for each of these interfaces. Especially as for different libraries a different subset of the community could end up collaborating. Here I see 3 options: 1) each library bundles the interfaces (even though they sit in some common namespace), 2) each project asks their users to fetch the common interfaces from some other place 3) runtime "coupling". Option 3) doesn't exist today and is what this blog post is about.
Actually maybe this is already possible using the runkit extension. This extension lets you mess around with essentially everything that is defined and usually can't be changed anymore: functions, classes, constants etc. Sounds harmful? It is! Right now the only valid use case I see is testing of untestable code (like being able to modify the behavior of date() at runtime). For option 3) what I am hoping for is a way to simply declare one interface to be implementing another interface. This would likely be run as part of your bootstrap or maybe the autoloader.
Here is some pseudo code that Ralph tweeted:
spl_register_compatible_interface('My\ LoggerInterface', 'Your\LoggerInterface');
Note this should effectively work a bit like the use statement in that it shouldn't trigger autoloading (maybe an optional parameter should trigger autoloading for debugging).
To give some historical context, I got the idea for this while listening in on a ZF2 IRC meeting. So I briefly mentioned this idea after the meeting and it seems like Ralph also saw some merit. Again neither of us was thinking about any way to alias method names let alone supporting reordering of signatures etc. So the use case would essentially in 99% (except for coincidences) of the cases still require direct communication between the authors of the libraries.
Of course another alternative to the "how to get the interfaces to my users" would be to make them part of PHP itself. Here I see a lot of practical issues:
As this solution would allow collaboration between projects with minimal logistical pains it could also then be the way to mature interfaces to the point where they are sufficiently widely adopted and used that it makes sense to put them into core. And this solution would then also be an easy to use migration path for the original collaborators. So with the above approach we are just offering a practical solution to real world problem. It certainly feels hacky, but at least even after a few days I still haven't decided that its going to spell the end of the world either. So what do people think?
Maybe it's time to introduce structura typing into PHP -> an example in Scala http://codemonkeyism.com/scala-goodness-structural-typing/ .
This kind of typing checks if an object implements a defined interface by examining it's method declarations instead of checking if an object's class declares explicitly than it implements some interface.
While this seems appealing its slightly tricky with a language like PHP where you are dynamically typed and there are no type hints for primitive types (on that topic here is my proposal).
To take lessons from other areas that have done this sort of thing:
If you look at the way data is modelled, first we had a shot with XML - which didn't really work, so we tried a few things with namespaces, xsd, etc.
All of these things were fine, to a point - XSD for instance tends to be way too restrictive - so even though two parties can share the same XSD; the more parties you add, the more likely it is for someone to fork the schema.
RDF solves a lot of those issues - with either OWL or RDFS - describing what a data object looks like; without excluding other information by default.
So - we've got the tools in that domain. But 10 years of effort has only gotten us a few really good vocabularies; like FOAF; GoodRelations, DublinCore, etc.
10 years? And we've got really only a semantic web we can draw on a picture still (http://richard.cyganiak.de/2007/10/lod/) ? Eeek! And John Q Public doesn't really care about it either?
So... introducing a mechanism to state that one piece of code is loosely the same as another; and getting all of those communities to adopt it - I suspect there will be far more human, communication related issues that will emerge.
I suspect you will need concepts for:
* X === Y (owl:sameAs)
* X implements Y
* X extends Y
Unfortunately, interfaces aren't full of enough data by themselves - you need to specify the arguments, return values, and exceptions.
Docblocks do this... but so does WSDL to some extent.
I also think you also want a different mechanism in the PHP language, that emits a warning if you stick an unregistered type rather than a fatal error - or by putting a type hint as an interface name, allows you to put whatever content you like in there so long as it implements the interface (in terms of the methods available, rather than explicitly having 'implements Foo').
Again the goal is to simply make it easier to get the common interfaces to your users. So what I am proposing here is a solution for a logistical problem. I am not offering a solution here to the need for communication. However I currently see the logistical problem is the initial hurdle to overcome before people will feel sufficiently inclined to take the next steps. Of course the final goal will entail getting over multiple hurdles and so even with the initial hurdle done away, we might discover that we cannot overcome the others. But I am hoping that we can do away with one hurdle at a time. Trying to do it all at once seems to not have worked in the past ..
I understand the goal, but I'm not sure if it's actually worth the effort.
For this to work, project A will need to make the affirmative, active step to register compatible \A\SomethingInterface == \CommonPlatform\SomeInterface. It's still work on their part.
Simply including the interface definition in the code is also affirmative, active work. I don't know that it's any more work to just include \CommonPlatform\SomeInterface in your code base than to setup an alias. Interfaces are hardly high-overhead, and there would still be the same autoload hit to actually use the interface, whatever it is.
So, I guess I don't see how it buys us much to do that, since the amount of work for a project to adopt it is not appreciably different either way.
Oh the intention isn't for frameworks to ship this alias, but to enable users to configure the alias if they want to switch implementation.
I don't actually get your point about problems with tricky implementation of structural typing. It is very simple - just find out if method signatures of some interface are identical to these defined in the class that an object belongs to. If a decision in future would be made about implementing scalar type hints, than I think than checking should be done regarding the rules of scalar type conversion.
Well the thing is "structural type hinting" has a big risk of giving false positives, especially with simple interfaces with few parameters. Now if those few parameters are scalar then the chances of false positives are even higher. So this way you effectively break the assumption that you currently can have about instanceof etc checks and so incorrect usage of an API could remain undetected as compile or runtime until something horribly breaks and you don't know why.
Ok, now I understand your point. I think, that this situation could be resolved by adding a return type hint - it would tighten the definiton. Another resolution would be generating a warning, when a possible "false positive" may occur.