Update: Now on github.
I’ve just finished listening to the second /dev/hell podcast from Chris (my GTAPHP co-organizer) and Ed where they discussed a big problem with frameworks in PHP. They’re too big, and its too difficult to extract only the functionality you want without bringing over a whole pile of dependencies at the same time. I’ve been unhappy about the same kind of problem. I’ve been looking for a new CMS, and it drives me nuts that big CMS’s include their own frameworks instead of building on existing frameworks. There are exceptions, but generally a CMS includes its own framework.
Listening to Chris and Ed got me thinking about this, and about another pet peeve of mine with PHP frameworks. They don’t tend to make good use of class interfaces, depending instead on concrete classes. If they worked with interfaces then it would be easier for frameworks to share common code, and for CMS’s to build on common code. I have a modest proposal to address this problem…
I’d love to see a set of standard class interfaces that were not dependent on any existing frameworks. Then if the framework people would make their code implement and more importantly depend on those interfaces we would all be free to pick and choose whichever implementations we wanted to use and they would work together easily. I realize that this is asking a lot, and won’t happen any time soon, but I think it is a worthwhile goal for the PHP community to strive towards. I believe in this concept so strongly that I’m pledging right now to build a proof of concept.
I’ll call this project the Standard PHP Interface Library” (SPIL) until I think of something better. This is different from the Standard PHP Library (SPL) which provides standard classes and interfaces because the SPL provides concrete classes, and just a few generic interfaces for common behaviours while SPIL would provide only the definitions for classes without any concrete classes except a “loader” class.
The real usefulness of this comes from class providers. This will be familiar to those of you used to package management in Linux. Packages are maintained in different repositories, but it doesn’t matter much to the user where they get the package from so long as the software it provides is available. Likewise with SPIL a developer wouldn’t have to care a whole lot where the concrete class came from so long as it implements the required interface.
My real hope is that code would be factored out of popular frameworks and CMS’s and into SPIL repositories from those products. Then the product would use SPIL to load those classes and it would become easier for everyone to make use of that wealth of tried and true code, and encourage sharing of code between frameworks and CMS’s. SPIL classes could also be manually loaded allowing developers to inject their own code into systems that use SPIL to replace chunks of functionality without touching the code that calls it.
With an Interface library like this available, installation of required classes into development environments could be completely automated. Imagine we were building a REST service in PHP. We’re using SPIL so we request the SPIL loader, ask for a class which implements the REST interface, and start using it. Here’s how it might look.
<?php require_once('SPIL/loader.php'); SPIL_Loader::registerRepository('http://spil.symfony-project.org/'); //Wishful SPIL_Loader::registerRepository('http://spil.framework.zend.com/'); //Thinking! $restServer = new SPIL_REST_Server(); class MyRESTServices { public function index() { .. } public function get() { .. } public function post() { .. } public function put() { .. } public function delete() { .. } } $restServer->processRequest(new MyRESTServices()); ?>
The first time this is run in a development environment the class auto loader provided by SPIL can’t find a class which implements the SPIL_REST_Server interface in local storage, so it tries to auto-load one from the first repository. Let’s say for the sake of argument that it can’t find one there. No problem, it moves onto the next repository where it finds an implementation. Great – it downloads it from the remote server and puts it into a writeable folder on the local file system. Remember we’re in development mode now and this folder can be made read only in production when we’re no longer adding new classes. It loads the new class and we’re good to go.
Now let’s imagine that the concrete implementation loaded above requires a request object, and that the same author provides this object. Instead of hard coding it to that class they load the class through a SPIL interface calling it SPIL_HTTP_Request. Our loader goes to work on this, and again sees that we don’t have a local class to satisfy this request. It checks with the first repository and this time finds a matching class. It copies it to local storage and loads it. Now we have components from two different authors who may have not even known about each other. Still it all works together because they’re using agreed upon SPIL interfaces.
The SPIL project will supply the software needed to supply a repository, and the loader. It will also supply a set of interfaces, documentation for those interfaces, and PHPUnit tests for implementations. It will supply a repository with concrete classes for all standard interfaces.
Playing devils advocate with myself I have a few questions…
Will SPIL result in a “lowest common denominator” problem? No – the standard interface library will require a solid minimum level of functionality, and implementers are free to provide their own interfaces that require a superset of it. So for example, we might have a SPIL_HTTP_Request interface which also implements SPIL_X_ZF_HTTP_Request which implements a superset of SPIL_HTTP_Request. Here I’m imagining that we use SPIL_ to trigger the SPIL auto loader, X to indicate a vendor specific interface, ZF to indicate the vendor, and HTTP_Request to name that vendor’s interface. This way the code that needs the class can request a SPIL_HTTP_Request if they only need the base level of functionality or a SPIL_X_ZF_HTTP_Request if they need the extra functionality provided by that vendor. Also, it means that if another vendor’s HTTP_Request class could satisfy the same superset of features they could also implement SPIL_X_ZF_HTTP_Request so that classes that need the extra features and include the vendor specific functionality could use their version if it was available.
Will SPIL allow “bad guys” to inject evil code into my sites? You have to register SPIL repositories before invoking the auto loader. I plan to disallow further registrations after the first time the auto loader is invoked so that loaded classes can’t register their own repositories to make sure that developers are in complete control over which repositories they trust. Also, loading from repositories should only happen in development environments where they can be tested, and loading should be disabled in production environments.
It seems bold to go ahead and call this a standard. Who the hell do you think you are? I’m just the guy who is putting the pieces together to allow this, and to encourage the use of common interfaces in the PHP community. I’d love to make promotion of “unofficial” to “standard” interfaces completely democratic, but for now somebody’s going to have to take on that role. If the idea gets traction I’d like to set up a group of gatekeepers who work together to make sure that only high quality interfaces are allowed into the library.
How would SPIL deal with PHP version requirements? What about interface versions? I would have the loader class send the available PHP version to the repository, and allow or even require SPIL classes to indicate their compatibility with those versions before they’re required. As for interface versions, I would allow for a manual class loader that would only load implementations which implement a specific method. This would provide limited “duck typing” instead of managing interface version numbers, which I think would be overly complex.
Hopefully this post generates more criticisms that can be addressed. Also, if somebody has already done this please let me know before I waste too much time on my proof of concept implementation. I’ll post a github link as soon as I have SPIL code worth looking at.