Thomas Gossmann

Unicyclist. Artist. Developer.

Composer Development with local Dependencies

TL;DR: If you want to do composer development with local dependencies and do not want to roundtrip packagist you might checkout my plugin composer-localdev-plugin and see if it suits you.

Composer has undoubtly become the de facto packaging standard for php applications. IDEs support this and make development even more beneficial. You need some behavior for your development? Just use the provided UI or use the still fashioned terminal to download the desired dependency and composer will care about the rest and everything is up and running smoothly. What if you are developing this dependency on your own and the dependency is just a directory away? Actually, developing with local developments is kinda terrible, hence there are plenty issues open. This post lists some workarounds and a conclusion what needs to be changed in the composer-core to better suit this environment.

The Pitfalls of Local Dependencies

There are some pitfalls you come across, when developing with local dependencies and the first one basically nails it pretty hard: Composer doesn’t know about local dependencies. You are basically forced to roundtrip packagist or some other remote endpoint to fetch information about packages and install them from there. The default development workflow looks like the following:

  1. Develop your local dependency
  2. Publish them online (e.g. packagist)
  3. Wait for caching to finish until your package is consumable (~10 minutes for packagist)
  4. Install the version you published

Updating the local dependency is a journey on its own:

  1. Develop your code
  2. Publish it (This mostly means pushing code to Github)
  3. Create a new version number (optional)
  4. Wait for caching to finish until your new version number is available (depends on #3)
  5. Update your consuming package

Every step after #1 is too much (Step #5 is only required if your local dependency changed its own dependencies). This is way too much overhead for a dependency that is just a folder away. Some workarounds arised which improve the situation yet are unable to smooth the situation completely because they are limited in their capabilites.

Workaround #1: Symlinking

This is an approach by myself for a limited set of packages. I needed to create a custom installer for my package types and I realized for local development I could set a directory in the extra part of composer.json to find these packages and then symlink the origin to the desired install location. This works as expected you may find the code at keeko/composer-installer. A more general and similar approach is available through piwi/composer-symlinker and Letudiant/composer-shared-package-plugin.

The benefits:

  • No more roundtrip to remote endpoints when some of the code changes.
  • No need to publish new versions.

Problems (some still persists, some are new):

  • composer.json from the local dependency is still fetched from the remote location (= packagist, etc).
  • When the local dependency changed its own dependencies, these must be pushed to the remote location and re-fetched from there by the consuming package to install these new dependencies as well.
  • The consuming package needs to define the local folder dependency structure. These information are pushed with composer.json. This is local stuff, that must not leave this scope. Although this works if you are working alone, with team effort this forces every team member the same directory structure.

Workaround #2: Local Repositories

I must admit I haven’t tried this out on my own (please take this information with this special grain of salt), just found this in the composer issues: #2171.

The benefits:

  • composer.json of local dependencies is fetched locally and would install new dependencies on the consuming package as well.

Problems (some still persists, some are new):

  • Require a composer update on the consuming package for every change in the local dependency.
  • The consuming package needs to define the local folder dependency structure. These information are pushed with composer.json. This is local stuff, that must not leave this scope. Although this works if you are working alone, with team effort this forces every team member the same directory structure.

Brave new Local Development

Simply spoken: „Hey composer, here are my local packages, whenever I try to install one of them in a development version, point them there“. Your local environment belongs to the developer and is very much different across developers. Every developer needs to describe its environment to its own composer installation. Luckily there is a global ~/composer.json that already manages global package installs. This can be used to tell composer about local packages. Something like this:

// ~/composer.json
{
  ..
  "extra": {
    "packages": {
      "": "path/to/packages", // global location
      "<vendor>": "path/to/<vendor>",
      "<vendor/pkg>": "this/is/somewhere/else"
    }
  }
  ..
}

Whenever composer runs an update or install command, the first source to check for packages are those defined above. If they can’t be find locally, proceed with the current search progress. composer.json from local dependencies can be fetched from local sources and changed dependencies can be applied on either install or update. Locally defined packages are always symlinked to their desired location (that given, custom installers still work as expected), almost eliminating the need for composer update on code changes from local dependencies.

The benefits:

  • No roundtrip for local dependencies to remote endpoints
  • Code of local dependencies is available in consuming packages immediately
  • composer.json of packages isn’t cluttered with local directory information
  • Works across teams

All of this has been put into an experimental composer-localdev-plugin with the intention to contribute this into composer itself.

Update 7th of May 2015: This is now an issue on composer@github.