We all love Composer. It changed dramatically the way we build PHP applications, based on small and reusable components, but this creates new challenges, especially when we have a single point of failure (SPO). With Satis, the deployment process can be made robust by adding redundancy in all potential SPOFs (Packagist and GitHub). Let’s see how it works.

How does Composer work?

The following graph shows how Composer works when using Packagist as a central repository:

satis.001

Unless we explicitly indicate that we want to use a different repository, Composer makes use of the default repository: Packagist. Composer will ask Packagist for information about all packages required in the composer.json file, as well as dependencies required by those packages. When Composer gets all that information, it solves the dependency graph using a SAT solver and generates the composer.lock file with the specific packages that must be installed in order to fulfil the requirements defined in the composer.json file. Finally, Composer downloads those packages from different sources: GitHub, Bitbucket, PEAR or any GIT/SVN/Mercurial repository.

This architecture has a few problems: what happens if Packagist is down? It won’t work. Composer won’t be able to solve the dependency graph. In addition to this problem, what happens if the required packages are hosted in GitHub, and it’s currently down? It won’t work either. Packages won’t be downloaded, affecting your development and deployments.

How can we minimize all these issues? By adding redundancy. We can create our own repository with Satis to use this repository instead (or in addition to) Packagist. The following graph shows the same schema but using a custom repository:

satis.002

Now, Composer asks our custom repository for information about the required packages and only in case that our repository does not have that information, it asks Packagist. We can even disable Packagist and configure our repository to download all the dependencies of the packages as we’ll see later. In addition, our repository can download the packages and act as a reverse proxy. That way, we no longer depend on GitHub to download packages.

Installation

We can install Satis using Composer:

$ composer create-project composer/satis --stability=dev

Repository configuration file

Let’s pretend that we are a company with a few developers and all our projects only need two dependencies: the Symfony HttpFoundation component and Twig. We want to have a custom repository with all versions (excluding development versions) of those two packages so we don’t have to rely on GitHub:

{
    "name": "ServerGrove repository",
    "homepage": "http://149.5.47.251:8001",
    "repositories": [
        {
            "type": "vcs",
            "url": "git@github.com:symfony/HttpFoundation.git"
        },
        {
            "type": "vcs",
            "url": "git@github.com:twigphp/Twig.git"
        }
    ],
    "require-all": true,
    "require-dependencies": true,
    "archive": {
        "directory": "dist",
        "format": "tar",
        "skip-dev": true
    }
}

The format of the file is quite straightforward, but let’s see what each of the fields mean:

  • “name”: the name of the repository.
  • “homepage”: URL of the repository.
  • “repositories”: contains the list of repositories we want to mirror.
  • “require-all”: download all packages, not only tagged ones.
  • “require-dependencies”: when “true”, Satis automatically resolves and adds all dependencies, so Composer won’t need to use Packagist.
  • “archive”: “tar” files will be stored in the “dist” directory and we won’t download “dev” packages.

Then, we tell Satis to build the repository:

$ ./bin/satis build satis.json servergrove-satis/

After this, we have a new directory called servergrove-satis, which contains two files: packages.json and index.html. The former is the repository configuration file, the one that will be read by Composer to determine what packages the repository offers. The index.html file is just a static html file with information about the repository. It also contains the dist directory with all packages so they won’t have to be downloaded from GitHub anymore.

And finally, we publish our repository using the PHP built-in server (for real repositories it is recommended to use Apache or nginx):

$ php -S localhost:8001 -t servergrove-satis/

Now, if we go to http://localhost:8001/, we should see something like this:

Screen Shot 2015-04-29 at 13.35.51

Using the repository in Composer

Once we have the repository up and running, the last step is to tell Composer that we want to use it. This can be done by adding the following lines to the composer.json file of your project.

{
    "repositories" : [
        {"type" : "composer", "url" : "http://localhost:8001/"}
    ],
    ...
}

Then, when executing composer install, our custom repository should be used:

composer install
Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing symfony/http-foundation (v2.6.6)
    Downloading: 100%

Writing lock file
Generating autoload files

We can see in the composer.lock file that download URLs are no longer from GitHub, but from our own repository:

{
    ...
    "packages": [
        {
            "name": "symfony/http-foundation",
            "version": "v2.6.6",
            "target-dir": "Symfony/Component/HttpFoundation",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/HttpFoundation.git",
                "reference": "8a6337233f08f7520de97f4ffd6f00e947d892f9"
            },
            "dist": {
                "type": "tar",
                "url": "http://localhost:8001/dist/symfony-http-foundation-8a6337233f08f7520de97f4ffd6f00e947d892f9-zip-3c2665.tar",
                "reference": "8a6337233f08f7520de97f4ffd6f00e947d892f9",
                "shasum": "04c8f9c294585c8fc982762f5181eab0083fdf3a"
            }
        }
    ...
}

Also, we can see the requests that Composer did in order to get the repository first (packages.json includes /include/all$e0f2af5bfe26328fcc97241cbd95de682f4fbfaa.json) and then the package itself:

[Wed Apr 29 13:38:56 2015] x.x.x.x:64826 [200]: /packages.json
[Wed Apr 29 13:38:56 2015] x.x.x.x:64827 [200]: /include/all$e0f2af5bfe26328fcc97241cbd95de682f4fbfaa.json
[Wed Apr 29 13:39:13 2015] x.x.x.x:64839 [200]: /dist/symfony-http-foundation-8a6337233f08f7520de97f4ffd6f00e947d892f9-zip-3c2665.tar

More info