This Composer plugin creates a package manager and serves private packages to Composer from a local web server. By default, it creates one package manager per project within a packman
folder. A local web server is started and stopped automatically when needed by a composer task (usually http://localhost:8081
). Packman assumes that all private packages have the same vendor name and that their source is hosted at a common location (e.g. https://github.com/CompanyName/...
).
In Composer terminology, a repository is a set of packages, and a package is simply a commit in a repository. A commit can be identified in relative terms by its tag (it it has one), and optionally, by brach name. The composer.lock
identifies a package by its commit hash whereas the composer.json
declares version constraint for all required packages. For example, in plain English, version constraints read as "most recent commit in the develop branch" or "package with version 1.0 or higher".
A private repository is a set of private packages. Packages can be required by other packages in relative terms based on their semantic version. That is, instead of specifying a commit hash, one can request the newest package that matches a version pattern, such as 1.1.*
.
After running composer install
, update
or require
, the resulting require
pattern of each package is locked to the specific commit hash that satisfies the required version constraint.
Composer has a special syntax to declare constraints based on branch names. The dev-NAME
prefix tells composer that you want the branch named NAME
. In that case, Composer assumes that you want a clone of the repo in your vendor folder instead of just the files in the repo. The main difference is that you can go into the repo within the vendor folder, make changes and commit directly from there. That is convenient when developing interdependent modules. You can modify the dependant and skip the composer update DEPENDANT
step since you already have the latest version (in fact, you had it before its remote repo).
"If you wanted to check out the my-feature branch, you would specify dev-my-feature as the version constraint in your require clause. This would result in Composer cloning the my-library repository into my vendor directory and checking out the my-feature branch."
In contrast, for tag based constraints, Composer copies the needed files without cloning the repo. This behavior can be modified with the --prefer-source and --prefer-dist options.
Composer limits which tags are considered valid based on the value of "minimum-stability" in composer.json, which defaults to "stable". To work with development-level packages (including tags suffixed with -dev, -alpha, etc), set the minimum stability to @dev
. Alternatively, stability flags are version constraints in the form "constraint@LEVEL"
, where constraint
can be empty and LEVEL
is the stability level. The flag tells Composer that a given package can be installed in a different stability than the minimum-stability
setting. For example,
{
"require": {
"monolog/monolog": "1.0.*@beta",
"acme/foo": "@dev"
}
}
Version constraints can be defined as ranges 1.0 - 2.0
, >=1.0.0 <2.1
, wildcards 1.0.*
, and more. The only symbol that is particularly unclear is ~
, which means "Next Significant Release". E.g. ~1.2
is equivalent to >=1.2 <2.0.0
, while ~1.2.3
is equivalent to >=1.2.3 <1.3.0
.
Whenever a composer task is run from the CLI, the plugin reads the composer.json
of the root project and looks for packages listed under require
and require-dev
. The packages whose vendor name is equal to that of the root project are considered candidate private packages. The set of candidates is pruned by ignoring the ones publicly available from Packagist. The final set of private packages are downloaded from the their source location and served from a local web server acting as a composer-type repository.
- Read the
composer.json
of the root project, search for requirements in the same vendor name than the root project, and add them as candidate private packages; - Read the Composer CLI command arguments, identify if a private package is being required (e.g.
composer require my-org/my-package
), and if so, add it to the set of candidates; - Create the final set of private packages by ignoring the candidates that are already publicly available at Packagist;
- Create a Satis repository in the sub-folder packman/repos with the selected packages;
- Start a PHP built-in web server on the packman/repos folder at the selected URL (usually
localhost:8081
) just for the duration of the active Composer command, and close it when done; - Tell Composer to also look for repositories in the temporary local Packman web server.
Packman assumes that the standard ssh credentials required to fetch the needed repositories have been set up already. That's the usual case since you are probably using git to fetch your repositories.
Add the plugin to the global composer (usually located at ~/.composer
)
$ composer global require proximify/packman
The global method is the best because it only needs to be performed once and works on all types of projects.
There is nothing else to do if the default parameter values are appropriate. Moreover, the folder packman
is added to .gitignore
automatically, so that's also taken care of 🥳.
Packman will activate for all composer projects and see if they depend on private packages. If they don't, it won't do anything, so you won't get a
packman
folder in projects that don't need it.
Add the plugin to the development dependencies of your project
$ composer require proximify/packman --dev
The method works well on new projects because Packman can be installed before the private packages. However, when performing a composer install
on an existing project with private packages, Packman won't yet be available to plugin into Composer and manage them. A possible, clunky solution for such cases is to remove the private dependencies from the project, install Packman, and then put them back. Since that's not super fun, we recommend installing Packman globally.
When developing multiple interdependent components at the same time, it is better to work with the dependant repositories directly instead of using copies of their package files fetched by satisfying version constraints. There are two mains ways to do that. One is to require a package with a branch constraint instead of a tag constraint. For example,
$ composer require proximify/bibutils:dev-master
requires the branch "master" (the dev-
prefix means "branch name"). When requesting a package by branch name, Composer clones the repo within the appropriate place in the vendor
folder. Having a cloned repo instead just files means that you can make changes to it and then commit from its location in the vendor folder. It is weird, but it does the job.
An alternative to that approach is to use symlink repositories. Similarly to the cloned repo approach, the dependant packages won't need to be updated via composer every time they are locally changed by you. You can modify your local copy of the repo and the change is "applied" to the copy of the package within the vendor folder of another project.
Conceptually, the symlink approach and the cloned repo approach are identical. The only difference is that the cloned repo approach keeps an independent clone within the vendor folder, so if you also have the repo somewhere else, you have to remember to pull the latest changes in the clone that was not modified directly. With symlinks, you avoid that step.
Packman can add symlink repositories to composer automatically when a Composer command is run. To enable that feature, the symlinkDir
option has to be set to an existing directory. When symlinkDir
is defined, any repository name listed under symlinks
will be designated as a path-type repository and symlinked, even if publicly available from Packagist.
Packman adds the symlink repositories to the active composer object automatically so there is no need to manually add them to the repositories
section of a composer.json
.
// composer.json
{
"extra": {
"packman": {
"symlinkDir": "../",
"symlinks": ["repo-name1", "repo-name2"]
}
}
}
Note: If the symlinkDir
value is set, the committed composer.json
, it should be given as a relative path (either ../
or ~/
). By doing that, other members of your team will be able to have a similar configuration for them. If you don't expect other team members to also use symlink repositories, you should set the symlinkDir
value in the local packman/packman.json
file, which is in .gitignore
by default. Alternatively, you can set the its value in the global ~/.composer/composer.json
.
Make sure that the composer.json files at "../repo-name1" and "../repo-name2" set "name" to "my-org/repo-name1" and "my-org/repo-name2", respectively. In other words, the name of a package is defined by its composer file and not by the path to its repo.
If you prefer defining your symlink repositories explicitly, it's a good idea to define them in the global composer settings. In that way, you don't have to remember to remove the repository specs from each local composer.json
that needs it.
// Example global configuration of a symlink repository
// ~/.composer/composer.json
{
"require": {
"my-org/my-repo": "@dev"
},
"repositories": [
{
"type": "path",
"url": "../my-repo",
"options": {
"symlink": true
}
}
]
}
The variables here are: my-org
, my-repo
, and the value of url
. Everything else stays as shown.
Use a relative path for the "url" that works for all the members of your dev team. You can either start from your home directory with
~/
or you can make it relative to the root project with../
. The~/
is convenient for packages installed globally. The../
allows for more freedom as long as the package is not installed globally or that the relative path also works from the global.composer
folder.
It is fine and normal to have both symlink repositories and private once managed with . The symlink repositories will have higher precedence than the private ones.
Once the Packman plugin is installed to fetch remote private packages, and the global and/or local composer.json
is configured for any additional local symlink repositories, you can run
composer require my-org/my-repo:dev-master
to get the package defined as the latest development commit of the master branch of the given repository.
It's not a good idea to use symlink repositories to deploy to a production machine. The symlink repositories are only meant for local development. In contrast, Packman can be used to deploy to a production server (e.g. as a zip bundle). But, if the intent is to fetch the packages on the production server, then the default Packman solution of hosting private packages on localhost
won't work if that URL is not authenticated in some way. In other words, Packman is for local machines only. If used on a server, the Packman URL (localUrl
) should only be accessible only from the local machine.
The assumption that private packages have the same vendor name than that of the root project might not be correct. The vendor name to use for private packages can be set via a custom parameter.
Custom parameter values can be set in three different places, in the extra
property of the root project's composer.json
, in the extra
property of the global composer.json
(usually under ~/.composer
), and in the packman.json
(usually under ./packman
). The three sources are merged, with the packman file having priority over the local composer, and the global composer options having the least priority.
are set in the composer.json
under the extra
property. For example,
// composer.json
{
"name": "my-vendor/my-composer-project",
"extra": {
"packman": {
"vendor": "alt-my-vendor",
"localUrl": "http://localhost:8081",
"remoteUrl": "https://github.com"
}
}
}
If the protocol of the localUrl is http instead of https, packman will set the secure-http
to false
(see secure-http). Othewrise, Composer will refuse to contact a URL like http://localhost:8081
.
The paremeters are set in the global and/or local composer.json files under the keys extra: {packman: { ... }}
.
Parameter | Default value | Description |
---|---|---|
vendor | {ROOT-VENDOR} |
The vendor name of the private packages to manage. |
remoteUrl | https://github.com/{ROOT-VENDOR} |
Base URL of the repository hosting servers where the private repositories are located. |
localUrl | http://localhost:8081 |
The URL to be used for the local server of Composer packages. |
packmanDir | packman |
The folder where to used by Packman to store repositories and the packman.json configuration file. It can be local to each project (default), or shared by multiple projects. |
symlinkDir | The folder to used for symlinked repositories. | |
symlinks | An array of repository names to symlink. |
runs automatically when composer runs, and, in general, it does the right thing at the right time. However, it is also possible to run commands on demand using
composer COMMAND
.
Parameter | Description |
---|---|
packman-build | Perform an update of all registered packages. |
packman-reset | Reset the entire package store. |
packman-start | Build private packages and start the local web server to server them to composer. |
packman-stop | Stop the local web server that servers the packages to composer. |
packman-link | Adds the given folder names to the packman.json under the symlinks key. Note that tt does not add packages but just local repo folders. Packages requirements have to be added with composer require . It only adds the instructions to symlink to repository folders where some required packages can be found. |
packman-unlink | Remove symlinks from the packman.json file. |
By tagging a commit, one can attach a semantic version (semver) to a commit in order to make it referentiable in relative terms. For example, package "vendor/repo:1.2.0-beta" is the commit in the master brach that has the tag '1.2.0-beta'.
Version suffix labels of the form "-label" provide an additional way to communicate information about the state and intent of a particular commit. The meaning of lables is rank order as: dev
, alpha
, beta
, rc
, and they can have a numeric suffix too, such as beta1
or rc2
. A no-label version number, n.n.n
, is a higher version than the same number with a label, n.n.n-label
, for any label.
An untagged commit represents a package with an unknown version number that exists within a range of known version numbers. It's still possible to refer to an untagged commit. For example, one can request the last commit before a tagged commit.
When asking for a "vendor/repo:dev-master" package, one is asking for the latest commit in the master
branch of the "vendor/repo" repository with label -dev
or higher. For example, a -alpha
labelled version number would meet that condition.
This plugin uses the Satis repository generator (see using satis).
This project welcomes contributions and suggestions.
Copyright (c) Proximify Inc. All rights reserved.
Licensed under the MIT license.
Software component is made by Proximify. We invite the community to participate.