Announcing composer-patches 2.0.0

After nearly a decade of off-and-on development, I’m happy to announce the release of Composer Patches 2.0.0. If you’re not familiar, Composer Patches is a simple Composer plugin that allows you to apply patches to your project’s dependencies at install time. It’s become a standard part of the development workflow in many PHP communities (including Drupal, TYPO3, Magento, Yii, and more) and has been downloaded over 80 million times. What started as a quick work project back in 2014 has grown beyond anything I imagined, and today marks the biggest release yet.

Composer Patches 2.0.0 brings a host of improvements and new features aimed at making patch management more reliable, extensible, and integrated with Composer itself. In this post, I’ll walk through some of the highlights: a new patches lock file for reproducible builds, Git-powered patch application (no more relying on the patch tool), a “freeform” patcher that lets you apply patches with any command, a brand new documentation site, an extensible API for custom patch behaviors, better Composer integration, and more.

Patches lock file for integrity and reproducibility

One of the biggest additions in 2.0.0 is the introduction of a patches.lock.json file. This file is analogous to Composer’s own composer.lock: it records a snapshot of all patch definitions applied to your project. Whenever you run a Composer install/update with Composer Patches, the plugin will generate/update patches.lock.json to include each patch in an expanded, detailed format that includes a SHA-256 checksum of the patch file.

Why is this important? In a word: reproducibility. By locking the exact patches (and their checksums) that were applied, you ensure that everyone on your team and your CI systems are applying the same patches from the same sources. If a patch file changes upstream or gets corrupted, the checksum in your lock file will catch it.

Git-powered patching (goodbye, patch)

Another major change under the hood: Composer Patches now uses Git for all patch operations. In the 1.x release series, the plugin would attempt to apply patches using patch. That led to a lot of unpredictable behavior across different systems and environments (fun fact: there are around half a dozen different variants of patch that can be found in different places, and many of them accept different command line arguments and have different behaviors). In 2.0.0, I’ve simplified and standardized patch application by relying exclusively on git apply.

I can already hear some of you asking “What if a dependency isn’t a Git checkout?”. In cases where a package wasn’t cloned from Git, the plugin will git init in the dependency directory and then use git apply as usual. This trick lets us use Git’s patching functionality everywhere, ensuring consistent behavior.

The switch to Git brings a few benefits. Git’s patch apply mechanism tends to be more robust and consistent across platforms. It also means that we can leverage Git’s understanding of context and renames, resulting in fewer patch failures. In my testing, this change alone eliminated an entire class of patching headaches.

Extending Composer Patches via new APIs

Composer Patches 2.0.0 isn’t just more flexible for users – it’s also far more extensible for developers. In the past, the plugin’s behavior was essentially fixed: if it didn’t so something, you were out of luck (or had to submit a PR and wait). With 2.0.0, a lot of the plugin’s functionality can be extended or customized via new APIs.

Specifically, Composer Patches now defines several Composer capabilities that third-party plugins can implement. (Capabilities are Composer’s way of letting plugins provide additional behaviors by implementing certain interfaces). Composer Patches exposes capabilities for finding patches, downloading patches, and applying patches. This means that you can write your own Composer plugin that plugs into Composer Patches and provides, say, a new patch source or a different patch application strategy, and Composer Patches will use it. In addition, the plugin emits events at various stages (resolving patches, patch download, patch application, etc), allowing your plugin to hook in and perform actions or modify data during the entire patching process.

What does this enable? For one, it lays the groundwork for the community to add features without having them all included in the core plugin. For example, during the 2.x development cycle, I had removed the dependency patch resolution feature (don’t worry, I added it back in) to simplify maintenance. The idea was that it could be re-implemented as an external plugin using these new extension points. More generally, if you have a novel idea – like integrating a patches workflow with an external issue tracker, or auto-downloading patches from a custom repository – you can now do that by writing a small plugin that leverages the provided capabilities/events, instead of forking Composer Patches itself.

I’m excited to see what the community builds on top of this new API. If you build something interesting and open source it, let me know and we can talk about adding a page to the docs that lists third-party extensions.

Deeper Composer integration

We now play much more nicely with Composer’s own features and settings. A lot of work has gone into integrating the plugin’s operations with Composer’s core so that things “just work” in environments that were tricky before. Here are a couple of notable improvements:

  • HTTP proxy support: If you’re working behind a corporate proxy, you likely have environment variables like HTTP_PROXY set for Composer. In the past, Composer Patches might have bypassed those when downloading patch files (although support was present for that in later versions of 1.x). In 2.0.0, the plugin now reuses Composer’s built-in downloader – the same one that it uses to fetch package archives from remote sources. If your Composer project correctly installs packages behind your corporate proxy, it should just work when you’re trying to download and apply patches as well.
  • Secure HTTP enforcement: Composer has a config option secure-http that, when true, forbids downloading packages over plain HTTP. Composer Patches now honors this setting as well. By default, it will refuse to download patches from an http:// URL unless you explicitly allow insecure downloads (the same way that you would for Composer itself).
  • Respecting alternate Composer files: Some projects use the COMPOSER environment variable to specify a custom composer.json file (e.g. COMPOSER=composer-alt.json composer install). Composer Patches is now aware of this setting and will use a separate patches lock file accordingly. For instance, if you run with COMPOSER=composer-foo.json, the plugin will read/write composer-foo-patches.lock.json. This ensures that projects with multiple composer files (and potentially different patch sets) don’t conflict or overwrite each other’s patch locks.

Under the hood, a lot of these improvements come from the plugin leveraging Composer’s own facilities (like the ComposerDownloader and configuration values) instead of reinventing the wheel. The end result is that using Composer Patches feels more seamless.

Dependency patch resolution (back by popular demand)

If you’ve been following the 2.0.0 development, you might recall there was some mild drama around the removal of dependency patch resolution – the feature where a Composer package can specify patches in its own composer.json, which then get applied to other dependencies automatically. Initially, I planned to drop this feature entirely in 2.0 to simplify the core plugin (it was a source of many bugs and even security concerns in 1.x). The early 2.0.0 beta indeed removed it and I discussed possibly offloading that functionality to a separate plugin.

However, feedback from the community was loud and clear: many teams rely on dependency-defined patches in their workflows, and removing it was a bigger disruption than I was comfortable with. So I went back to the drawing board. I’m happy to report that dependency patch resolution is alive and well in Composer Patches 2.0.0. The new implementation works just like you’d expect and is (as far as I can tell) fully backwards-compatible with how 1.x behaved, but it’s been refactored to be more maintainable under the hood.

New documentation site

Because so much has changed in 2.0.0, I overhauled the documentation and moved it to a new site: docs.cweagans.net. The new documentation is much more comprehensive and easier to navigate than the old README-based docs.

I strongly encourage everyone upgrading to 2.0.0 to spend a few minutes browsing the docs, especially the “Defining Patches” and “Recommended Workflows” sections. There are subtle changes in how things work (for instance, the recommendation to use a separate patches.json file so that patch changes don’t churn your composer.lock).

If anything is unclear after reading the new documentation, please let me know. The documentation is a living resource and I’d prefer to improve the documentation when needed.

Upgrading from 1.x to 2.0.0

If you’ve been using Composer Patches 1.x on a complex project, you might be a bit nervous about upgrading. The good news is that most projects will be able to upgrade to 2.0.0 without much trouble. The plugin is designed to be largely backwards compatible in terms of how you define patches. Your existing patches definitions (whether in composer.json or patches.json) should continue to work in 2.0.0. The main difference is that the plugin will generate that patches.lock.json file we discussed before.

For the majority of cases, upgrading should be straightforward. I’ve tried to test 2.0.0 against a variety of projects and scenarios to ensure it works as a drop-in replacement for 1.x. In fact, several users have been running the 2.0.0 betas and reporting success.

That said, some complex projects might need a bit of tweaking to get it working properly. If you run into anything that you don’t know how to handle, please feel free to open a discussion. I’m happy to help work through any upgrade snags that you come across. Composer Patches 2.0.0 has been a long time coming and I want the transition to be as smooth as possible for everyone.

Community contributions and thanks

Considering how long this release took, I have to acknowledge that much of the work was done solo – for a long stretch, Composer Patches was basically my personal project to maintain. There were periods of burnout and slow progress (as some of you know from my frank blog posts on the subject). However, I’m very happy to say that recently there’s been a surge in community engagement and contributions.

Over the course of the 2.x development, I’ve gotten contributions from many community members. In fact, the beta releases alone included code from over 30 different contributors besides myself. This uptick in pull requests, testing, and feedback has been incredibly encouraging. To everyone who submitted a PR, reported a bug, helped test a beta, or even just chimed in on discussions: thank you. Composer Patches 2.0.0 is a better release because of your involvement.

Getting involved and supporting the project

As 2.0.0 usage increases, I welcome continued community involvement. There are a few ways you can help or get help:

  • Contribute to development: If you encounter issues or have ideas for improvements, feel free to open an issue or pull request on GitHub. There are open issues that could use attention. I’m more than happy to mentor new contributors – just comment on an issue and tag me if you’d like guidance, and I’ll help you get started.
  • Give feedback: Even if you’re not coding, testing the plugin in your projects and providing feedback is incredibly valuable. Let me know how 2.0.0 is working for you! If you run into any bugs or unexpected behaviors, report them and we’ll get them sorted out.
  • GitHub Sponsors: Maintaining open source software is a labor of love, but it’s also nice to get support. If Composer Patches saves you time or headaches, please consider sponsoring the project via GitHub Sponsors. Even small contributions help me dedicate time to maintain and improve the project.
  • Professional support: For organizations with very complex setups or mission-critical use of Composer Patches, I’m available for consulting/contract work. If you need dedicated help upgrading to 2.0.0 or tailoring the plugin to your workflow, you can reach out to me via email and we can work something out. Sometimes a short engagement can save a lot of time and ensure everything is running smoothly.

I’m incredibly excited to finally get Composer Patches 2.0.0 into your hands. This release represents a huge effort to modernize and future-proof the tool, and I believe it will make patch management in Composer projects more pleasant for everyone. Thank you for all your patience and support over the years. I look forward to hearing your experiences with the new release (and hopefully not too many bug reports 😉).