Using composer with unpublished packages

First of all “unpublished packages” refers to packages that are not published on public repositories (like the main one for composer – packagist.org) and are instead just in VCS repo, containing the composer.json file.

The reason to use this approach varies, be it you want to test and/or improve a package’s code before public release, or maybe you are using a private repository (so a private package therefore), and are not decided to use Private Packagist just yet. Anyway…

Working with your unpublished packages using composer to solve your app’s dependencies require some extra definitions in composer.json file.

Well, the rules are pretty straight forward – just add the repository to the “repositories” list in composer.json file, and then the package name to packages list (one of “require” or “require-dev” block). Just like this.

{
  "name": "my-org/my-app",
  "license": "proprietary",
  "repositories": [
    {
      "type": "github",
      "url": "https://github.com/alexbusu/tool1.git"
    }
  ],
  "require": {
    "alexbusu/tool1": "0.*",
  },
  "autoload": {
    "psr-4": {
      "MyOrg\\": "./src"
    }
  }
}

You are all set after a composer update.

Are you? 🙂

Well, the answer is, it depends. It depends on the package you require, if the later requires another package which is located in an another custom repository which is defined in his own composer.json configuration file. Following the example, let’s suppose alexbusu/tool1 requires alexbusu/tool2, repository of which is defined in alexbusu/tool1 package’s composer.json file.

Composer file of alexbusu/tool1 would look like this:

{
  "name": "alexbusu/tool1",
  "license": "proprietary",
  "repositories": [
    {
      "type": "github",
      "url": "https://github.com/alexbusu/tool2.git"
    }
  ],
  "require": {
    "alexbusu/tool2": "0.*",
  },
  "autoload": {
    "psr-4": {
      "MyOrg\\Tool1\\": "./src"
    }
  }
}

You have already noticed that alexbusu/tool2 can’t be found in dependencies directory (which is ./vendor by default). And since you are here, I guess you already know that Composer don’t load repositories recursively.

The good news is that it can, but you need to instruct it.

The easiest way is to add the second repository entry in your app’s composer.json file.

{
  "name": "myself/my-app",
  "license": "proprietary",
  "require": {
    "alexbusu/tool1": "0.*"
  },
  "autoload": {
    "psr-4": {
      "MyNamespace\\": "./src"
    }
  },
  "repositories": [
    {
      "type": "github",
      "url": "https://github.com/alexbusu/tool1.git"
    },
    {
      "type": "github",
      "url": "https://github.com/alexbusu/tool2.git"
    }
  ]
}

Now the Composer will be aware of the alexbusu/tool2 repository, and will fetch the package, as alexbusu/tool1 requires it.

Maybe it doesn’t look nice, and smells like a technical debt, but hey – it works at least.

Note: This sample works for public VCS repositories. For private repositories you might want to use a slightly different approach; for example one could use Git+SSH (invoking ssh-agent in docker containers or using a specific configuration in ~/.ssh/config). This part is not covered in this article.

Using the approach above in a single app is OK, but when you work with a lot of apps (websites, micro-services, etc) which require several of these unpublished packages, you might want to avoid adding repetitive repositories list to composer.json file of each app, so you can move them into composer global config directly (config.json file in composer home directory). This is especially handy if you have a large list of packages you develop.

More on how you can configure repositories list in global config can be found on Composer Docs.

After you add the list of repositories to composer global config, you can check the config using composer config --global --list command.

Another good news is that you can prepend more entries to the package repository sources by adding them into app’s composer.json.

{
  ...
  "repositories": [
    {
      "type": "path",
      "url": "/wip/tool1"
    },
    {
      "type": "path",
      "url": "/wip/tool2"
    }
  ]
  ...
}

This is useful especially when the dependency is the package you actively develop with the main app, so you want it to be symlink-ed from a local directory.

The repositories scan order will be in this case:

  1. Root composer.json file repositories
  2. Composer global config’s repositories (includes the main, packagist.org repository)

Anyway, composer will still search through all repositories, local copies being preferred.

Are you facing similar issues trying to solve apps’ dependencies? Or is there something to add here? Please leave it in a comment below.

Happy developing!

Leave a Reply

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