Proxify Composer for PHP

Every PHP developer who use modern libraries or frameworks should know Composer. Composer is a dependency manager for PHP which allows you to declare the dependent libraries your project needs and it will install them in your project for you.

Unlike system like PEAR, Composer is only "project-aware". This means your dependencies must be installed for each of your project. This is essential because your projects can depend on different version of a same library. In the other hand it can be quite painful to download or git-clone many times all the vendors from a (far far away) remote server. This is especially true when you:

  • are working on many projects at the same time
  • regulary rm -rf vendor/ to be sure to remove the old dependencies (or because Composer just got a bug :))
  • work on a project where everyone is using the same local network (that's true when you work in a company)

Since Composer approaches the date of the first stable version, the second argument is becoming obsolete. Nevertheless, I want to focus on the last point.

When you use a development version of a framework or library, you generally want to get it from the sources. For instance, if you want to start a new project with the 2.1 version of the Symfony fullstack framework (for now, 2.1.* is an alias for dev-master), you have to git-clone the sources from GitHub. From an European network, it takes quite long to fetch a repository hosted on a US server:

$ time git clone git://github.com/symfony/symfony.git
Cloning into symfony...
remote: Counting objects: 140968, done.
remote: Compressing objects: 100% (43619/43619), done.
remote: Total 140968 (delta 89494), reused 132229 (delta 81990)
Receiving objects: 100% (140968/140968), 20.97 MiB | 198 KiB/s, done.
Resolving deltas: 100% (89494/89494), done.

real    2m15.30s
user    0m1.47s
sys     0m3.00s

More than 2 minutes is necessary to clone our main vendor (hopefully we don't use SVN anymore). In addition to other libraries like Doctrine or Twig, the installation of all vendors could easily take 5 minutes or more. The objective of this article would be to find a solution to install the same vendors, in less than 15 seconds.

This would be really usefull to save time and bandwidth not only for the developers, but also for CI systems (Jenkins/Travis), or PaaS solution like PagodaBox or Heroku which run a composer install each time you deploy a new code.

As Git is a decentralized system, it is relativly easy to create a proxy --- ie. a local clone of the repository --- which everyone could use instead of the original one.

Using Gitpod

Gitpod is a local caching server for Git. Its operation is very simple: once you've cloned a repository, each time you git-fetch it, it will git-fetch the original repository first. Thus, the first time will be as long as you if you git-fetched the original repository, while the second will be instantaneous.

The following example shows how I set up Gitpod to work with Composer.

According to the documentation, you first need to create a gp user on your gp-server (it could be your localhost, or a different server on your local network), and copy or clone the binaries into its home directory:

# useradd gp
# git clone git://github.com/sitaramc/gitpod.git /home/gp/bin

Then change its default shell:

# chsh gp -s /home/gp/bin/gitpod

Now you can proxify any git repository from your shell by simply running:

$ ssh gp@gp-server clone URL reponame

The result with the Symfony repository:

$ ssh gp@gp-server clone git://github.com/symfony/symfony.git symfony
running: git clone --progress --mirror git://github.com/symfony/symfony.git
Cloning into bare repository symfony.git...
...skipping cloning command...

$ time git clone git://gp@gp-server/symfony.git
Cloning into symfony-new...
fetching from git://github.com/symfony/symfony.git
remote: Counting objects: 141630, done.
remote: Compressing objects: 100% (41715/41715), done.
remote: Total 141630 (delta 89896), reused 135418 (delta 84468)
Receiving objects: 100% (141630/141630), 21.11 MiB | 14.19 MiB/s, done.
Resolving deltas: 100% (89896/89896), done.

real    0m9.090s
user    0m2.152s
sys     0m1.776s

It takes less than 10 seconds to clone the Symfony sources into my local computer (once I previously cloned it in Gitpod).

To use this git repository with into your project, just add the corresponding repository section into your composer.json:

{
    "require": {
        "symfony/symfony": "dev-master"
    },
    "repositories": [
        {
            "type": "vcs",
            "url":  "git://gp@gp-server/symfony.git"
        }
    ]
}

Now the php composer.phar install command will directly clone your local repository instead of fetching GitHub.

Not to end up with lots of unmeaningful repositories in your composer.json, you can use Satis to reference all of your local proxies. Satis is usefull when you want to declare private repositories for your project, but in this case you can use it to overload any public repository with your proxy. Then you only have to add the Satis URI in your composer.json:

{
    "repositories": [
        {
            "type": "composer",
            "url": "http://packages.example.org/"
        }
    ],
    "require": {
        "symfony/symfony": "2.1.*"
    }
}

Using Broker

I recently discovered Broker, a PHP application which creates a full repository proxy for Composer.

Like the previous method, you have to initialize your proxy with the desired vendors before running a composer install in your project. However Broker has the advantage to directly take your composer.json as the reference, and download all your vendors as Composer do. So it not only works for git repositories, but also with archives (and everything Composer can manage). Finally, it operates like Packagist or Satis, by providing a packages.json with just the libraries you need.

To make it work, the README is really clear:

$ git clone git://github.com/researchgate/broker.git
$ cd broker
$ curl -s https://getcomposer.org/installer | php
$ php composer.phar install
$ php broker.php broker:add project_name path/to/project/composer.json

This will download all your vendors into the repositories/project_name folder. Once you've set up your server to point on the root directory of Broker, you could had it to your composer.json file:

{
    "repositories": [
        {
            "type": "composer",
            "url": "http://broker-server/repositories/project_name"
        }
    ],
    "require": {
        "symfony/symfony": "2.1.*"
    }
}

I would recommend this method which is much easier than the previous one, and also much more powerfull as it is provided for Composer. However, keep in mind to update your proxy repositories first before running a composer update.

The missing option

Both of the previous methods work, but they have the same major incovenient: you have to modify the composer.json to use the proxy. In the case of a private project for your company it is not a real problem as every developer will use the company proxy. But it can be more problematic on an open source project as the file will no longer be shareable.

In my opinion, that would be a nice option for Composer to use a local proxy:

$ php composer.php install --proxy http://composer-proxy.local/

Or event use a local folder to download the vendors before copying them to your project:

$ php composer.php install --proxy /path/to/cache

In that case, Composer will explicitly tell the proxy what vendors to clone/download, and then it will download/copy the needed ones in your project. Thereby, every developer using the same proxy will save time and bandwidth, and even you, if you are working on several projects on your machine, will be able to download vendors only once.

What do you think about that?