Working with Composer (PHP)

CodeScoring.Save works as a Composer repository (the composer type) and is compatible with Composer 2.x. You can create:

  • a proxy repository — a caching proxy in front of Packagist or another Composer repository (Private Packagist, Satis, Nexus, Artifactory, GitLab, another Save);
  • a hosted repository — storage for the company's internal packages.

Composer 1 is not supported.

Creating a Repository in the Web Interface

  1. In the sidebar, choose Projects and open the project.

  2. Click Create repository.

  3. Fill in the common fields Name, Key, Description, and Color — the same way as for other formats (see Repository management).

  4. In the Format field, choose PHP / Composer.

  5. In the Type field, choose Proxy or Hosted.

  6. For Proxy, fill in the additional fields:

    • Proxy URL — the URL of the Composer repository where its packages.json is available:

      UpstreamExample URL
      Packagisthttps://repo.packagist.org
      Nexushttps://nexus.example.com/repository/composer-proxy
      Artifactoryhttps://artifactory.example.com/artifactory/api/composer/php-remote
      Another Savehttps://save2.example.com/cs-save/php/<project>/<repository>
    • Cache TTL, seconds — a required field: how many seconds Save keeps data received from upstream before checking it again. For Packagist, 86400 (one day) is enough.

    • Authentication — how Save authenticates to upstream. For Packagist, leave none.

      ValueFieldsWhat upstream receivesWhen to choose
      none—Requests without authenticationA public upstream such as Packagist
      basicUsername, PasswordAuthorization: Basic ...Upstream accepts a username and a password, or a token instead of the password — this is the case for Nexus, Artifactory, and another Save. Composer calls this method http-basic
      bearerTokenAuthorization: Bearer <token>Upstream accepts a token in the Bearer header — the bearer method in Composer terms

      Once saved, the password and the token are never shown in the UI or returned by the API: on the edit form, a stored value appears as ******** — leave it as is to keep it. Save sends the credentials only to the host from Proxy URL (for details, see Upstream access for proxy repositories).

  7. Click Create repository.

On the page of the created repository, the chain-link icon button shows ready-made commands for connecting Composer to this particular repository.

Creating through the API

The same can be done with an API request. Set format to php and pass the upstream credentials in the auth block:

curl -X POST https://save.example.com/api/v1/repos \
  -H "Content-Type: application/json" \
  -u "<username>:<password>" \
  -d '{
    "project": "web",
    "name": "nexus-composer-proxy",
    "format": "php",
    "repository_type": "proxy",
    "remote_url": "https://nexus.example.com/repository/composer-proxy",
    "auth": {"auth_type": "basic", "username": "<upstream-user>", "password": "<upstream-password>"},
    "cache_ttl": 86400
  }'

For a hosted repository, "repository_type": "hosted" without remote_url and auth is enough.

Proxy repository specifics
  • Package archives are cached on the first download; subsequent installs take them from Save.
  • Save does not cache package metadata yet and asks upstream on every request. If upstream is unavailable, Composer can install only what is already in its own cache.
  • Packagist package archives are stored on GitHub, so Save needs outbound access to api.github.com and codeload.github.com.
  • Versions that have no zip archive in upstream (source code in git only) cannot be installed through a proxy.

URL Scheme

All paths below are relative to the repository URL https://save.example.com/cs-save/php/<project>/<repository>:

PathWhat it is
packages.jsonRepository root
p2/<vendor>/<package>.jsonRelease versions of a package
p2/<vendor>/<package>~dev.jsonDev branches of a package
dist/<vendor>/<package>/<version>.zipPackage archive
packages/list.jsonPackage list
search.json?q=<text>Search

All links inside packages.json and p2/... are absolute: Save builds them from the URL the client used to reach it (taking the X-Forwarded-Host and X-Forwarded-Proto headers into account).

Connecting Composer

The repository URL has the form https://save.example.com/cs-save/php/<project>/<repository>. Add it to your project as a composer type repository and disable packagist.org so that every package goes through Save:

# Add the repository to the current project
composer config repositories.save composer https://save.example.com/cs-save/php/<project>/packagist-proxy

# Disable packagist.org so that every request goes through Save
composer config repo.packagist false

The same in composer.json:

{
  "repositories": [
    { "type": "composer", "url": "https://save.example.com/cs-save/php/<project>/packagist-proxy" },
    { "packagist.org": false }
  ]
}

Save accepts http-basic — the Save user's username and password:

composer config [--global] http-basic.save.example.com <username> <password>

From then on, Composer works as usual: composer require, composer install, composer update.

Robot accounts in CI

For CI/CD, you can use a robot account: composer config http-basic.save.example.com 'sa$<robot-login>' '<api-key>'. In CI, it is convenient to pass the credentials through the COMPOSER_AUTH environment variable instead of committing auth.json to the repository.

export COMPOSER_AUTH='{"http-basic": {"save.example.com": {"username": "sa$ci-builder", "password": "<api-key>"}}}'

For details on robot accounts, see Authentication.

Test environments without TLS

By default, Composer works over https only. For an environment without TLS, run composer config secure-http false.

Publishing Packages to a Hosted Repository

Composer has no built-in publish command: packages usually reach Packagist or Satis from a git repository. Private registries (Nexus, Artifactory, GitLab) accept a zip archive of the package over HTTP instead. Save accepts the archive the same way Nexus does, so tools that can publish to Nexus work with Save too.

With the composer push Command

The elendev/composer-push plugin adds the composer push command to Composer: it builds a zip archive from the current project (without vendor/, VCS directories, and hidden files) and uploads it.

  1. Install the plugin in the package project:

    composer require --dev elendev/composer-push
  2. Specify the hosted repository URL in composer.json:

    {
      "extra": {
        "push": {
          "url": "https://save.example.com/cs-save/php/<project>/php-hosted",
          "type": "nexus",
          "ignore": ["tests/", "docs/"]
        }
      }
    }
  3. The plugin takes the username and password from auth.json for the Save host — the same ones you set with composer config http-basic... above.

  4. Publish the version:

    composer push 1.0.0

    If the version is set in composer.json, you can omit it.

Do not use

--src-type, --src-url, and --src-ref With these options, the plugin sends the archive in a different format, and Save responds with 400. A link to the git source is not needed for packages from Save: Composer downloads them as archives.

From CI Without PHP

If there is no PHP in CI, upload the archive with curl. The --upload-file option sends the file with a PUT request. The package name and the version are part of the URL:

curl -u "<username>:<password>" \
  --upload-file acme-demo-1.0.0.zip \
  https://save.example.com/cs-save/php/<project>/php-hosted/packages/upload/acme/demo/1.0.0

You can also upload the archive to the repository URL itself — the name and the version are then taken from composer.json inside the archive. If composer.json has no version, pass it in the ?version= parameter:

curl -u "<username>:<password>" \
  --upload-file acme-demo-1.0.0.zip \
  "https://save.example.com/cs-save/php/<project>/php-hosted?version=1.0.0"

Building the Archive

The easiest way is the composer archive command. It respects archive.exclude and export-ignore from .gitattributes:

composer archive --format=zip --file=acme-demo-1.0.0

git archive --format=zip -o acme-demo-1.0.0.zip v1.0.0 works as well. When publishing with composer push, you do not need to build the archive.

Package Requirements

Save validates the archive before storing it, so nothing is written on error:

  • the archive is a zip file, and composer.json is in its root or in a single top-level folder (as in GitHub and GitLab archives);
  • name in composer.json has the vendor/package format. If the name is also given in the upload URL, it must match composer.json;
  • the version follows the Composer rules. You can set it in version in composer.json or pass it on upload. If it is present in both places, the values must match (v1.0.0 and 1.0.0 are the same version). A branch with a slash in its name (dev-feature/x) cannot be uploaded;
  • the archive size does not exceed HOST_PHP_MAX_FILE_SIZE (256 MB by default); otherwise Save responds with 413.

A published version cannot be overwritten: uploading it again returns 409. To replace a version, delete it first.

Deleting a Version (hosted)

A version can be deleted in the web interface like any other artifact. There are two ways:

  • tick the checkbox to the left of the artifact in the repository tree and click Delete in the panel that appears above the tree. This way you can delete several versions at once;
  • click the artifact in the repository tree and, in the right pane, choose Delete artifact in the three-dot menu (next to Download).

Or through the API:

curl -u "<username>:<password>" \
  -X DELETE https://save.example.com/cs-save/php/<project>/php-hosted/packages/acme/demo/1.0.0

The change is visible in the metadata immediately.

Repository URL Migration

Use case: migrating a Composer repository from Nexus / Artifactory to CodeScoring.Save.

Sourceurl in composer.json before migrationurl in composer.json after migration
Nexushttps://nexus.host.ru/repository/composer-proxy/https://save.example.com/cs-save/php/<project>/composer-proxy
Artifactoryhttps://jfrog.host.ru/artifactory/api/composer/php-remotehttps://save.example.com/cs-save/php/<project>/php-remote
Official repositoryhttps://repo.packagist.orghttps://save.example.com/cs-save/php/<project>/packagist-proxy

The upload URL packages/upload/<vendor>/<package>/<version> is the same as in Nexus, so composer push and publishing scripts only need the new URL. After changing the URL, run composer update so that composer.lock gets the new archive links.

Troubleshooting

Which Requests Composer Makes

Run Composer with the -vvv option — it shows every URL it requests:

composer update -vvv

All requests must go to the Save URL. If the links start with http:// although Save is served over https, check that the ingress passes the X-Forwarded-Proto and X-Forwarded-Host headers.

Checking Package Metadata

curl -u "<username>:<password>" \
  https://save.example.com/cs-save/php/<project>/packagist-proxy/p2/monolog/monolog.json

A 404 means the package exists neither in Save nor in upstream.

Service Status

curl https://save.example.com/health

Repository Audit

curl -u "<username>:<password>" \
  "https://save.example.com/api/v1/admin/audit?resource_type=repository&q=packagist-proxy&limit=50"
Was this page helpful?