This is more of a rant about how we (not limited to, but including PHP community) have been designing our packages than a real article. So, i bet as anyone right now, when you are writing a project you are depending on bunch of packages/libraries people have created and you are using composer to manage your dependencies. This is all fine an you should keep doing that as it’s the best way to manage your dependencies and will save you the time you would spent reinventing the wheel.
Now the not so good parts. If you are a big framework your composer.json will look something like this:
You can pull this many dependencies off since most of them are under your direct control and they mostly don’t depend on anything outside your own packages. Now let’s look how might an example composer.json look for a medium to large size project.
So what’s changed? You might see that in this case we are depending on bunch of Zend stuff, some PHP extensions and a lot of stuff that directly under your (as a Magento maintainer) control. But we are interested in what remains — the depends you don’t control and that are from independent developers around the world.
It might not be immediately obvious what the problem here is. So let’s have an example. You want to create an application that works with reddit API and some lesser known API — maybe a local newspaper api. So what you’ll do? Jump on packagist and look for some libraries that would wrap those APIs, and you’ll find some — nice. so you’ll require them both in your project, but OH NO! Both of those packages depend on different versions of Guzzle so you get something like this pretty error message:
Naturally, you’ll try to force the resolution of one of the required versions to match the other one. Easy right?
But sadly, it won’t work because those were two major versions and there have been a breaking change in between them on which the libraries depend, so now one of them doesn’t work properly.
Sooner or later you will arrive at this point and you’ll ask yourself, is there something i can do to fix this? Could have the authors of these libraries do a better job designing them? Well, maybe… What we really want to do is what people have been already doing when designing a system, but for some reason it’s mostly overlooked in libraries. We want to decouple the dependencies from what the code does. Ideally make the dependencies implementation easily swappable via an adapter.
Let’s look at the news API library. It will live in a package called local-news/local-news. Instead of Guzzle, we would want to depend on some ClientInterface that would ship with the library. So, now we don’t depend directly on Guzzle, but now we don’t have any real implementation of our ClientInterface. Which means that our libraries don’t do jack. So now we have to solve the problem of providing an implementation of the ClientInterface for each of our libraries.
So what can we do?
Basic implementation in the package
If feasible, we could have a basic, no dependencies required, implementation of ClientInterface in the local-news/local-news package. If we do this, we don’t have to provide any adapters as the users can create these themselves if needed. This could be an option here as we are doing only GET requests to the API, so just CURL could suffice. However, this is not doable most of the time.
Adapter inside the package
You could technically have the adapters inside the main package, and the required dependencies for them in “suggested” part of composer.json, but this just introduces another point of failure since the package doesn’t communicate it’s dependencies properly.
Separate package with adapters
We can have only the interfaces for the dependencies in our package and then have a separate package with the adapters that would depend on this one. So in our case, we would have only the ClientInterface without the implementation in one package, let’s say local-news/local-news-no-deps. This package will have no outside dependencies (maybe PHP version, or extensions). Then we’ll have second package with the familiar name local-news/local-news, that’ll depend on local-news/local-news-no-deps and guzzlehttp/guzzle and will implement the ClientInterface.
Is it worth it?
In short, yes. Design like this is already used in packages that need to provide multiple implementations of 1 interface. For example Flysystem or Omnipay. The added work you put in designing the package provides much needed flexibility for the users. You will have to release 2 packages, but for the users nothing will change, except when they’ll run into unresolvable version conflicts, they’ll be able to use the underlying package that has no dependencies. Yes, this will require work on their part to provide implementation of the interfaces your library uses, but is still preferable to not being able to use the package at all.