My PHP Forked Package Workflow
Packages are great - they bring specific functionality out of the box, without the need to re-invent it yourself. The downside is that each additional package installed into an application creates one extra dependency. Eventually, one of these dependencies will lag behind, potentially causing your project to grind to a halt.
My personal preference is to keep dependencies at a minimum, for several reasons, but that's a rant for another time.
Here are a few ways in which 3rd party packages can cause headaches:
- the package stops being maintained
- the author is slow to release new features or fix bugs
- the package isn't updated in a timely manner for new language or framework versions
When a dependency "breaks" for me, I typically do the following:
- fork the package
- implement a fix
- make a pull request to the original repository
- while waiting for the PR to be merged (not always guaranteed), I'll switch to using the fork
This will get me unstuck for the time being, but the ideal outcome is for my contribution to be accepted in the official package.
Quick primer on PHP package management
In PHP we use composer
to manage packages (install, remove, update, etc). Dependencies are defined in the composer.json
file.
To install a new package, we run the following command (actual example from one of my apps):
composer require codetoad/strava
This creates an entry in composer.json
like so:
...
"require": {
"php": "^8.0",
"ext-json": "*",
"codetoad/strava": "^1.0",
...
},
...
Note that the official repository for this package is located at https://github.com/RichieMcMullen/laravel-strava.
Forking the original package
Continuing on the previous example, I've been using the Laravel Strava package in one of my apps, and so far it's been great at pulling data from Strava's API. However, it does not have the ability to modify data.
I decided I wanted to update several parameters belonging to an Activity, such as name, description, and gear. Since I couldn't simply ask the author to implement this for me, I knew I had to do it myself.
The first step is to fork the package. In GitHub, simply click the Fork button in the top right corner, and it will create a copy of the repository under your own organization. For me, that would be https://github.com/breadthe/laravel-strava.
Clone the fork locally
Now I want to work on the fork locally, so I simply clone it with git clone https://github.com/breadthe/laravel-strava
, then implement the features I need.
Linking the app to the local fork
One of the cool things Composer lets you do is to link a package to a local directory. This can be done with a relative or absolute link.
I cloned the forked package in the following directory (Mac): /Users/myuser/code/packages/php/laravel-strava
.
The app that requires the package is in /Users/myuser/code/laravel/example-app
. Let's switch to it now, in the terminal. Then I'm going to remove the codetoad/strava
package using Composer.
cd /Users/myuser/code/laravel/example-app
composer remove codetoad/strava
Open up composer.json
and add the following top-level key:
"repositories": [
{
"type": "path",
"url": "/Users/myuser/code/packages/php/laravel-strava"
}
],
This tells Composer to install a package called laravel-strava
from the local path. Alternatively, you can use a relative path such as ../../packages/php/laravel-strava
.
Finally install the package with a @dev
constraint:
composer require codetoad/strava @dev
You should see this in composer.json
:
"require": {
"php": "^8.0",
"ext-json": "*",
"codetoad/strava": "@dev",
...
Now any changes made to the local fork will appear in the app.
Linking the app to the remote fork
Once local development on the fork is complete, I can push my changes to the remote fork. From there, I can create a pull request to the original package, and hope the changes will be accepted.
Meanwhile, there's no need to delay development, since I can simply link to my own fork.
To do this, once again remove the package:
composer remove codetoad/strava
Then modify the repositories
key in composer.json
like so:
"repositories": [
{
"type": "vcs",
"url": "https://github.com/breadthe/laravel-strava"
}
],
This tells Composer to install a package called laravel-strava
from the Version Control System location specified in the URL (instead of the default repo for codetoad/strava
).
Finally, install the package once more, with a dev-master
constraint:
composer require codetoad/strava dev-master
You should see this in composer.json
:
"require": {
"php": "^8.0",
"ext-json": "*",
"codetoad/strava": "dev-master",
...
And that's it. The app can now be deployed to a production server, and running composer install
will install the forked package.
PR merged, now what?
At the time of writing this, my PR hasn't yet been merged, but here's what I would do next:
- remove the package
composer remove codetoad/strava
- remove the
repositories
key fromcomposer.json
- (optional) delete the
vendor/
folder - run the original install command
composer require codetoad/strava
Conclusion
This is simpler than it sounds, but I had to document it for my own benefit. I don't deal with forks often enough to have it committed to muscle memory, so writing it down will definitely help when I need it again. I hope it will be useful to you as well, and if I missed anything or you find any mistakes, let me know on Twitter.