Composer: How to use Git repositories

It’s not uncommon to find ourselves wanting to use composer to include Git repositories within our PHP project. In many cases repositories have been created on packagist.org and requiring them with composer is very straight forward. But what do we do when a repository has not been created as a package on packagist? The answer is that we use composer to require the package directly from the repository.

Note: Some of the terminology in the post is confusing because multiple words are used to describe different things. Here is a quick vocabulary list that will help:

  • Project – The custom software we are building. This can be a website, a command line utility, application, or anything else we dream up.
  • Package – Any 3rd party software we want to download and use within our project. It can be a library, Drupal theme, WordPress plugin, or any other number of things.
  • Git repository – AKA, Git repo. The version control host for a package. Common hosts are: GitHub, GitLab, or Bitbucket; but any URL accessible Git repository will work for this tutorial.
  • Composer repositories – In a composer.json file there is an optional property named “repositories“. This property is where we can define new places for Composer to look when downloading packages.

When adding a Git repo to our project with composer there are two situations we can find ourselves in: the repo contains a composer.json file and it defines how the repo should be handled when required, or it does not. Regardless, in both cases we are able to add the Git repository to our project.

Let’s start with a Git repo that has defined a composer.json file.

Git repo with composer.json

When a repository includes a composer.json file, it defines aspects of itself that are important to how composer manages the package. Here is an example of a simple composer.json file a package may include.

{
    "name": "daggerhart/my-custom-library",
    "type": "library"
}
composer.json

Shown are two important properties that a composer.json file can define:

  • name: The package’s namespaced name. In this case daggerhart is the namespace for the package my-custom-library.
  • type: The type of package the repo represents. Package types are used for installation logic. Out of the box, composer allows for the following package types: library, project, metapackage, composer-plugin.

You can verify this by looking at the composer.json file of any popular project. They each define their package name and the package type near the top of the file.

When a repository has this information defined in its composer.json file, requiring the repository within our project is relatively straight-foward.

Require a Git repository that has a composer.json file

Now that we’ve identified a GIt repository with a composer.json file, let’s require that repository as a package within our project.

Within our project’s composer.json file we need to define a new property (assuming it doesn’t exist already) named “repositories“. The value of the repositories property is an array of objects. Each object containing information about the repository we want to include in our project. Consider this composer.json file for our custom project.

{
  "name":  "daggerhart/my-project-that-uses-composer",
  "repositories": [
    {
      "type": "vcs",
      "url": "https://github.com/daggerhart/my-custom-library.git"
    }
  ],
  "require": {
    "daggerhart/my-custom-library": "dev-master"
  }
}
composer.json

We are doing two important things here. First, we’re defining a new repository that composer can reference when requiring packages. And second, we’re requiring the package from the newly defined repository.

Note: The package version is a part of the require statement, and not a part of the repository property. We can require a specific branch of a repo by choosing a version named “dev-<branch name>“.

If we were to run composer install in the context of this file, composer would look for a project at the defined URL, and if that URL represents a Git repo that contains a composer.json file that defines its name and type, composer will download that package to our project and place it in the appropriate location.

Custom Package Types

The example package shown above is of the type “library“, but often in our work with WordPress and Drupal we’re dealing with plugins, modules, and themes. When requiring special package types in our project it’s important for them to be installed in specific locations within the project file structure. Wouldn’t it be nice if we could convince composer to treat these types of packages as special cases? Well we’re in luck. There is an official composer-plugin that will do that for us.

composer/installers – The installers composer plugin contains the custom logic required for handling many different package types for a large variety of projects. It is extremely helpful when working with projects that have well known and supported package installation steps.

This project allows us to define package types like: drupal-theme, drupal-module, wordpress-plugin, wordpress-theme, and many more, for a variety of projects. In the case of a drupal-theme package, the composer/installers plugin will place the required repo within the /themes/contrib folder of our Drupal installation.

Here is an example of a composer.json file that might live within a Drupal theme project as its own Git repository:

{
    "name": "daggerhart/whatever-i-call-my-theme",
    "type": "drupal-theme",
    "description": "Drupal 8 theme",
    "license": "GPL-2.0+"
}
composer.json

Note that the only important difference here is that the “type” is now “drupal-theme“. With the drupal-theme type defined, any project that uses the composer/installers plugin can easily require our repo in their Drupal project and it will be treated as a contributed theme.

Require Any Git Repository with Composer

Now that we know how to require a Git repo that contains a composer.json file, let’s consider the case where the repo we want to include in our project does not define anything about itself with a composer.json file.

The short of it is, when a repo does not define its name or type, we have to define that information for the repo within our project’s composer.json file. Take a look at this example:

{
  "name": "daggerhart/my-project-that-uses-composer",
  "repositories": [
    {
      "type": "package",
      "package": {
        "name": "daggerhart/my-custom-theme",
        "version": "1.2.3",
        "type": "drupal-theme",
        "source": {
          "url": "https://github.com/daggerhart/my-custom-theme.git",
          "type": "git",
          "reference": "master"
        }
      }
    }
  ],
  "require": {
    "daggerhart/my-custom-theme": "^1",
    "composer/installers": "^1"
  }
}
composer.json

Notice that our repository type is now “package“. That is where we are going to define everything about the package we want to require. Next, we create a new object named “package” where we define all the important information that composer needs to know to be able to include this arbitrary git repo within our project. Including:

  • name – The namespaced package name. It should probably match the repository we’re requiring, but doesn’t have to.
  • type – The type of package. How we want composer to treat this repository.
  • version – A made up version number for the repo.
  • source – An object that contains the following repository information:
    • url –  The Git (or other VCS) URL where the package repo can be found.
    • type – The VCS type for the package. git, svn, cvs (?)… etc.
    • reference – The branch or tag we want to download.

I recommend reviewing the official documentation on composer package repositories. Note that it is possible to include zip files as composer packages as well.

Essentially, we are now responsible for all parts of how composer treats this repository. Since the repository itself is not providing composer with any information, we are responsible for determining almost everything, including the current version number for the package.

This approach allows us to include almost anything as a composer package in our project, but it has some drawbacks. Notable drawbacks:

  • Composer will not update the package unless you change the version field.
  • Composer will not update the commit references, so if you use master as reference you will have to delete the package to force an update, and will have to deal with an unstable lock file.

Custom Package Versions

Maintaining the package version in our composer.json file isn’t always necessary. Composer is smart enough to look for GitHub releases and use them as the package versions. But eventually we will find ourselves wanting to include a simple project that only has a few branches and no official releases.

When a repository does not have releases, we will be responsible for deciding what version the repository branch represents to our project. In other words, if we want composer to update the package, we will need to increment the “version” defined in our project’s composer.json file before running composer update.

Overriding a Git Repository’s composer.json

An interesting thing we can do when defining a new composer repository of the type package, is override a package’s own composer.json definitions. Consider a Git repository defines itself as a library in its composer.json, but we know that the code is actually a drupal-theme. We can use this above approach to include the Git repository within our project as a drupal-theme, allowing composer to treat the code appropriately when required.

Example: require Guzzle as a drupal-theme just to prove that we can.

{
  "name": "daggerhart/my-project-that-uses-composer",
  "repositories": [
    {
      "type": "package",
      "package": {
        "name": "daggerhart/guzzle-theme",
        "version": "1.2.3",
        "type": "drupal-theme",
        "source": {
          "url": "https://github.com/guzzle/guzzle.git",
          "type": "git",
          "reference": "master"
        }
      }
    }
  ],
  "require": {
    "daggerhart/guzzle-theme": "^1",
    "composer/installers": "^1"
  }
}
composer.json

This works! This will download the Guzzle library and place it within the /themes folder of my Drupal project. Obviously this is not a very practical example, but hopefully it highlights how much control the packge type approach provides.

Summary

Composer offers us plenty of options for including arbitrary packages within our project. The question of how those packages are included in the project primarily comes down to “who is definining the package information?“. If the Git repository includes a composer.json file that defines its name and type, then we can have composer rely on the repository itself for the definition. But if we want to include a repository that does not define its name and type, then it is up to our project to define and maintain that information for our own internal use.

Alternatively, if a repository doesn’t define a composer.json file, consider submitting a pull request that adds it. ?

References:

4 Thoughts

Discussion

Čamo
April 15, 2020

Great! Thanks a lot. Goes to my bookmarks.

Andrew Musholt
January 22, 2022

This very helpful! Thank you.

Nate Angell
February 24, 2023

Thank you so much for this guide! It helped me out with an issue I was having including a third-party WordPress plugin as a dependency, documented here: https://discourse.roots.io/t/private-or-commercial-wordpress-plugins-as-composer-dependencies/13247/25?u=xolotl

TomSaw
June 24, 2023

Thank you so much! I find composer docs very hard to read but your explanation fitted my neurons.

Leave a Reply

Your email address will not be published. Required fields are marked *