Handle monolithic Git repositories with Composer

tl;dr Use the »path« type in Composer to handle monolithic Git repositories.

Composer is able to fetch packages from various sources, as long as these packages are self-contained. Each package in a single VCS or ZIP-file.

This improves reusability and distribution of packaged code a lot. It is also required to enable smart dependency management.

However, I need to work with monolithic repositories every so often. Monolithic Git repositories contain several packages within one repository. They may be used due to historic reasons or because they offer some benefits. This approach is an obstacle to the whole concept of Composer obviously.

So while searching for a simple way to checkout only a part of a repository or make Composer use a subdirectory of a repository only, all threads will lead to a simple answer eventually: It is not possible.

It is not supported by Composer now and probably wont be supported ever.

But there is a workaround, which allows developers to handle monolithic Git repositories with Composer nonetheless.

Composer offers several drivers to handle different types of package repositories. One of these drivers is the »path«. This type expects an absolute or relative path to one ore more packages.

This is an example Git repository with some HTML templates and two PHP packages.

acme-repo
└ templates
    index.html
└ packages
  └ package-foo
      composer.json
      Classes/Hello.php
  └ package-bar
      composer.json
      Classes/Hello.php

An example project is supposed to use a regular package and the two dummy packages »package-foo« and »package-bar« of the acme repository.

www
└ acme-repo
  └ (subfolders same as above)
└ web
  └ vendor
  └ composer.json
  └ index.php

Change into the web folder to create a new project and load all dependecies.

If all packages are stored in the same folder level, then it is sufficient to add the repository path to this folder:

php composer.phar config repositories.baz '{"type": "path", "url": "../acme-repo/packages/*"}'

The »*« matches 0 or more of any character except a »/«. If the packages would be stored in diverging directories, or you need specific directories only, or you want to add even another repository then add the path to each single package:

php composer.phar config repositories.foo '{"type": "path", "url": "../acme-repo/packages/package-foo"}'
php composer.phar config repositories.bar '{"type": "path", "url": "../acme-repo/packages/package-bar"}'
php composer.phar config repositories.yar '{"type": "path", "url": "../yet-another-repo/packages/*"}'

Require the desired packages in the project:

php composer.phar require mfacenet/hello-world:1.0.* acme/package-foo:dev-master acme/package-bar:dev-master

Composer will then set a symlink to the package directories:

php composer.phar update
  Updating dependencies (including require-dev)
    - Installing mfacenet/hello-world (v1.0.3): Loading from cache
    - Installing acme/package-foo (dev-master): Symlinking from ../acme-repo/packages/package-foo
    - Installing acme/package-bar (dev-master): Symlinking from ../acme-repo/packages/package-bar

The final composer.json of the dummy project will look like this:

{
  "repositories": {
    "baz": {
      "type": "path",
      "url": "../acme-repo/packages/*"
    }
  },
  "require": {
    "mfacenet/hello-world": "1.0.3",
    "acme/package-foo": "dev-master",
    "acme/package-bar": "dev-master"
  }
}

If the composer.json of the packages don't contain a »version« property, and Composer cannot retrieve the version from Git meta information, then Composer sets »dev-master« as version fallback for these packages. Note that it is considered a bad practice to rely on »dev-master«, instead of specific versions.

Btw: It is not possible to add a composer.json in the root of the »acme-repo«, which sets the path for all subpackages and then inherits these settings to the project. This would be the most convenient way to handle such repositories, but Composer can't load repositories recursively.

Update: I created a simple Git repository to test this path feature:

Update 2: Another cunning way to handle packages of monolithic Git repositories is the »artifact« type.

Create a ZIP-file of each package and publish it somewhere. Then add the path to each folder containing any number of ZIP-files.

Example project structure:

www
└ package-folder-with-zip-files
  └ acme-package-foo-master.zip
  └ acme-package-bar-master.zip
└ web
  └ vendor
  └ composer.json
  └ index.php

composer.json of the dummy project:

{
  "repositories": {
    "baz": {
      "type": "artifact",
      "url": "../package-folder-with-zip-files/"
    }
  },
  "require": {
    "mfacenet/hello-world": "1.0.3",
    "acme/package-foo": "1.0.5",
    "acme/package-bar": "1.0.0"
  }
}

Composer will unzip the packages and import them into the vendor folder.

Be aware that »artifacts« require a »version« property in the composer.json of the packages. This is different to the »path« type, which has a fallback to »dev-master«. This disparate behaviour is known and wont be changed. Notice that it is strongly recommended to omnit this property in packages stored in VCS repositories, since Git tags are used as »version« then. But an »artifact« does not have this meta information given by the VCS and therefore can't gues the version and needs it explicitly in the composer.json file.

⌛ Warning! This post is old. The information may be outdated.

No comments on this notepad. If you would like to add something, then dont hesitate to contact me via E-Mail or Twitter.