Disclaimer

This article intends to document the research i have done in order to understand how composer works.  It is only my understanding of things, and may contain errors, or be otherwise inaccurate.

It is not a tutorial on how to use composer; the reader is expected to be familiar with composer and its use in general.

Use -vvv with composer to get verbose output that will aid understanding of composer workings.

Packages

A composer package is any hierarchy of files, that has at its root a file named composer.json.  This means that in any project, adding a composer.json at the root of the project makes it a composer-compatible package.

Packages are usually published on public servers so that they can be easily downloadable.  These days, projects that use git as their VCS are published on sites like github.  Github exposes an API that permits easy access to different versions of a project.

If a project on github has a composer.json in its root directory, it is ready to be used with composer.

Packagist.org

Packagist.org is the default composer repository.  It is a repository of type ‘composer’; this repository object is defined by default in your main project’s composer.json.  Thus, when installing packages using “composer install”, composer will search packagist.org for the required packages (other repositories may be specified, that will be searched in addition to packagist.org – repositories are consulted in the order mentioned in composer.json, i.e, if a package is found in a repository listed above, that one will be used).  It is also possible to disable packagist.org altogether in the composer.json.

When a package is submitted to packagist.org, a repository URL is required to be given.  In this discussion i assume packages are hosted on git repositories (commonly on github).

Packagist crawls that repository immediately, to find all tags and branches.  The name of a package version is taken from the name specified in the composer.json from the default branch, and this is not allowed to change.  From the tag/branch version number, repository URL and metadata from the package’s composer.json, packagist constructs a json object for each version found (similar to a composer.json), that describes that version of the package, and the location from where it can be downloaded (the dist and source).  This json object is stored in the packages.json of the repository, so that composer clients can query the information when doing installs and updates.  Packagist does this crawling job peridically, because of course packages are evolving, and it needs to keep the packages.json up-to-date.

For example, suppose the URL of the repository that you supply to packagist.org is a public git repo.  The repo contains the following branches and tags:

Branches:

  • master
  • 2.0
  • 1.1.x

Tags:

  • 1.0
  • 1.2
  • 2.1-beta

Packagist.org will pull out each branch and tag, and the composer.json therein.  It will then create a package version (the json object) for each of those branches and tags.  To create a package version that is distributable to composer clients, it needs a minimum of three things: the name of the package, the version of the package, and dist or source location (or both) for the package version.

The name of each package version is taken from the one specified in the default branch’s (normally master) composer.json.

The version of each package is constructed from the fact whether it originates from a branch or a tag.  For the branches above, the corresponding package versions would be:

Package versions originating from branches:

  • dev-master
  • 2.0.x-dev
  • 1.1.x-dev

Package versions originating from tags:

  • 1.0
  • 1.2
  • 2.1-beta

An important thing we see here is that, in the original composer.json we avoid specifying the version: we let packagist infer the version from the tag names and branch names.  If we maintained the version number by hand in composer.json, we would have to keep it up-to-date with the tag/branch, and this is error-prone.

For the location of where to download the package, packagist uses the url of the git VCS to generate source and dist links: if it is a github-like site, it will use the url of the public api to get zip/tar balls of branches and tags for dist, and for source, the url to checkout that VCS version.  If it is just a simple git repo (which does not provide an api like github), it will put enough information in the package version json object such that composer clients are able to clone/checkout that particular version via a git client (this would be an entry for source, but i do not know if it can put something for dist as well, i.e, given a git repo url, can we ask it to give us a zipball of a particular project version?).

The json object generated by packagist will also contain the dependencies of the package (as specified by the package authors in the original composer.json of that VCS version).  This information will be used in dependency resolution by composer clients.

Below is the composer.json for the monolog/monolog package from its project root dir:

{
   "name": "monolog/monolog",
   "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
   "keywords": ["log", "logging", "psr-3"],
   "homepage": "http://github.com/Seldaek/monolog",
   "type": "library",
   "license": "MIT",
   "authors": [{
     "name": "Jordi Boggiano",
     "email": "j.boggiano@seld.be",
     "homepage": "http://seld.be"
   }],
   "require": {
     "php": ">=5.3.0",
     "psr/log": "~1.0"
   },
   "require-dev": {
     "phpunit/phpunit": "~4.5",
     "graylog2/gelf-php": "~1.0",
     "sentry/sentry": "^0.13",
     "ruflin/elastica": ">=0.90 <3.0",
     "doctrine/couchdb": "~1.0@dev",
     "aws/aws-sdk-php": "^2.4.9 || ^3.0",
     "php-amqplib/php-amqplib": "~2.4",
     "swiftmailer/swiftmailer": "~5.3",
     "php-console/php-console": "^3.1.3",
     "phpunit/phpunit-mock-objects": "2.3.0",
     "jakub-onderka/php-parallel-lint": "0.9"
   },
   "_": "phpunit/phpunit-mock-objects required in 2.3.0 due to https://github.com/sebastianbergmann/phpunit-mock-objects/issues/223 - needs hhvm 3.8+ on travis",
   "suggest": {
     "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
     "sentry/sentry": "Allow sending log messages to a Sentry server",
     "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
     "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
     "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
     "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
     "ext-mongo": "Allow sending log messages to a MongoDB server",
     "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
     "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
     "rollbar/rollbar": "Allow sending log messages to Rollbar",
     "php-console/php-console": "Allow sending log messages to Google Chrome"
   },
   "autoload": {
     "psr-4": {
       "Monolog\\": "src/Monolog"
     }
   },
   "autoload-dev": {
     "psr-4": {
       "Monolog\\": "tests/Monolog"
     }
   },
   "provide": {
     "psr/log-implementation": "1.0.0"
   },
   "extra": {
     "branch-alias": {
       "dev-master": "2.0.x-dev"
     }
   },
   "scripts": {
     "test": ["parallel-lint . --exclude vendor", "phpunit"]
   }
}

This is the equivalent json generated in packages.json by packagist for version 1.22.0:

{
   "name": "monolog/monolog",
   "version": "1.22.0",
   "source": {
     "type": "git",
     "url": "https://github.com/Seldaek/monolog.git",
     "reference": "bad29cb8d18ab0315e6c477751418a82c850d558"
   },
   "dist": {
     "type": "zip",
     "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558",
     "reference": "bad29cb8d18ab0315e6c477751418a82c850d558",
     "shasum": ""
   },
   "require": {
     "php": ">=5.3.0",
     "psr/log": "~1.0"
   },
   "provide": {
     "psr/log-implementation": "1.0.0"
   },
   "require-dev": {
     "aws/aws-sdk-php": "^2.4.9 || ^3.0",
     "doctrine/couchdb": "~1.0@dev",
     "graylog2/gelf-php": "~1.0",
     "jakub-onderka/php-parallel-lint": "0.9",
     "php-amqplib/php-amqplib": "~2.4",
     "php-console/php-console": "^3.1.3",
     "phpunit/phpunit": "~4.5",
     "phpunit/phpunit-mock-objects": "2.3.0",
     "ruflin/elastica": ">=0.90 <3.0",
     "sentry/sentry": "^0.13",
     "swiftmailer/swiftmailer": "~5.3"
   },
   "suggest": {
     "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
     "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
     "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
     "ext-mongo": "Allow sending log messages to a MongoDB server",
     "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
     "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
     "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
     "php-console/php-console": "Allow sending log messages to Google Chrome",
     "rollbar/rollbar": "Allow sending log messages to Rollbar",
     "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
     "sentry/sentry": "Allow sending log messages to a Sentry server"
   },
   "type": "library",
   "extra": {
     "branch-alias": {
       "dev-master": "2.0.x-dev"
     }
   },
   "autoload": {
     "psr-4": {
       "Monolog\\": "src/Monolog"
     }
   },
   "notification-url": "https://packagist.org/downloads/",
   "license": ["MIT"],
   "authors": [{
     "name": "Jordi Boggiano",
     "email": "j.boggiano@seld.be",
     "homepage": "http://seld.be"
   }],
   "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
   "homepage": "http://github.com/Seldaek/monolog",
   "keywords": ["log", "logging", "psr-3"],
   "time": "2016-11-26T00:15:39+00:00"
}

VCS type repository

If in our composer.json we specify a repository of type vcs, and give a git repository url, our composer client will know that it needs to crawl that repo in order to get information about the available versions, so that it is able to download the version required by our project.  In other words, what packagist does as batch job crawling repositories, our composer client must do on the fly here (but of course for only those repos specified in the composer.json).  This is also shows us the utility of a central package source like packagist.org: it does the hard job of crawling VCS repos and compiles that information so that composer clients can just use it, and also avoids to list the VCS urls of each package that a project requires in the composer.json.

Advertisements