Thinking in packages
6th Jun '18 • 5 of your Earth minutes
Thinking in packages
If you’re in the mindset of writing and using your own Composer packages, no doubt you’ve come across the following problem:
“How do I keep the (frequently updated) package code in the main application up to date in development without having to run
composer update
all of the time?”
Actually, it’s even more involved than that. Why?
In a traditional Composer setup, packages are installed by copying the contents of the source code repositories into your application, checking them out under /vendor
. Any updates are usually handled by running composer update
which determines the latest version that can work with your other dependencies and, if it’s newer than what you have, pulling that new version in.
If we were to use this approach for our packages, we have to do something like this every time we make a change that we want to test with an application that uses that package:
- Commit package changes (& merge if they’re on a branch & tag if we’re not using
dev-master
) - Push changes to the central repo (e.g. GitHub)
- Wait for the Composer server (Packagist etc) to update
- Run
composer update
in the main app to get the latest package version
If you’ve ever done this, you’ll know that most of these steps can take anywhere between a few seconds to many minutes to complete. This adds up quickly and gets very tired when you’re making a lot of breaking changes in a rapidly evolving application.
Plus, when your main application and its dependencies are being developed on the same machine, it just feels really redundant to go through this whole flow every time you want to test a code change.
Perhaps we’re able to save some of this time? Yep, we sure can.
One sure-fire way to do it is by not separating functionality out into a package in the first place, just bundle it into your application and extract the package later.
But sometimes that’s just not possible. Thanks to Composer, there are a couple of other ways of making the development process simpler whilst still working in packages:
NB: Before you can do anything here, make sure your packages have at least a valid composer.json
file in their root.
Path repository
This method is the quickest to get going with because you don’t need to version control your package or push it to a central repo (even though you should); you can just start using your package as a dependency straight away.
Simply add it as a path
repository in your application’s composer.json
:
1"require": {2 "super-dev/module-package-1": "dev-master"3},4"repositories": [5 {6 "type": "path",7 "url": "../relative/path/to/module-package-1"8 }9]
Then run composer install
. You should see the package gets symlinked. This means you don’t need to keep running composer update
every time you make a change in your package.
This is ok for a while, but as soon as you want to deploy to staging or production, you’ll need to change this, either by changing the path repository into a vcs
repository (or composer
repository for Private Packagist or Satis) or removing it altogether. (Composer doesn’t allow falling back on different sources for a package, for good reason.)
One problem with this is that path
repository info is stored in your composer.lock
file too, which means this can’t really be shared well with others.
Basically, I wouldn’t recommend committing the path
repository stuff to your application’s repo as it’s probably not very useful outside of the context of your computer. If you do want to keep it around, make sure it’s on a local-only branch.
This leads me to my preferred approach…
Override PSR-4 Autoloading
It goes a bit like this:
- Get the package set up in version control
require
it in your application- Then override the autoloading of its classes in your application’s
composer.json
Here’s how:
1"require": {2 "super-dev/module-package-1": "dev-master"3},4"autoload-dev": {5 "psr-4": {6 "SuperDev\\ModulePackageOne\\": "../modules/module-package-1/src/"7 }8}
This is a bit more dirty than the previous option, it requires a bit more set up (i.e. making sure your package is using PSR-4 autoloading) and it won’t work for everything.
One thing you’ll notice right away is, locally, you’ll get a bunch of warnings from Composer about conflicting classes, which will grow and grow as your package and class numbers increase. There’s no need to worry about these, but it can be a bit jarring at first. If you prefer not to see these, use the quiet flag (-q | —-quiet
) to suppress them.
Also, you might still want to keep this local-only, essentially removing the extra autoload declaration code when sharing or deploying your app (although I have tried to mitigate this by putting it in autoload-dev
, which you shouldn’t be using in production anyway).
As for setup, the very least you’ll need is your package under a supported version control repository. Additionally, you may want it available via a Composer server — either Packagist, Private Packagist, or Satis — otherwise, just refer to it using a vcs
repo.
Then when you require
it, whatever is in version control will be what ends up in the/vendor
folder. But when Composer generates the autoloader, it will use your development version of the package instead!
Of course, this only works for files that can be loaded via a PSR-4 autoloader, so it may not be suitable in all situations.
But if your modules are primarily PHP classes, then it’s ok and you might find this easier to work with as it doesn’t change the composer.lock
file.
One final tip: I find it handy (when saving this change in a local-only branch) to have a githook that runs composer dump-autoload
when I switch between branches. This makes sure that when I switch to my dev
branch, the app is autoloading the development copy of my packages instead of the vendor
folder versions.
That’s it. Hope you find this useful :)