How do I release a PHP Composer package?

Composer is the standard package manager for PHP. Here’s how to release a package for it.

First let’s clarify what I’m doing. I have an example project called donut-news, and its composer.json looks like this:

{
    "name": "jameshfisher/donut-news",
    "description": "Donut news",
    "type": "project",
    "authors": [
        {
            "name": "Jim Fisher",
            "email": "jameshfisher@gmail.com"
        }
    ],
    "require": {
        "monolog/monolog": "^1.23"
    }
}

The require block defines the dependencies for jameshfisher/donut-news, and currently it has one dependency: monolog, a logging library. This dependency was added by running:

php composer.phar require monolog/monolog

Now donut-news has a lot of donut logic which would be better put in a generic donuts library. I want to create a new Composer library package called donut-logic, so that I can add it to my donut-news project by running

php composer.phar require jameshfisher/donut-logic

When you run php composer.phar require foo/bar, Composer looks for the package foo/bar in repositories. A repository is a store of Composer packages. The default Composer repository is https://packagist.org, and you can browse all the packages on Packagist.org here. To release jameshfisher/donut-logic, I need to get it into this list.

Composer package names are prefixed with a “vendor” name. In jameshfisher/donut-logic, the vendor is jameshfisher. On Packagist.org, vendor names correspond to accounts. I created an account on Packagist.org called jameshfisher.

Packages on Packagist.org are backed by git repositories. I created a new repository at https://github.com/jameshfisher/donut-logic.

Just like your PHP projects have a composer.json, Composer packages/libraries also have a composer.json. I created this composer.json for donut-logic, and added it to the root of the repository:

{
    "name": "jameshfisher/donut-logic",
    "description": "Shared logic related to donuts",
    "require": {}
}

Next, I visited https://packagist.org/packages/submit, which asks for the repository URL. I submitted it, and it registed a new Packagist.org package: https://packagist.org/packages/jameshfisher/donut-logic. However, this new package is not “stable”, so can’t be installed by default!:

$ php composer.phar require jameshfisher/donut-logic

  [InvalidArgumentException]
  Could not find package jameshfisher/donut-logic at any version for your m
  inimum-stability (stable). Check the package spelling or your minimum-sta
  bility

It turns out that “stable” means “has a git tag”. So I tagged my current version as 0.0.1:

$ git tag 0.0.1
$ git push --tags
Total 0 (delta 0), reused 0 (delta 0)
To github.com:jameshfisher/donut-logic.git
 * [new tag]         0.0.1 -> 0.0.1

This isn’t enough, because Packagist.org doesn’t know that I’ve updated the source git repository. I need to tell it that the repository has been updated:

curl \
  -X POST \
  -H 'Content-Type: application/json' \
  -d '{"repository":{"url":"https://github.com/jameshfisher/donut-logic"}}' \
  'https://packagist.org/api/update-package?username=jameshfisher&apiToken=MY_API_TOKEN'

(I got my Packagist.org API token at https://packagist.org/profile/.) Now at https://packagist.org/packages/jameshfisher/donut-logic I can see that there are two versions of my package: dev-master and 0.0.1. I can now require my package, and it gets version 0.0.1:

$ php composer.phar require jameshfisher/donut-logic
Using version ^0.0.1 for jameshfisher/donut-logic
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing jameshfisher/donut-logic (0.0.1): Downloading (100%)
Writing lock file
Generating autoload files

There’s a more reliable way to ensure that Packagist.org is up-to-date with the source git repo: GitHub Service Hooks. I went to https://github.com/jameshfisher/donut-logic/settings/hooks/new?service=packagist, and configured it with my username and Packagist.org API token. Now, when my repository is updated, GitHub will notify Packagist.org.

I wrote this because I'm learning this for work This post is not associated with my employer.