Dependency patch resolution

Thank you to everyone who provided feedback on the development version of Composer Patches over the last few months.

One of the things that has come up frequently in your feedback is that removing the dependency patch resolution functionality is going to cause people some headaches. For those who aren’t familiar with it, this is the functionality that allows you to define patches in the composer.json of one of your dependencies and then have that patch applied as if it were defined in your root composer.json or patches.json. I’d like to take a moment to discuss why the functionality was removed and my plans for the future.

Support

Of the several hundred issues I closed earlier this year, many of them (perhaps even the majority of them) were related to the dependency patch resolution functionality in the 1.x versions. Many of the issues that came up were related to having the same patch defined twice (e.g. multiple project dependencies added the same patch; or a newly added dependency added a patch that was already included in the root composer.json). Directly supporting these issues was a time consuming endeavor for a while and then it got to the point where I just couldn’t keep up with them.

Additionally, all of the other methods that Composer Patches used to discover patches used data sources that were directly controlled by the user. Resolving patches from dependencies was the only mechanism that relied on data outside of the users’ direct control (for instance, if their project depended on some package that defined a patch, but they didn’t maintain that package). This led to some strange situations where a dependency required a patch that was already applied/committed upstream! A few mechanisms were added over the years to attempt to give the user more control over how patches were added, but it was a losing battle from the get-go.

Security

Arbitrary code execution is generally something we like to avoid, but Composer Patches allows a malicious package maintainer to do just that: somebody can apply a patch to any other package in your project, and the only indication that you’d have that something happened was a message in your Composer output.

Even worse, many projects didn’t have a direct need for patches to be applied! The only reason that Composer Patches was added to their project in the first place was that a dependency had a need for a patch to be applied (for instance, a Drupal module might have needed to apply a patch to Drupal core). In Composer 2, the steps required to enable the application of the patch made it seem like “official” Composer functionality (and therefore “safe”). This led to many misunderstandings over the years where people were assuming that they had just enabled some piece of Composer functionality, when in reality they were enabling a third party plugin which has the capability of making arbitrary changes to any other package in their project!

This is not a good situation for anyone involved.

Motivation

By all measures, Composer Patches is a reasonably successful open source project. It’s been installed over 42 million times and it’s approaching 43 thousand installs per day. It’s a standard part of most Drupal projects, and I’ve come across it in many other PHP communities as well (Magento, TYPO3, and Laravel are the big ones that I’m aware of). I’ve put it on my resume, used it as an example when talking about open source maintenance, etc.

The problem is that all of the firefighting and time spent debugging other peoples’ projects really took a toll on me personally. I was (and probably still am) burned out. For a while, I didn’t want anything to do with Composer Patches at all. That I was still the “owner” of the project was more of a burden than anything, and certainly not something that I felt particularly proud of. But even choosing to not work on the project came with its own issues: I felt an intense amount of guilt about neglecting the issue queue and the project in general over the years. Sure, the plugin was in a working state for most users, but there were a few persistent problems that simply didn’t get resolved in a timely manner (or in some cases, at all).

And the worst part? I didn’t want to add this functionality in the first place. The original version of Composer Patches was something that I wrote for work. Resolving patches from dependencies was a requirement of the project I was working on at the time. That was in 2014. I’ve rotated through several projects and employers since then. Lesson learned, I guess.

Moving forward

Dependency patch resolution has been removed from Composer Patches. I don’t intend to bring it back. I intend to keep the core plugin pretty lean, given the extensibility that it now offers. Removing that functionality allows me to focus on the most important parts of the plugin: the parts that discover and apply patches in a reliable, reproducible, and understandable way.

However, I also recognize that dependency patch resolution is a key part of many teams’ workflows. Composer Patches can be extended by other Composer plugins to provide this functionality again. Maybe some of the people who really need the functionality can band together and come up with a workable solution. If there’s some momentum behind such a plugin, I’d be glad to spend the time to document some of the edge cases and various workflows that would need to be handled and help answer questions about it.

2.0.0 update

Making the decision to drop this functionality was among the last major blockers for a 2.0.0 release. Another is making a decision on 1.x support. This will be detailed in the 2.0.0 release announcement, but the short version is that I’m going to do a couple of small, final updates to the 1.x branch prior to the 2.0.0 release, at which point 1.x will become effectively unsupported.

If you haven’t already done so, now is an extremely good time to test the beta release on your project and open any issues that you run into. I’ve tagged 2.0.0-beta1 for ease of testing. In the mean time, I’m doing some final passes through the plugin code and the documentation to make sure things are working in a way that I can commit to maintaining in the long term.