From 080aca2673fbe709f094cff13b72c89964406ecc Mon Sep 17 00:00:00 2001 From: upstream Date: Wed, 7 Oct 2026 19:38:14 +0200 Subject: [PATCH] Upstream composer/composer state of the touched files before 9e784fec18 --- doc/03-cli.md | 1537 ++++++++++++++++++++++++ src/Composer/Command/ConfigCommand.php | 1344 +++++++++++++++++++++ 2 files changed, 2881 insertions(+) create mode 100644 doc/03-cli.md create mode 100644 src/Composer/Command/ConfigCommand.php diff --git a/doc/03-cli.md b/doc/03-cli.md new file mode 100644 index 0000000..e2e072f --- /dev/null +++ b/doc/03-cli.md @@ -0,0 +1,1537 @@ +# Command-line interface / Commands + +You've already learned how to use the command-line interface to do some +things. This chapter documents all the available commands. + +To get help from the command-line, call `composer` or `composer list` +to see the complete list of commands, then `--help` combined with any of those +can give you more information. + +As Composer uses [symfony/console](https://github.com/symfony/console) you can call commands by short name if it's not ambiguous. +```shell +php composer.phar dump +``` +calls `composer dump-autoload`. + +## Bash Completions + +To install bash completions you can run `composer completion bash > completion.bash`. +This will create a `completion.bash` file in the current directory. + +Then execute `source completion.bash` to enable it in the current terminal session. + +Move and rename the `completion.bash` file to `/etc/bash_completion.d/composer` to make +it load automatically in new terminals. + +## Global Options + +The following options are available with every command: + +* **--verbose (-v):** Increase verbosity of messages. +* **--help (-h):** Display help information. +* **--quiet (-q):** Do not output any message. +* **--no-interaction (-n):** Do not ask any interactive question. +* **--no-plugins:** Disables plugins. +* **--no-scripts:** Skips execution of scripts defined in `composer.json`. +* **--no-cache:** Disables the use of the cache directory. Same as setting the COMPOSER_CACHE_DIR + env var to /dev/null (or NUL on Windows). +* **--working-dir (-d):** If specified, use the given directory as working directory. +* **--profile:** Display timing and memory usage information +* **--ansi:** Force ANSI output. +* **--no-ansi:** Disable ANSI output. +* **--version (-V):** Display this application version. + +## Process Exit Codes + +* **0:** OK +* **1:** Generic/unknown error code +* **2:** Dependency solving error code + +## init + +In the [Libraries](02-libraries.md) chapter we looked at how to create a +`composer.json` by hand. There is also an `init` command available to do this. + +When you run the command it will interactively ask you to fill in the fields, +while using some smart defaults. + +```shell +php composer.phar init +``` + +### Options + +* **--name:** Name of the package. +* **--description:** Description of the package. +* **--author:** Author name of the package. +* **--type:** Type of package. +* **--homepage:** Homepage of the package. +* **--require:** Package to require with a version constraint. Should be + in format `foo/bar:1.0.0`. +* **--require-dev:** Development requirements, see **--require**. +* **--stability (-s):** Value for the `minimum-stability` field. +* **--license (-l):** License of package. +* **--repository:** Provide one (or more) custom repositories. They will be stored + in the generated composer.json, and used for auto-completion when prompting for + the list of requires. Every repository can be either an HTTP URL pointing + to a `composer` repository or a JSON string which is similar to what the + [repositories](04-schema.md#repositories) key accepts. +* **--autoload (-a):** Add a PSR-4 autoload mapping to the composer.json. Automatically maps your package's namespace to the provided directory. (Expects a relative path, e.g. src/) See also [PSR-4 autoload](04-schema.md#psr-4). + +## install / i + +The `install` command reads the `composer.json` file from the current +directory, resolves the dependencies, and installs them into `vendor`. + +```shell +php composer.phar install +``` + +If there is a `composer.lock` file in the current directory, it will use the +exact versions from there instead of resolving them. This ensures that +everyone using the library will get the same versions of the dependencies. + +If there is no `composer.lock` file, Composer will create one after dependency +resolution. + +### Options + +* **--prefer-install:** There are two ways of downloading a package: `source` + and `dist`. Composer uses `dist` by default. If you pass + `--prefer-install=source` (or `--prefer-source`) Composer will install from + `source` if there is one. This is useful if you want to make a bugfix to a + project and get a local git clone of the dependency directly. + To get the legacy behavior where Composer use `source` automatically for dev + versions of packages, use `--prefer-install=auto`. See also [config.preferred-install](06-config.md#preferred-install). + Passing this flag will override the config value. +* **--dry-run:** If you want to run through an installation without actually + installing a package, you can use `--dry-run`. This will simulate the + installation and show you what would happen. +* **--download-only:** Download only, do not install packages. +* **--dev:** Install packages listed in `require-dev` (this is the default behavior). +* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader + generation skips the `autoload-dev` rules. Also see [COMPOSER_NO_DEV](#composer-no-dev). +* **--no-autoloader:** Skips autoloader generation. +* **--no-progress:** Removes the progress display that can mess with some + terminals or scripts which don't handle backspace characters. +* **--audit:** Run an audit after installation is complete. +* **--audit-format:** Audit output format. Must be "table", "plain", "json", or "summary" (default). +* **--no-security-blocking:** DEPRECATED, use `--no-blocking` instead. Allows installing packages with security advisories, that are abandoned or that are still in the cooldown period. Also see [COMPOSER_NO_SECURITY_BLOCKING](#composer-no-security-blocking). +* **--no-blocking:** Disables all policy based dependency blocking during this command, including the cooldown period. Also see [COMPOSER_NO_BLOCKING](#composer-no-blocking). +* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster + autoloader. This is recommended especially for production, but can take + a bit of time to run so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize-autoloader`. +* **--strict-psr-autoloader:** Return a failed exit code (6) if PSR-4 or PSR-0 mapping errors + are present in the current project (dependencies excluded). Requires `--optimize-autoloader` or `--classmap-authoritative` to work. +* **--apcu-autoloader:** Use APCu to cache found/not-found classes. +* **--apcu-autoloader-prefix:** Use a custom prefix for the APCu autoloader cache. + Implicitly enables `--apcu-autoloader`. +* **--ignore-platform-reqs:** ignore all platform requirements (`php`, `hhvm`, + `lib-*` and `ext-*`) and force the installation even if the local machine does + not fulfill these. + See also the [`platform`](06-config.md#platform) config option. +* **--ignore-platform-req:** ignore a specific platform requirement(`php`, + `hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine + does not fulfill it. Multiple requirements can be ignored via wildcard. Appending + a `+` makes it only ignore the upper-bound of the requirements. For example, if a package + requires `php: ^7`, then the option `--ignore-platform-req=php+` would allow installing on PHP 8, + but installation on PHP 5.6 would still fail. + +## update / u / upgrade + +In order to get the latest versions of the dependencies and to update the +`composer.lock` file, you should use the `update` command. This command is also +aliased as `upgrade` as it does the same as `upgrade` does if you are thinking +of `apt-get` or similar package managers. + +```shell +php composer.phar update +``` + +This will resolve all dependencies of the project and write the exact versions +into `composer.lock`. + +If you only want to update a few packages and not all, you can list them as such: + +```shell +php composer.phar update vendor/package vendor/package2 +``` + +You can also use wildcards to update a bunch of packages at once: + +```shell +php composer.phar update "vendor/*" +``` + + +If you want to downgrade a package to a specific version without changing your +composer.json you can use `--with` and provide a custom version constraint: + +```shell +php composer.phar update --with vendor/package:2.0.1 +``` + +Note that with the above all packages will be updated. If you only want to +update the package(s) for which you provide custom constraints using `--with`, +you can skip `--with` and instead use constraints with the partial update syntax: + +```shell +php composer.phar update vendor/package:2.0.1 vendor/package2:3.0.* +``` + +> **Note:** For packages also required in your composer.json the custom constraint +> must be a subset of the existing constraint. The composer.json constraints still +> apply and the composer.json is not modified by these temporary update constraints. + + +### Options + +* **--prefer-install:** There are two ways of downloading a package: `source` + and `dist`. Composer uses `dist` by default. If you pass + `--prefer-install=source` (or `--prefer-source`) Composer will install from + `source` if there is one. This is useful if you want to make a bugfix to a + project and get a local git clone of the dependency directly. + To get the legacy behavior where Composer use `source` automatically for dev + versions of packages, use `--prefer-install=auto`. See also [config.preferred-install](06-config.md#preferred-install). + Passing this flag will override the config value. +* **--dry-run:** Simulate the command without actually doing anything. +* **--dev:** Install packages listed in `require-dev` (this is the default behavior). +* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules. Also see [COMPOSER_NO_DEV](#composer-no-dev). +* **--no-install:** Does not run the install step after updating the composer.lock file. +* **--no-audit:** Does not run the audit steps after updating the composer.lock file. Also see [COMPOSER_NO_AUDIT](#composer-no-audit). +* **--audit-format:** Audit output format. Must be "table", "plain", "json", or "summary" (default). +* **--no-security-blocking:** DEPRECATED, use `--no-blocking` instead. Allows installing packages with security advisories, that are abandoned or that are still in the cooldown period. Also see [COMPOSER_NO_SECURITY_BLOCKING](#composer-no-security-blocking). +* **--no-blocking:** Disables all policy based dependency blocking during this command, including the cooldown period. Also see [COMPOSER_NO_BLOCKING](#composer-no-blocking). +* **--lock:** Overwrites the lock file hash to suppress warning about the lock file being out of + date without updating package versions. Package metadata like mirrors and URLs are updated if + they changed. +* **--with:** Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 +* **--no-autoloader:** Skips autoloader generation. +* **--no-progress:** Removes the progress display that can mess with some + terminals or scripts which don't handle backspace characters. +* **--with-dependencies (-w):** Update also dependencies of packages in the argument list, except those which are root requirements. Can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var. +* **--with-all-dependencies (-W):** Update also dependencies of packages in the argument list, including those which are root requirements. Can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var. +* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster + autoloader. This is recommended especially for production, but can take + a bit of time to run, so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize-autoloader`. +* **--strict-psr-autoloader:** Return a failed exit code (6) if PSR-4 or PSR-0 mapping errors + are present in the current project (dependencies excluded). Requires `--optimize-autoloader` or `--classmap-authoritative` to work. +* **--apcu-autoloader:** Use APCu to cache found/not-found classes. +* **--apcu-autoloader-prefix:** Use a custom prefix for the APCu autoloader cache. + Implicitly enables `--apcu-autoloader`. +* **--ignore-platform-reqs:** ignore all platform requirements (`php`, `hhvm`, + `lib-*` and `ext-*`) and force the installation even if the local machine does + not fulfill these. + See also the [`platform`](06-config.md#platform) config option. +* **--ignore-platform-req:** ignore a specific platform requirement(`php`, + `hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine + does not fulfill it. Multiple requirements can be ignored via wildcard. Appending + a `+` makes it only ignore the upper-bound of the requirements. For example, if a package + requires `php: ^7`, then the option `--ignore-platform-req=php+` would allow installing on PHP 8, + but installation on PHP 5.6 would still fail. +* **--prefer-stable:** Prefer stable versions of dependencies. Can also be set via the + COMPOSER_PREFER_STABLE=1 env var. +* **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal + versions of requirements, generally used with `--prefer-stable`. Can also be set via the + COMPOSER_PREFER_LOWEST=1 env var. +* **--minimal-changes (-m):** Only perform absolutely necessary changes to dependencies. + If packages cannot be kept at their currently locked version they are updated. For partial + updates the allow-listed packages are always updated fully. Can also be set via + the COMPOSER_MINIMAL_CHANGES=1 env var. +* **--patch-only:** Only allow patch version updates for currently installed dependencies. +* **--interactive:** Interactive interface with autocompletion to select the packages to update. +* **--root-reqs:** Restricts the update to your first degree dependencies. +* **--bump-after-update:** Runs `bump` after performing the update. Set to `dev` or `no-dev` to only bump those dependencies. + +Specifying one of the words `mirrors`, `lock`, or `nothing` as an argument has the same effect as specifying the option `--lock`, for example `composer update mirrors` is exactly the same as `composer update --lock`. + +## require / r + +The `require` command adds new packages to the `composer.json` file from +the current directory. If no file exists one will be created on the fly. + +If you do not specify a package, Composer will prompt you to search for a package, and given +results, provide a list of matches to require. + +```shell +php composer.phar require +``` + +After adding/changing the requirements, the modified requirements will be +installed or updated. + +If you do not want to choose requirements interactively, you can pass them +to the command. + +```shell +php composer.phar require "vendor/package:2.*" vendor/package2:dev-master +``` + +If you do not specify a version constraint, composer will choose a suitable one based +on the available package versions. + +```shell +php composer.phar require vendor/package vendor/package2 +``` + +If you do not want to install the new dependencies immediately you can call it with --no-update + +### Options + +* **--dev:** Add packages to `require-dev`. +* **--dry-run:** Simulate the command without actually doing anything. +* **--prefer-install:** There are two ways of downloading a package: `source` + and `dist`. Composer uses `dist` by default. If you pass + `--prefer-install=source` (or `--prefer-source`) Composer will install from + `source` if there is one. This is useful if you want to make a bugfix to a + project and get a local git clone of the dependency directly. + To get the legacy behavior where Composer use `source` automatically for dev + versions of packages, use `--prefer-install=auto`. See also [config.preferred-install](06-config.md#preferred-install). + Passing this flag will override the config value. +* **--no-progress:** Removes the progress display that can mess with some + terminals or scripts which don't handle backspace characters. +* **--no-update:** Disables the automatic update of the dependencies (implies --no-install). +* **--no-install:** Does not run the install step after updating the composer.lock file. +* **--no-audit:** Does not run the audit steps after updating the composer.lock file. Also see [COMPOSER_NO_AUDIT](#composer-no-audit). +* **--audit-format:** Audit output format. Must be "table", "plain", "json", or "summary" (default). +* **--no-security-blocking:** DEPRECATED, use `--no-blocking` instead. Allows installing packages with security advisories, that are abandoned or that are still in the cooldown period. Also see [COMPOSER_NO_SECURITY_BLOCKING](#composer-no-security-blocking). +* **--no-blocking:** Disables all policy blocking during this command, including the cooldown period. Also see [COMPOSER_NO_BLOCKING](#composer-no-blocking). +* **--update-no-dev:** Run the dependency update with the `--no-dev` option. Also see [COMPOSER_NO_DEV](#composer-no-dev). +* **--update-with-dependencies (-w):** Also update dependencies of the newly required packages, except those that are root requirements. Can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var. +* **--update-with-all-dependencies (-W):** Also update dependencies of the newly required packages, including those that are root requirements. Can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var. +* **--ignore-platform-reqs:** ignore all platform requirements (`php`, `hhvm`, + `lib-*` and `ext-*`) and force the installation even if the local machine does + not fulfill these. + See also the [`platform`](06-config.md#platform) config option. +* **--ignore-platform-req:** ignore a specific platform requirement(`php`, + `hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine + does not fulfill it. Multiple requirements can be ignored via wildcard. +* **--prefer-stable:** Prefer stable versions of dependencies. Can also be set via the + COMPOSER_PREFER_STABLE=1 env var. +* **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal + versions of requirements, generally used with `--prefer-stable`. Can also be set via the + COMPOSER_PREFER_LOWEST=1 env var. +* **--minimal-changes (-m):** During an update with `-w`/`-W`, only perform absolutely necessary + changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var. +* **--sort-packages:** Keep packages sorted in `composer.json`. +* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to + get a faster autoloader. This is recommended especially for production, but + can take a bit of time to run, so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize-autoloader`. +* **--apcu-autoloader:** Use APCu to cache found/not-found classes. +* **--apcu-autoloader-prefix:** Use a custom prefix for the APCu autoloader cache. + Implicitly enables `--apcu-autoloader`. + +## remove / rm / uninstall + +The `remove` command removes packages from the `composer.json` file from +the current directory. + +```shell +php composer.phar remove vendor/package vendor/package2 +``` + +After removing the requirements, the modified requirements will be +uninstalled. + +### Options + +* **--unused:** Remove unused packages that are not a direct or indirect dependency (anymore). +* **--dev:** Remove packages from `require-dev`. +* **--dry-run:** Simulate the command without actually doing anything. +* **--no-progress:** Removes the progress display that can mess with some + terminals or scripts which don't handle backspace characters. +* **--no-update:** Disables the automatic update of the dependencies (implies --no-install). +* **--no-install:** Does not run the install step after updating the composer.lock file. +* **--no-audit:** Does not run the audit steps after installation is complete. Also see [COMPOSER_NO_AUDIT](#composer-no-audit). +* **--audit-format:** Audit output format. Must be "table", "plain", "json", or "summary" (default). +* **--no-security-blocking:** DEPRECATED, use `--no-blocking` instead. Allows installing packages with security advisories, that are abandoned or that are still in the cooldown period. Also see [COMPOSER_NO_SECURITY_BLOCKING](#composer-no-security-blocking). +* **--no-blocking:** Disables all policy based dependency blocking during this command, including the cooldown period. Also see [COMPOSER_NO_BLOCKING](#composer-no-blocking). +* **--update-no-dev:** Run the dependency update with the --no-dev option. Also see [COMPOSER_NO_DEV](#composer-no-dev). +* **--update-with-dependencies (-w):** Also update dependencies of the removed packages. Can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var. + (Deprecated, is now default behavior) +* **--update-with-all-dependencies (-W):** Allows all inherited dependencies to be updated, + including those that are root requirements. Can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var. +* **--minimal-changes (-m):** During an update with `-w`/`-W`, only perform absolutely necessary + changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var. +* **--ignore-platform-reqs:** ignore all platform requirements (`php`, `hhvm`, + `lib-*` and `ext-*`) and force the installation even if the local machine does + not fulfill these. + See also the [`platform`](06-config.md#platform) config option. +* **--ignore-platform-req:** ignore a specific platform requirement(`php`, + `hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine + does not fulfill it. Multiple requirements can be ignored via wildcard. +* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to + get a faster autoloader. This is recommended especially for production, but + can take a bit of time to run so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize-autoloader`. +* **--apcu-autoloader:** Use APCu to cache found/not-found classes. +* **--apcu-autoloader-prefix:** Use a custom prefix for the APCu autoloader cache. + Implicitly enables `--apcu-autoloader`. + +## bump + +The `bump` command increases the lower limit of your composer.json requirements +to the currently installed versions. This helps to ensure your dependencies do not +accidentally get downgraded due to some other conflict, and can slightly improve +dependency resolution performance as it limits the amount of package versions +Composer has to look at. + +Running this blindly on libraries is **NOT** recommended as it will narrow down +your allowed dependencies, which may cause dependency hell for your users. +Running it with `--dev-only` on libraries may be fine however as dev requirements +are local to the library and do not affect consumers of the package. + +### Options + +* **--dev-only:** Only bump requirements in "require-dev". +* **--no-dev-only:** Only bump requirements in "require". +* **--dry-run:** Outputs the packages to bump, but will not execute anything. + +## reinstall + +The `reinstall` command looks up installed packages by name, +uninstalls them and reinstalls them. This lets you do a clean install +of a package if you messed with its files, or if you wish to change +the installation type using --prefer-install. + +```shell +php composer.phar reinstall acme/foo acme/bar +``` + +You can specify more than one package name to reinstall, or use a +wildcard to select several packages at once: + +```shell +php composer.phar reinstall "acme/*" +``` + +### Options + +* **--prefer-install:** There are two ways of downloading a package: `source` + and `dist`. Composer uses `dist` by default. If you pass + `--prefer-install=source` (or `--prefer-source`) Composer will install from + `source` if there is one. This is useful if you want to make a bugfix to a + project and get a local git clone of the dependency directly. + To get the legacy behavior where Composer use `source` automatically for dev + versions of packages, use `--prefer-install=auto`. See also [config.preferred-install](06-config.md#preferred-install). + Passing this flag will override the config value. +* **--no-autoloader:** Skips autoloader generation. +* **--no-progress:** Removes the progress display that can mess with some + terminals or scripts which don't handle backspace characters. +* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster + autoloader. This is recommended especially for production, but can take + a bit of time to run so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize-autoloader`. +* **--apcu-autoloader:** Use APCu to cache found/not-found classes. +* **--apcu-autoloader-prefix:** Use a custom prefix for the APCu autoloader cache. + Implicitly enables `--apcu-autoloader`. +* **--ignore-platform-reqs:** ignore all platform requirements. This only + has an effect in the context of the autoloader generation for the + reinstall command. +* **--ignore-platform-req:** ignore a specific platform requirement. This only + has an effect in the context of the autoloader generation for the + reinstall command. Multiple requirements can be ignored via wildcard. + +## check-platform-reqs + +The check-platform-reqs command checks that your PHP and extensions versions +match the platform requirements of the installed packages. This can be used +to verify that a production server has all the extensions needed to run a +project after installing it for example. + +Unlike update/install, this command will ignore config.platform settings and +check the real platform packages so you can be certain you have the required +platform dependencies. + +### Options + +* **--lock:** Checks requirements only from the lock file, not from installed packages. +* **--no-dev:** Disables checking of require-dev packages requirements. +* **--format (-f):** Format of the output: text (default) or json + +## global + +The global command allows you to run other commands like `install`, `remove`, `require` +or `update` as if you were running them from the [COMPOSER_HOME](#composer-home) +directory. + +This is merely a helper to manage a project stored in a central location that +can hold CLI tools or Composer plugins that you want to have available everywhere. + +This can be used to install CLI utilities globally. Here is an example: + +```shell +php composer.phar global require friendsofphp/php-cs-fixer +``` + +Now the `php-cs-fixer` binary is available globally. Make sure your global +[vendor binaries](articles/vendor-binaries.md) directory is in your `$PATH` +environment variable, you can get its location with the following command : + +```shell +php composer.phar global config bin-dir --absolute +``` + +If you wish to update the binary later on you can run a global update: + +```shell +php composer.phar global update +``` + +## search + +The search command allows you to search through the current project's package +repositories. Usually this will be packagist. You pass it the terms you want +to search for. + +```shell +php composer.phar search monolog +``` + +You can also search for more than one term by passing multiple arguments. + +### Options + +* **--only-name (-N):** Search only in package names. +* **--only-vendor (-O):** Search only for vendor / organization names, returns only "vendor" + as a result. +* **--type (-t):** Search for a specific package type. +* **--format (-f):** Lets you pick between text (default) or json output format. + Note that in the json, only the name and description keys are guaranteed to be + present. The rest (`url`, `repository`, `downloads` and `favers`) are available + for Packagist.org search results and other repositories may return more or less + data. + +## show / info + +To list all of the available packages, you can use the `show` command. + +```shell +php composer.phar show +``` + +To filter the list you can pass a package mask using wildcards. + +```shell +php composer.phar show "monolog/*" +``` +```text +monolog/monolog 2.4.0 Sends your logs to files, sockets, inboxes, databases and various web services +``` + +If you want to see the details of a certain package, you can pass the package +name. + +```shell +php composer.phar show monolog/monolog +``` +```text +name : monolog/monolog +descrip. : Sends your logs to files, sockets, inboxes, databases and various web services +keywords : log, logging, psr-3 +versions : * 1.27.1 +type : library +license : MIT License (MIT) (OSI approved) https://spdx.org/licenses/MIT.html#licenseText +homepage : http://github.com/Seldaek/monolog +source : [git] https://github.com/Seldaek/monolog.git 904713c5929655dc9b97288b69cfeedad610c9a1 +dist : [zip] https://api.github.com/repos/Seldaek/monolog/zipball/904713c5929655dc9b97288b69cfeedad610c9a1 904713c5929655dc9b97288b69cfeedad610c9a1 +names : monolog/monolog, psr/log-implementation + +support +issues : https://github.com/Seldaek/monolog/issues +source : https://github.com/Seldaek/monolog/tree/1.27.1 + +autoload +psr-4 +Monolog\ => src/Monolog + +requires +php >=5.3.0 +psr/log ~1.0 +``` + +You can even pass the package version, which will tell you the details of that +specific version. + +```shell +php composer.phar show monolog/monolog 1.0.2 +``` + +### Options + +* **--all:** List all packages available in all your repositories. +* **--installed (-i):** List the packages that are installed (this is enabled by default, and deprecated). +* **--locked:** List the locked packages from composer.lock. +* **--platform (-p):** List only platform packages (php & extensions). +* **--available (-a):** List available packages only. +* **--self (-s):** List the root package info. +* **--name-only (-N):** List package names only. +* **--path (-P):** List package paths. +* **--tree (-t):** List your dependencies as a tree. If you pass a package name it will show the dependency tree for that package. +* **--latest (-l):** List all installed packages including their latest version. +* **--outdated (-o):** Implies --latest, but this lists *only* packages that have a newer version available. +* **--ignore:** Ignore specified package(s). Can contain wildcards (`*`). Use it with the --outdated option if you don't want to be informed about new versions of some packages +* **--no-dev:** Filters dev dependencies from the package list. +* **--major-only (-M):** Use with --latest or --outdated. Only shows packages that have major SemVer-compatible updates. +* **--minor-only (-m):** Use with --latest or --outdated. Only shows packages that have minor SemVer-compatible updates. +* **--patch-only:** Use with --latest or --outdated. Only shows packages that have patch-level SemVer-compatible updates. +* **--sort-by-age (-A):** Displays the installed version's age, and sorts packages oldest first. Use with the --latest or --outdated option. +* **--direct (-D):** Restricts the list of packages to your direct dependencies. +* **--strict:** Return a non-zero exit code when there are outdated packages. +* **--format (-f):** Lets you pick between text (default) or json output format. +* **--ignore-platform-reqs:** ignore all platform requirements (`php`, `hhvm`, + `lib-*` and `ext-*`) and force the installation even if the local machine does + not fulfill these. Use with the --outdated option. +* **--ignore-platform-req:** ignore a specific platform requirement(`php`, + `hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine + does not fulfill it. Multiple requirements can be ignored via wildcard. Use with + the --outdated option. + +## outdated + +The `outdated` command shows a list of installed packages that have updates available, +including their current and latest versions. This is basically an alias for +`composer show -lo`. + +The color coding is as such: + +- **green (=)**: Dependency is in the latest version and is up to date. +- **yellow (`~`)**: Dependency has a new version available that includes backwards compatibility breaks according to semver, so upgrade when + you can but it may involve work. +- **red (!)**: Dependency has a new version that is semver-compatible and you should upgrade it. + +### Options + +* **--all (-a):** Show all packages, not just outdated (alias for `composer show --latest`). +* **--direct (-D):** Restricts the list of packages to your direct dependencies. +* **--strict:** Returns non-zero exit code if any package is outdated. +* **--ignore:** Ignore specified package(s). Can contain wildcards (`*`). Use it if you don't want to be informed about new versions of some packages +* **--major-only (-M):** Only shows packages that have major SemVer-compatible updates. +* **--minor-only (-m):** Only shows packages that have minor SemVer-compatible updates. +* **--patch-only (-p):** Only shows packages that have patch-level SemVer-compatible updates. +* **--sort-by-age (-A):** Displays the installed version's age, and sorts packages oldest first. +* **--format (-f):** Lets you pick between text (default) or json output format. +* **--no-dev:** Do not show outdated dev dependencies. +* **--locked:** Shows updates for packages from the lock file, regardless of what is currently in vendor dir. +* **--ignore-platform-reqs:** ignore all platform requirements (`php`, `hhvm`, + `lib-*` and `ext-*`) and force the installation even if the local machine does + not fulfill these. +* **--ignore-platform-req:** ignore a specific platform requirement(`php`, + `hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine + does not fulfill it. Multiple requirements can be ignored via wildcard. + +## browse / home + +The `browse` (aliased to `home`) opens a package's repository URL or homepage +in your browser. + +### Options + +* **--homepage (-H):** Open the homepage instead of the repository URL. +* **--show (-s):** Only show the homepage or repository URL. + +## suggests + +Lists all packages suggested by the currently installed set of packages. You can +optionally pass one or multiple package names in the format of `vendor/package` +to limit output to suggestions made by those packages only. + +Use the `--by-package` (default) or `--by-suggestion` flags to group the output by +the package offering the suggestions or the suggested packages respectively. + +If you only want a list of suggested package names, use `--list`. + +### Options + +* **--by-package:** Groups output by suggesting package (default). +* **--by-suggestion:** Groups output by suggested package. +* **--all:** Show suggestions from all dependencies, including transitive ones (by + default only direct dependencies' suggestions are shown). +* **--list:** Show only list of suggested package names. +* **--no-dev:** Excludes suggestions from `require-dev` packages. + +## fund + +Discover how to help fund the maintenance of your dependencies. This lists +all funding links from the installed dependencies. Use `--format=json` to +get machine-readable output. + +### Options + +* **--format (-f):** Lets you pick between text (default) or json output format. + +## depends / why + +The `depends` command tells you which other packages depend on a certain +package. As with installation `require-dev` relationships are only considered +for the root package. + +```shell +php composer.phar depends doctrine/lexer +``` +```text +doctrine/annotations 1.13.3 requires doctrine/lexer (1.*) +doctrine/common 2.13.3 requires doctrine/lexer (^1.0) +``` + +You can optionally specify a version constraint after the package to limit the +search. + +Add the `--tree` or `-t` flag to show a recursive tree of why the package is +depended upon, for example: + +```shell +php composer.phar depends psr/log -t +``` +```text +psr/log 1.1.4 Common interface for logging libraries +├──composer/composer 2.4.x-dev (requires psr/log ^1.0 || ^2.0 || ^3.0) +├──composer/composer dev-main (requires psr/log ^1.0 || ^2.0 || ^3.0) +├──composer/xdebug-handler 3.0.3 (requires psr/log ^1 || ^2 || ^3) +│ ├──composer/composer 2.4.x-dev (requires composer/xdebug-handler ^2.0.2 || ^3.0.3) +│ └──composer/composer dev-main (requires composer/xdebug-handler ^2.0.2 || ^3.0.3) +└──symfony/console v5.4.11 (conflicts psr/log >=3) (circular dependency aborted here) +``` + +### Options + +* **--recursive (-r):** Recursively resolves up to the root package. +* **--tree (-t):** Prints the results as a nested tree, implies -r. + +## prohibits / why-not + +The `prohibits` command tells you which packages are blocking a given package +from being installed. Specify a version constraint to verify whether upgrades +can be performed in your project, and if not why not. See the following +example: + +```shell +php composer.phar prohibits symfony/symfony 3.1 +``` +```text +laravel/framework v5.2.16 requires symfony/var-dumper (2.8.*|3.0.*) +``` + +Note that you can also specify platform requirements, for example to check +whether you can upgrade your server to PHP 8.0: + +```shell +php composer.phar prohibits php 8 +``` +```text +doctrine/cache v1.6.0 requires php (~5.5|~7.0) +doctrine/common v2.6.1 requires php (~5.5|~7.0) +doctrine/instantiator 1.0.5 requires php (>=5.3,<8.0-DEV) +``` + +As with `depends` you can request a recursive lookup, which will list all +packages depending on the packages that cause the conflict. + +### Options + +* **--recursive (-r):** Recursively resolves up to the root package. +* **--tree (-t):** Prints the results as a nested tree, implies -r. + +## validate + +You should always run the `validate` command before you commit your +`composer.json` file (and `composer.lock` [if applicable](01-basic-usage.md#commit-your-composer-lock-file-to-version-control)), and before you tag a release. + +It will check if your +`composer.json` is valid. If a `composer.lock` exists, it will also check if it is up to date with the `composer.json`. + +```shell +php composer.phar validate +``` + +### Options + +* **--no-check-all:** Do not emit a warning if requirements in `composer.json` use unbound or overly strict version constraints. +* **--no-check-lock:** Do not emit an error if `composer.lock` exists and is not up to date. +* **--check-lock** Check if lock file is up to date (even when [config.lock](06-config.md#lock) is false) +* **--no-check-publish:** Do not emit an error if `composer.json` is unsuitable for publishing as a package on Packagist but is otherwise valid. +* **--no-check-version:** Do not emit an error if the version field is present. +* **--with-dependencies:** Also validate the composer.json of all installed dependencies. +* **--strict:** Return a non-zero exit code for warnings as well as errors. + +## status + +If you often need to modify the code of your dependencies and they are +installed from source, the `status` command allows you to check if you have +local changes in any of them. + +```shell +php composer.phar status +``` + +With the `--verbose` option you get some more information about what was +changed: + +```shell +php composer.phar status -v +``` +```text +You have changes in the following dependencies: +vendor/seld/jsonlint: + M README.mdown +``` + +## self-update / selfupdate + +To update Composer itself to the latest version, run the `self-update` +command. It will replace your `composer.phar` with the latest version. + +```shell +php composer.phar self-update +``` + +If you would like to instead update to a specific release specify it: + +```shell +php composer.phar self-update 2.4.0-RC1 +``` + +If you have installed Composer for your entire system (see [global installation](00-intro.md#globally)), +you may have to run the command with `root` privileges + +```shell +sudo -H composer self-update +``` + +If Composer was not installed as a PHAR, this command is not available. +(This is sometimes the case when Composer was installed by an operating system package manager.) + +When updating, Composer warns you if the version you are on or updating to is end of life, or is an +older line nearing end of life that only receives critical security fixes, and points you to the +latest stable release. If a newer Composer version exists but requires a newer PHP version than the +one you are running, Composer tells you which PHP version you would need and that you are pinned to +an older release line. + +Backups created for `--rollback` are stored in the [`data-dir`](06-config.md#data-dir), +and the public keys used to verify downloads are stored in `COMPOSER_HOME`. Both +directories must be writable only by the user that owns the Composer installation and +should be treated as trusted: a directory writable by other users could be used to plant +a malicious `composer.phar` that a privileged `self-update --rollback` would then install. +When rolling back to a tagged release the backup is verified against the published +signature from getcomposer.org before being installed (the rollback aborts on a mismatch +or if the signature cannot be downloaded); snapshot/dev builds cannot be verified this way, +so a rollback to one asks for confirmation when run interactively. + +### Options + +* **--rollback (-r):** Rollback to the last version you had installed. +* **--clean-backups:** Delete old backups during an update. This makes the + current version of Composer the only backup available after the update. +* **--no-progress:** Do not output download progress. +* **--update-keys:** Prompt user for a key update. +* **--stable:** Force an update to the stable channel. +* **--preview:** Force an update to the preview channel. +* **--snapshot:** Force an update to the snapshot channel. +* **--1:** Force an update to the stable channel, but only use 1.x versions +* **--2:** Force an update to the stable channel, but only use 2.x versions +* **--set-channel-only:** Only store the channel as the default one and then exit + +## config + +The `config` command allows you to edit Composer config settings and repositories +in either the local `composer.json` file or the global `config.json` file. + +Additionally it lets you edit most properties in the local `composer.json`. + +```shell +php composer.phar config --list +``` + +### Usage + +`config [options] [setting-key] [setting-value1] ... [setting-valueN]` + +`setting-key` is a configuration option name and `setting-value1` is a +configuration value. For settings that can take an array of values (like +`github-protocols`), multiple setting-value arguments are allowed. + +You can also edit the values of the following properties: + +`description`, `homepage`, `keywords`, `license`, `minimum-stability`, +`name`, `prefer-stable`, `type` and `version`. + +See the [Config](06-config.md) chapter for valid configuration options. + +### Options + +* **--global (-g):** Operate on the global config file located at + `$COMPOSER_HOME/config.json` by default. Without this option, this command + affects the local composer.json file or a file specified by `--file`. +* **--editor (-e):** Open the local composer.json file using in a text editor as + defined by the `EDITOR` env variable. With the `--global` option, this opens + the global config file. +* **--auth (-a):** Affect auth config file (only used for --editor). +* **--unset:** Remove the configuration element named by `setting-key`. +* **--list (-l):** Show the list of current config variables. With the `--global` + option this lists the global configuration only. +* **--file="..." (-f):** Operate on a specific file instead of composer.json. Note + that this cannot be used in conjunction with the `--global` option. +* **--absolute:** Returns absolute paths when fetching `*-dir` config values + instead of relative. +* **--json:** JSON decode the setting value, to be used with `extra.*` keys. +* **--merge:** Merge the setting value with the current value, to be used with `extra.*` keys in combination with `--json`. +* **--append:** When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority). +* **--source:** Display where the config value is loaded from. + +### Modifying Repositories + +In addition to modifying the config section, the `config` command also supports making +changes to the repositories section by using it the following way: + +```shell +php composer.phar config repositories.foo vcs https://github.com/foo/bar +``` + +If your repository requires more configuration options, you can instead pass its JSON representation : + +```shell +php composer.phar config repositories.foo '{"type": "vcs", "url": "http://svn.example.org/my-project/", "trunk-path": "master"}' +``` + +### Modifying Extra Values + +In addition to modifying the config section, the `config` command also supports making +changes to the extra section by using it the following way: + +```shell +php composer.phar config extra.foo.bar value +``` + +The dots indicate array nesting, a max depth of 3 levels is allowed though. The above +would set `"extra": { "foo": { "bar": "value" } }`. + +If you have a complex value to add/modify, you can use the `--json` and `--merge` flags +to edit extra fields as json: + +```shell +php composer.phar config --json extra.foo.bar '{"baz": true, "qux": []}' +``` + +## repository / repo + +The `repo` command lets you manage repositories in your `composer.json`. It is more powerful and recommended over using `composer config repositories.*` to manipulate the repositories configuration. Refer to [Repositories](05-repositories.md) documentation for details on the available types and configuration options. + +### Usage + +```shell +repo [options] list +repo [options] add [repo-name] [repo-type] [url] +repo [options] add [repo-name] [json-repo-definition] +repo [options] remove [repo-name] +repo [options] set-url [repo-name] [url] +repo [options] get-url [repo-name] +repo [options] enable packagist.org +repo [options] disable packagist.org +``` + +### Options + +- **--global (-g):** to modify the global `$COMPOSER_HOME/config.json`. +- **--file (-f):** to modify a specific file instead of composer.json. +- **--append:** to add a repository with lower priority (by default repositories are prepended and have thus higher priority than existing ones). +- **--before [name]:** to insert the new repository before an existing repository named `[name]`. +- **--after [name]:** to insert the new repository after an existing repository named `[name]`. The `[name]` must match an existing repository name. + +### Examples + +```shell +php composer.phar repo list +php composer.phar repo add foo vcs https://github.com/acme/foo +php composer.phar repo add bar composer https://repo.packagist.com/bar +php composer.phar repo add zips '{"type":"artifact","url":"/path/to/dir/with/zips"}' +php composer.phar repo add baz vcs https://example.org --before foo +php composer.phar repo add qux vcs https://example.org --after bar +php composer.phar repo remove foo +php composer.phar repo set-url foo https://git.example.org/acme/foo +php composer.phar repo get-url foo +php composer.phar repo disable packagist.org +php composer.phar repo enable packagist.org +``` + +## policy + +The `policy` command lets you manage custom dependency policies and their sources in your `composer.json` under `config.policy`. A source points Composer at a remote URL which provides the set of package versions the policy applies to. Adding a source for a dependency policy that does not yet exist will create the policy automatically. + +Built-in dependency policies (`advisories`, `malware`, `abandoned`) do not accept sources and are rejected. To change their settings, use `composer config policy..` instead — see the [policy](06-config.md#policy) config documentation. + +### Usage + +```shell +policy [options] add-source [policy-name] [source-type] [url] +policy [options] add-source [policy-name] [json-source-definition] +``` + +Currently only `url` is supported as `source-type`, and URLs must start with `https://`. + +### Options + +- **--global (-g):** to modify the global `$COMPOSER_HOME/config.json`. +- **--file (-f):** to modify a specific file instead of composer.json. + +### Examples + +```shell +php composer.phar policy add-source my-list url https://example.org/list.json +php composer.phar policy add-source my-list '{"type":"url","url":"https://example.org/list.json"}' +``` + +## create-project + +You can use Composer to create new projects from an existing package. This is +the equivalent of doing a git clone/svn checkout followed by a `composer install` +of the vendors. + +There are several applications for this: + +1. You can deploy application packages. +2. You can check out any package and start developing on patches for example. +3. Projects with multiple developers can use this feature to bootstrap the + initial application for development. + +To create a new project using Composer you can use the `create-project` command. +Pass it a package name, and the directory to create the project in. You can also +provide a version as a third argument, otherwise the latest version is used. + +If the directory does not currently exist, it will be created during installation. + +```shell +php composer.phar create-project composer/hello-world my-project +``` + +It is also possible to run the command without params in a directory with an +existing `composer.json` file to bootstrap a project. + +By default the command checks for the packages on packagist.org. + +### Options + +* **--stability (-s):** Minimum stability of package. Defaults to `stable`. +* **--prefer-install:** There are two ways of downloading a package: `source` + and `dist`. Composer uses `dist` by default. If you pass + `--prefer-install=source` (or `--prefer-source`) Composer will install from + `source` if there is one. This is useful if you want to make a bugfix to a + project and get a local git clone of the dependency directly. + To get the legacy behavior where Composer use `source` automatically for dev + versions of packages, use `--prefer-install=auto`. See also [config.preferred-install](06-config.md#preferred-install). + Passing this flag will override the config value. +* **--repository:** Provide a custom repository to search for the package, + which will be used instead of packagist. Can be either an HTTP URL pointing + to a `composer` repository, a path to a local `packages.json` file, or a + JSON string which similar to what the [repositories](04-schema.md#repositories) + key accepts. You can use this multiple times to configure multiple repositories. +* **--add-repository:** Add the custom repository in the composer.json. If a lock + file is present, it will be deleted and an update will be run instead of an install. +* **--dev:** Install packages listed in `require-dev`. +* **--no-dev:** Disables installation of require-dev packages. +* **--no-scripts:** Disables the execution of the scripts defined in the root + package. +* **--no-progress:** Removes the progress display that can mess with some + terminals or scripts which don't handle backspace characters. +* **--no-secure-http:** Disable the secure-http config option temporarily while + installing the root package. Use at your own risk. Using this flag is a bad + idea. +* **--keep-vcs:** Skip the deletion of the VCS metadata for the created + project. This is mostly useful if you run the command in non-interactive + mode. +* **--remove-vcs:** Force-remove the VCS metadata without prompting. +* **--no-install:** Disables installation of the vendors. +* **--no-audit:** Does not run the audit steps after installation is complete. Also see [COMPOSER_NO_AUDIT](#composer-no-audit). +* **--audit-format:** Audit output format. Must be "table", "plain", "json", or "summary" (default). +* **--no-security-blocking:** DEPRECATED, use `--no-blocking` instead. Allows installing packages with security advisories, that are abandoned or that are still in the cooldown period. Also see [COMPOSER_NO_SECURITY_BLOCKING](#composer-no-security-blocking). +* **--no-blocking:** Disables all policy blocking during this command, including the cooldown period. Also see [COMPOSER_NO_BLOCKING](#composer-no-blocking). +* **--ignore-platform-reqs:** ignore all platform requirements (`php`, `hhvm`, + `lib-*` and `ext-*`) and force the installation even if the local machine does + not fulfill these. + See also the [`platform`](06-config.md#platform) config option. +* **--ignore-platform-req:** ignore a specific platform requirement(`php`, + `hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine + does not fulfill it. Multiple requirements can be ignored via wildcard. +* **--require:** Require additional package(s) to be added to composer.json after + installing the project. If a lock file is present it will be deleted and an + update will be run instead of install. Can be specified multiple times for + multiple packages. Should be in format `foo/bar:1.0.0` format if you want to + specify a constraint. +* **--ask:** Ask the user to provide a target directory for the new project. + +## dump-autoload / dumpautoload + +If you need to update the autoloader because of new classes in a classmap +package for example, you can use `dump-autoload` to do that without having to +go through an install or update. + +Additionally, it can dump an optimized autoloader that converts PSR-0/4 packages +into classmap ones for performance reasons. In large applications with many +classes, the autoloader can take up a substantial portion of every request's +time. Using classmaps for everything is less convenient in development, but +using this option you can still use PSR-0/4 for convenience and classmaps for +performance. + +### Options +* **--optimize (-o):** Convert PSR-0/4 autoloading to classmap to get a faster + autoloader. This is recommended especially for production, but can take + a bit of time to run, so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize`. +* **--apcu:** Use APCu to cache found/not-found classes. +* **--apcu-prefix:** Use a custom prefix for the APCu autoloader cache. + Implicitly enables `--apcu`. +* **--dry-run:** Outputs the operations but will not execute anything. +* **--no-dev:** Disables autoload-dev rules. Composer will by default infer this + automatically according to the last `install` or `update` `--no-dev` state. +* **--dev:** Enables autoload-dev rules. Composer will by default infer this + automatically according to the last `install` or `update` `--no-dev` state. +* **--ignore-platform-reqs:** ignore all `php`, `hhvm`, `lib-*` and `ext-*` + requirements and skip the [platform check](07-runtime.md#platform-check) for these. + See also the [`platform`](06-config.md#platform) config option. +* **--ignore-platform-req:** ignore a specific platform requirement (`php`, `hhvm`, + `lib-*` and `ext-*`) and skip the [platform check](07-runtime.md#platform-check) for it. + Multiple requirements can be ignored via wildcard. +* **--strict-psr:** Return a failed exit code (1) if PSR-4 or PSR-0 mapping errors + are present in the current project (dependencies excluded). Requires `--optimize` to work. +* **--strict-ambiguous:** Return a failed exit code (2) if the same class is found + in multiple files. It also checks if files or folders exist with different case variants that + cause issues on case-insensitive filesystems. Requires `--optimize` to work. + +## clear-cache / clearcache / cc + +Deletes all content from Composer's cache directories. + +### Options + +* **--gc:** Only run garbage collection, not a full cache clear + +## licenses + +Lists the name, version and license of every package installed. Use +`--format=json` to get machine-readable output. + +### Options + +* **--locked:** List licenses from the lock file, regardless of what is currently in vendor dir. +* **--format:** Format of the output: text, json or summary (default: "text"). +* **--no-dev:** Remove dev dependencies from the output. + +## run-script / run + +To run [scripts](articles/scripts.md) manually you can use this command, +give it the script name and optionally any required arguments. + +### Options + +* **--timeout:** Set the script timeout in seconds, or 0 for no timeout. +* **--dev:** Sets the dev mode. +* **--no-dev:** Disable dev mode. +* **--list (-l):** List user defined scripts. + +## exec + +Executes a vendored binary/script. You can execute any command and this will +ensure that the Composer bin-dir is pushed on your PATH before the command +runs. + +### Options + +* **--list (-l):** List the available Composer binaries. + +## diagnose + +If you think you found a bug, or something is behaving strangely, you might +want to run the `diagnose` command to perform automated checks for many common +problems. + +```shell +php composer.phar diagnose +``` + +## archive + +This command is used to generate a zip/tar archive for a given package in a +given version. It can also be used to archive your entire project without +excluded/ignored files. + +```shell +php composer.phar archive vendor/package 2.0.21 --format=zip +``` + +### Options + +* **--format (-f):** Format of the resulting archive: tar, tar.gz, tar.bz2 + or zip (default: "tar"). +* **--dir:** Write the archive to this directory (default: ".") +* **--file:** Write the archive with the given file name. + +## audit + +This command is used to audit the packages you have installed against defined dependency policies, +such as security advisories. It checks for and lists security vulnerability advisories using the +[Packagist.org API](https://packagist.org/apidoc#list-security-advisories) by default or other +repositories if specified in the `repositories` section of `composer.json`. The command also detects +abandoned packages and packages flagged as malware, or packages matched by other dependency policies. + +The audit command determines if there are vulnerable, abandoned, malware packages, or packages matched by other dependency +policies and returns the following exit codes based on the findings: + +* `0` No issues; +* `1` Found packages matching dependency policies or failed due to missing required packages. + +```shell +php composer.phar audit +``` + +### Options + +* **--no-dev:** Disables auditing of require-dev packages. +* **--format (-f):** Audit output format. Must be "table" (default), "plain", "json", or "summary". +* **--locked:** Audit packages from the lock file, regardless of what is currently in vendor dir. +* **--abandoned:** Behavior on abandoned packages. Must be "ignore", "report", + or "fail". See also [config.audit.abandoned](06-config.md#abandoned). Passing this + flag will override the config value and the environment variable. +* **--ignore-severity:** Ignore advisories of a certain severity level. Can be passed one or more + time to ignore multiple severities. + +## help + +To get more information about a certain command, you can use `help`. + +```shell +php composer.phar help install +``` + +## Command-line completion + +Command-line completion can be enabled by running the `composer completion --help` command and +following the instructions. + +## Environment variables + +You can set a number of environment variables that override certain settings. +Whenever possible it is recommended to specify these settings in the `config` +section of `composer.json` instead. It is worth noting that the env vars will +always take precedence over the values specified in `composer.json`. + +### COMPOSER + +By setting the `COMPOSER` env variable it is possible to set the filename of +`composer.json` to something else. + +For example: + +```shell +COMPOSER=composer-other.json php composer.phar install +``` + +The generated lock file will use the same name: `composer-other.lock` in this example. + +### COMPOSER_ALLOW_SUPERUSER + +If set to 1, this env disables the warning about running commands as root/super user. +It also disables automatic clearing of sudo sessions, so you should really only set this +if you use Composer as a super user at all times like in docker containers. + +### COMPOSER_ALLOW_UNSAFE_PHAR_METADATA + +This env var only has an effect on PHP versions before 8.0. On those versions Composer +refuses to read or extract `tar`/`phar` dist archives, because parsing such an archive +is not safe to do with untrusted input on PHP < 8.0. The recommended fix is to upgrade +to PHP 8.0 or newer. If you cannot upgrade and accept the risk, set this to 1 to allow +Composer to process these archives anyway. PHP 8.0+ is unaffected and ignores this setting. + +### COMPOSER_ALLOW_XDEBUG + +If set to 1, this env allows running Composer when the Xdebug extension is enabled, without restarting PHP without it. + +### COMPOSER_AUTH + +The `COMPOSER_AUTH` var allows you to set up authentication as an environment variable. +The contents of the variable should be a JSON formatted object containing [http-basic, +github-oauth, bitbucket-oauth, ... objects as needed](articles/authentication-for-private-packages.md), +and following the +[spec from the config](06-config.md). + +### COMPOSER_BIN_COMPAT + +Override the [`bin-compat`](06-config.md#bin-compat) config setting. + +### COMPOSER_BIN_DIR + +By setting this option you can change the `bin` ([Vendor Binaries](articles/vendor-binaries.md)) +directory to something other than `vendor/bin`. + +### COMPOSER_CACHE_DIR + +The `COMPOSER_CACHE_DIR` var allows you to change the Composer cache directory, +which is also configurable via the [`cache-dir`](06-config.md#cache-dir) option. + +By default, it points to `C:\Users\\AppData\Local\Composer` (or `%LOCALAPPDATA%/Composer`) on Windows. +On \*nix systems that follow the [XDG Base +Directory Specifications](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html), +it points to `$XDG_CACHE_HOME/composer`. On other \*nix systems and on macOS, it points to +`$COMPOSER_HOME/cache`. + +See [Caching Composer dependencies](articles/caching.md) for how to cache it in CI +and container builds. + +### COMPOSER_CAFILE + +By setting this environmental value, you can set a path to a certificate bundle +file to be used during SSL/TLS peer verification. + +### COMPOSER_DISABLE_XDEBUG_WARN + +If set to 1, this env suppresses a warning when Composer is running with the Xdebug extension enabled. + +### COMPOSER_DISCARD_CHANGES + +This env var controls the [`discard-changes`](06-config.md#discard-changes) config option. + +### COMPOSER_FUND + +If set to 0, this env suppresses funding notices when installing. + +### COMPOSER_HOME + +The `COMPOSER_HOME` var allows you to change the Composer home directory. This +is a hidden, global (per-user on the machine) directory that is shared between +all projects. + +Use `composer config --global home` to see the location of the home directory. + +By default, it points to `C:\Users\\AppData\Roaming\Composer` on Windows +and `/Users//.composer` on macOS. On \*nix systems that follow the [XDG Base +Directory Specifications](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html), +it points to `$XDG_CONFIG_HOME/composer`. On other \*nix systems, it points to +`/home//.composer`. + +This directory holds the public keys used to verify Composer downloads during +`self-update`, so it must be writable only by the user that owns the Composer +installation and should be treated as a trusted location. + +#### COMPOSER_HOME/config.json + +You may put a `config.json` file into the location which `COMPOSER_HOME` points +to. Composer will partially (only `config` and `repositories` keys) merge this +configuration with your project's `composer.json` when you run the `install` and +`update` commands. + +This file allows you to set [repositories](05-repositories.md) and +[configuration](06-config.md) for the user's projects. + +In case global configuration matches _local_ configuration, the _local_ +configuration in the project's `composer.json` always wins. + +### COMPOSER_HTACCESS_PROTECT + +Defaults to `1`. If set to `0`, Composer will not create `.htaccess` files in the +Composer home, cache, and data directories. + +### COMPOSER_MEMORY_LIMIT + +If set, the value is used as php's memory_limit. + +### COMPOSER_MIRROR_PATH_REPOS + +If set to 1, this env changes the default path repository strategy to `mirror` instead +of `symlink`. As it is the default strategy being set it can still be overwritten by +repository options. + +### COMPOSER_NO_INTERACTION + +If set to 1, this env var will make Composer behave as if you passed the +`--no-interaction` flag to every command. This can be set on build boxes/CI. + +### COMPOSER_PROCESS_TIMEOUT + +This env var controls the time Composer waits for commands (such as git +commands) to finish executing. The default value is 300 seconds (5 minutes). + +### COMPOSER_ROOT_VERSION + +By setting this var you can specify the version of the root package, if it +cannot be guessed from VCS info and is not present in `composer.json`. + +### COMPOSER_VENDOR_DIR + +By setting this var you can make Composer install the dependencies into a +directory other than `vendor`. + +### COMPOSER_RUNTIME_ENV + +This lets you hint under which environment Composer is running, which can help Composer +work around some environment specific issues. The only value currently supported is +`virtualbox`, which then enables some short `sleep()` calls to wait for the filesystem +to have written files properly before we attempt reading them. You can set the +environment variable if you use Vagrant or VirtualBox and experience issues with files not +being found during installation even though they should be present. + +### http_proxy or HTTP_PROXY +### HTTP_PROXY_REQUEST_FULLURI +### HTTPS_PROXY_REQUEST_FULLURI +### no_proxy or NO_PROXY + +See the [proxy documentation](faqs/how-to-use-composer-behind-a-proxy.md) for more details +on how to use proxy env vars. + +### COMPOSER_MAX_PARALLEL_HTTP + +Set to an integer to configure how many files can be downloaded in parallel. This +defaults to 12 and must be between 1 and 50. If your proxy has issues with +concurrency maybe you want to lower this. Increasing it should generally not result +in performance gains. + +### COMPOSER_MAX_PARALLEL_PROCESSES + +Set to an integer to configure how many processes can be executed in parallel. +This defaults to 10 and must be between 1 and 50. + +### COMPOSER_IPRESOLVE + +Set to `4` or `6` to force IPv4 or IPv6 DNS resolution. This only works when the +curl extension is used for downloads. + +### COMPOSER_SELF_UPDATE_TARGET + +If set, makes the self-update command write the new Composer phar file into that path instead of overwriting itself. Useful for updating Composer on a read-only filesystem. + +### COMPOSER_DISABLE_NETWORK + +If set to `1`, disables network access (best effort). This can be used for debugging or +to run Composer on a plane or a starship with poor connectivity. + +If set to `prime`, GitHub VCS repositories will prime the cache, so it can then be used +fully offline with `1`. + +### COMPOSER_DEBUG_EVENTS + +If set to `1`, outputs information about events being dispatched, which can be +useful for plugin authors to identify what is firing when exactly. + +### COMPOSER_SKIP_SCRIPTS + +Accepts a comma-separated list of event names, e.g. `post-install-cmd` for which scripts execution should be skipped. + +### COMPOSER_NO_AUDIT + +If set to `1`, it is the equivalent of passing the `--no-audit` option to a `require`, `update`, `remove` or `create-project` command. + +### COMPOSER_AUDIT_ABANDONED + +Set to `ignore`, `report` or `fail` to override the [policy.abandoned.audit](06-config.md#audit) config option. Has no effect when `policy.abandoned` is set to `false` in composer.json. + +### COMPOSER_POLICY + +Main dependency policy switch. Set to `0` to disable all dependency policy enforcement on updates, installs and audits, or `1` to enable it. Setting this to `1` will use the policy configuration in the composer.json. If you want to change the config value, use `composer config policy 1` instead. + +When set to `0`, all policy specific overrides below are ignored — the whole dependency policy config is short-circuited to disabled. + +### COMPOSER_NO_BLOCKING + +If set to `1`, it is the equivalent of passing the `--no-blocking` option to a `require`, `update`, `remove`, `install`, or `create-project` command. This disables all policy based blocking of dependencies, including the cooldown period. It overrides the `block` config option for each configured dependency policy e.g. [policy.advisories.block](06-config.md#block). + +### COMPOSER_NO_SECURITY_BLOCKING + +DEPRECATED, use [COMPOSER_NO_BLOCKING](#composer-no-blocking) instead. + +If set to `1`, it is the equivalent of passing the `--no-security-blocking` option to a `require`, `update`, `remove`, `install`, or `create-project` command. This allows installing packages with security advisories, that are abandoned or that are still in the cooldown period. It overrides the config option [policy.advisories.block](06-config.md#block). + +### COMPOSER_POLICY_ADVISORIES_BLOCK + +If set to `1`, enables blocking of packages with security advisories during dependency resolution (equivalent to setting `policy.advisories.block` to `true`). If set to `0`, disables blocking. + +### COMPOSER_POLICY_MALWARE_BLOCK + +If set to `1`, enables blocking of packages flagged as malware during dependency resolution (equivalent to setting `policy.malware.block` to `true`). If set to `0`, disables blocking. + +### COMPOSER_POLICY_ABANDONED_BLOCK + +If set to `1`, enables blocking of abandoned packages during dependency resolution (equivalent to setting `policy.abandoned.block` to `true`). If set to `0`, disables blocking. + +Value takes precedence over the value of the legacy variable [COMPOSER_SECURITY_BLOCKING_ABANDONED](#composer-security-blocking-abandoned) when it's set to a different value. + +### COMPOSER_SECURITY_BLOCKING_ABANDONED + +DEPRECATED, use [COMPOSER_POLICY_ABANDONED_BLOCK](#composer-policy-abandoned-block) instead. + +If set to `1`, enables blocking of abandoned packages during dependency resolution (equivalent to setting `audit.block-abandoned` config to `true`). If set to `0`, disables blocking of abandoned packages. It overrides the config option [audit.block-abandoned](06-config.md#block-abandoned). + +### COMPOSER_POLICY_COOLDOWN_PERIOD + +If set to a value accepted by [policy.cooldown.period](06-config.md#period), such as `"7 days"`, +`"24 hours"` or an integer number of seconds, enables the cooldown policy. Package versions +published within that period are withheld during `composer update`/`require`. Set to `0` to disable. +This overrides only the `period` value from [policy.cooldown](06-config.md#cooldown); any `ignore` +rules configured in composer.json are still respected. + +### COMPOSER_POLICY_COOLDOWN_BLOCK + +If set to `1`, forces cooldown blocking on; if set to `0`, disables it. Overrides the +[policy.cooldown.block](06-config.md#block-3) config option. + +### COMPOSER_NO_DEV + +If set to `1`, it is the equivalent of passing the `--update-no-dev` option to `require` + or the `--no-dev` option to `install` or `update`. You can override this for a single +command by setting `COMPOSER_NO_DEV=0`. + +### COMPOSER_PREFER_STABLE + +If set to `1`, it is the equivalent of passing the `--prefer-stable` option to +`update` or `require`. + +### COMPOSER_PREFER_LOWEST + +If set to `1`, it is the equivalent of passing the `--prefer-lowest` option to +`update` or `require`. + +### COMPOSER_PREFER_DEV_OVER_PRERELEASE + +If set to `1`, when resolving dependencies with both `--prefer-stable` and +`--prefer-lowest` enabled, dev versions are treated as more stable than +alpha/beta/RC versions in cases where no stable release exists. This is useful +to test lowest versions while still preferring branches that may contain +critical fixes over prerelease versions. + +### COMPOSER_MINIMAL_CHANGES + +If set to `1`, it is the equivalent of passing the `--minimal-changes` option to +`update`, `require` or `remove`. + +### COMPOSER_IGNORE_PLATFORM_REQ or COMPOSER_IGNORE_PLATFORM_REQS + +If `COMPOSER_IGNORE_PLATFORM_REQS` set to `1`, it is the equivalent of passing the `--ignore-platform-reqs` argument. +Otherwise, specifying a comma separated list in `COMPOSER_IGNORE_PLATFORM_REQ` will ignore those specific requirements. + +For example, if a development workstation will never run database queries, this can be used to ignore the requirement for the database extensions to be available. If you set `COMPOSER_IGNORE_PLATFORM_REQ=ext-oci8`, then composer will allow packages to be installed even if the `oci8` PHP extension is not enabled. + +### COMPOSER_WITH_DEPENDENCIES + +If set to `1`, it is the equivalent of passing the `--with-dependencies` option to +`update`, `require` or `remove`. + +### COMPOSER_WITH_ALL_DEPENDENCIES + +If set to `1`, it is the equivalent of passing the `--with-all-dependencies` option to +`update`, `require` or `remove`. + +### SHELL_VERBOSITY + +Since Composer uses [symfony/console](https://github.com/symfony/console), +you can define the [verbosity level](https://symfony.com/doc/current/console/verbosity.html). +`SHELL_VERBOSITY=-1` to hide the output of Composer +(this is equivalent to using the CLI option `--quiet`). +Please note that this will apply to every tool that rely on `symfony/console`, +you can set `SHELL_VERBOSITY=0` after the calls to Composer in order to restore the default verbosity level. + +← [Libraries](02-libraries.md) | [Schema](04-schema.md) → diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php new file mode 100644 index 0000000..49d7d8f --- /dev/null +++ b/src/Composer/Command/ConfigCommand.php @@ -0,0 +1,1344 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Pcre\Preg; +use Composer\Policy\CooldownPolicyConfig; +use Composer\Policy\IgnoreUnreachable; +use Composer\Policy\ListPolicyConfig; +use Composer\Policy\PolicyConfig; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\Silencer; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Config; +use Composer\Config\JsonConfigSource; +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Semver\VersionParser; +use Composer\Package\BasePackage; + +/** + * @author Joshua Estes + * @author Jordi Boggiano + */ +class ConfigCommand extends BaseConfigCommand +{ + /** + * List of additional configurable package-properties + * + * @var string[] + */ + protected const CONFIGURABLE_PACKAGE_PROPERTIES = [ + 'name', + 'type', + 'description', + 'homepage', + 'version', + 'minimum-stability', + 'prefer-stable', + 'keywords', + 'license', + 'repositories', + 'suggest', + 'extra', + ]; + + /** + * @var JsonFile + */ + protected $authConfigFile; + + /** + * @var JsonConfigSource + */ + protected $authConfigSource; + + protected function configure(): void + { + $this + ->setName('config') + ->setDescription('Sets config options') + ->setDefinition([ + new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'), + new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'), + new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'), + new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'), + new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'), + new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'), + new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'), + new InputOption('json', 'j', InputOption::VALUE_NONE, 'JSON decode the setting value, to be used with extra.* keys'), + new InputOption('merge', 'm', InputOption::VALUE_NONE, 'Merge the setting value with the current value, to be used with extra.* or audit.ignore[-abandoned] keys in combination with --json'), + new InputOption('append', null, InputOption::VALUE_NONE, 'When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)'), + new InputOption('source', null, InputOption::VALUE_NONE, 'Display where the config value is loaded from'), + new InputArgument('setting-key', null, 'Setting key', null, $this->suggestSettingKeys()), + new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), + ]) + ->setHelp( + <<%command.full_name% bin-dir bin/ + +To read a config setting: + + %command.full_name% bin-dir + Outputs: bin + +To edit the global config.json file: + + %command.full_name% --global + +To add a repository: + + %command.full_name% repositories.foo vcs https://bar.com + +To remove a repository (repo is a short alias for repositories): + + %command.full_name% --unset repo.foo + +To disable packagist.org: + + %command.full_name% repo.packagist.org false + +You can alter repositories in the global config.json file by passing in the +--global option. + +To add or edit suggested packages you can use: + + %command.full_name% suggest.package reason for the suggestion + +To add or edit extra properties you can use: + + %command.full_name% extra.property value + +Or to add a complex value you can use json with: + + %command.full_name% extra.property --json '{"foo":true, "bar": []}' + +To edit the file in an external editor: + + %command.full_name% --editor + +To choose your editor you can set the "EDITOR" env variable. + +To get a list of configuration values in the file: + + %command.full_name% --list + +You can always pass more than one option. As an example, if you want to edit the +global config.json file. + + %command.full_name% --editor --global + +Read more at https://getcomposer.org/doc/03-cli.md#config +EOT + ) + ; + } + + /** + * @throws \Exception + */ + protected function initialize(InputInterface $input, OutputInterface $output): void + { + parent::initialize($input, $output); + + $authConfigFile = $this->getAuthConfigFile($input, $this->config); + + $this->authConfigFile = new JsonFile($authConfigFile, null, $this->getIO()); + $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true); + + // Initialize the global file if it's not there, ignoring any warnings or notices + if ($input->getOption('global') && !$this->authConfigFile->exists()) { + touch($this->authConfigFile->getPath()); + $this->authConfigFile->write(['bitbucket-oauth' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject, 'gitlab-token' => new \ArrayObject, 'http-basic' => new \ArrayObject, 'bearer' => new \ArrayObject, 'forgejo-token' => new \ArrayObject()]); + Silencer::call('chmod', $this->authConfigFile->getPath(), 0600); + } + } + + /** + * @throws \Seld\JsonLint\ParsingException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + // Open file in editor + if (true === $input->getOption('editor')) { + $editor = Platform::getEnv('EDITOR'); + if (false === $editor || '' === $editor) { + if (Platform::isWindows()) { + $editor = 'notepad'; + } else { + foreach (['editor', 'vim', 'vi', 'nano', 'pico', 'ed'] as $candidate) { + if (exec('which '.$candidate)) { + $editor = $candidate; + break; + } + } + } + } else { + $editor = escapeshellarg($editor); + } + + $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath(); + system($editor . ' ' . escapeshellarg($file) . (Platform::isWindows() ? '' : ' > `tty`')); + + return 0; + } + + if (false === $input->getOption('global')) { + $this->config->merge($this->configFile->read(), $this->configFile->getPath()); + $this->config->merge(['config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : []], $this->authConfigFile->getPath()); + } + + $this->getIO()->loadConfiguration($this->config); + + // List the configuration of the file settings + if (true === $input->getOption('list')) { + $this->listConfiguration($this->config->all(), $this->config->raw(), $output, null, $input->getOption('source')); + + return 0; + } + + $settingKey = $input->getArgument('setting-key'); + if (!is_string($settingKey)) { + return 0; + } + + // If the user enters in a config variable, parse it and save to file + if ([] !== $input->getArgument('setting-value') && $input->getOption('unset')) { + throw new \RuntimeException('You can not combine a setting value with --unset'); + } + + // show the value if no value is provided + if ([] === $input->getArgument('setting-value') && !$input->getOption('unset')) { + $properties = self::CONFIGURABLE_PACKAGE_PROPERTIES; + $propertiesDefaults = [ + 'type' => 'library', + 'description' => '', + 'homepage' => '', + 'minimum-stability' => 'stable', + 'prefer-stable' => false, + 'keywords' => [], + 'license' => [], + 'suggest' => [], + 'extra' => [], + ]; + $rawData = $this->configFile->read(); + $data = $this->config->all(); + $source = $this->config->getSourceOfValue($settingKey); + + if (Preg::isMatch('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) { + if (!isset($matches[1])) { + $value = $data['repositories'] ?? []; + } else { + if (!isset($data['repositories'][$matches[1]])) { + throw new \InvalidArgumentException('There is no '.$matches[1].' repository defined'); + } + + $value = $data['repositories'][$matches[1]]; + } + } elseif (strpos($settingKey, '.')) { + $bits = explode('.', $settingKey); + if ($bits[0] === 'extra' || $bits[0] === 'suggest') { + $data = $rawData; + } else { + $data = $data['config']; + } + $match = false; + foreach ($bits as $bit) { + $key = isset($key) ? $key.'.'.$bit : $bit; + $match = false; + if (isset($data[$key])) { + $match = true; + $data = $data[$key]; + unset($key); + } + } + + if (!$match) { + throw new \RuntimeException($settingKey.' is not defined.'); + } + + $value = $data; + } elseif (isset($data['config'][$settingKey])) { + $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS); + // ensure we get {} output for properties which are objects + if ($value === []) { + $schema = JsonFile::parseJson((string) file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH)); + if ( + isset($schema['properties']['config']['properties'][$settingKey]['type']) + && in_array('object', (array) $schema['properties']['config']['properties'][$settingKey]['type'], true) + ) { + $value = new \stdClass; + } + } + } elseif (isset($rawData[$settingKey]) && in_array($settingKey, $properties, true)) { + $value = $rawData[$settingKey]; + $source = $this->configFile->getPath(); + } elseif (isset($propertiesDefaults[$settingKey])) { + $value = $propertiesDefaults[$settingKey]; + $source = 'defaults'; + } else { + throw new \RuntimeException($settingKey.' is not defined'); + } + + if (is_array($value) || is_object($value) || is_bool($value)) { + $value = JsonFile::encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + $sourceOfConfigValue = ''; + if ($input->getOption('source')) { + $sourceOfConfigValue = ' (' . $source . ')'; + } + + $this->getIO()->write($value . $sourceOfConfigValue, true, IOInterface::QUIET); + + return 0; + } + + $values = $input->getArgument('setting-value'); // what the user is trying to add/change + + $booleanValidator = static function ($val): bool { + return in_array($val, ['true', 'false', '1', '0'], true); + }; + $booleanNormalizer = static function ($val): bool { + return $val !== 'false' && (bool) $val; + }; + $auditValidator = static function ($val): bool { + return in_array($val, [ListPolicyConfig::AUDIT_IGNORE, ListPolicyConfig::AUDIT_REPORT, ListPolicyConfig::AUDIT_FAIL], true); + }; + $blockScopeValidator = static function ($val): bool { + return in_array($val, ListPolicyConfig::AVAILABLE_BLOCK_SCOPES, true); + }; + $keepAsIsNormalizer = static function ($val) { + return $val; + }; + + // handle config values + $uniqueConfigValues = [ + 'process-timeout' => ['is_numeric', 'intval'], + 'use-include-path' => [$booleanValidator, $booleanNormalizer], + 'use-github-api' => [$booleanValidator, $booleanNormalizer], + 'preferred-install' => [ + static function ($val): bool { + return in_array($val, ['auto', 'source', 'dist'], true); + }, + static function ($val) { + return $val; + }, + ], + 'gitlab-protocol' => [ + static function ($val): bool { + return in_array($val, ['git', 'http', 'https'], true); + }, + static function ($val) { + return $val; + }, + ], + 'store-auths' => [ + static function ($val): bool { + return in_array($val, ['true', 'false', 'prompt'], true); + }, + static function ($val) { + if ('prompt' === $val) { + return 'prompt'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'notify-on-install' => [$booleanValidator, $booleanNormalizer], + 'vendor-dir' => ['is_string', static function ($val) { + return $val; + }], + 'bin-dir' => ['is_string', static function ($val) { + return $val; + }], + 'archive-dir' => ['is_string', static function ($val) { + return $val; + }], + 'archive-format' => ['is_string', static function ($val) { + return $val; + }], + 'data-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-files-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-repo-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-vcs-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-ttl' => ['is_numeric', 'intval'], + 'cache-files-ttl' => ['is_numeric', 'intval'], + 'cache-files-maxsize' => [ + static function ($val): bool { + return Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val); + }, + static function ($val) { + return $val; + }, + ], + 'bin-compat' => [ + static function ($val): bool { + return in_array($val, ['auto', 'full', 'proxy', 'symlink']); + }, + static function ($val) { + return $val; + }, + ], + 'discard-changes' => [ + static function ($val): bool { + return in_array($val, ['stash', 'true', 'false', '1', '0'], true); + }, + static function ($val) { + if ('stash' === $val) { + return 'stash'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'autoloader-suffix' => ['is_string', static function ($val) { + return $val === 'null' ? null : $val; + }], + 'sort-packages' => [$booleanValidator, $booleanNormalizer], + 'optimize-autoloader' => [$booleanValidator, $booleanNormalizer], + 'strict-psr-autoloader' => [$booleanValidator, $booleanNormalizer], + 'classmap-authoritative' => [$booleanValidator, $booleanNormalizer], + 'apcu-autoloader' => [$booleanValidator, $booleanNormalizer], + 'prepend-autoloader' => [$booleanValidator, $booleanNormalizer], + 'update-with-minimal-changes' => [$booleanValidator, $booleanNormalizer], + 'disable-tls' => [$booleanValidator, $booleanNormalizer], + 'secure-http' => [$booleanValidator, $booleanNormalizer], + 'source-fallback' => [$booleanValidator, $booleanNormalizer], + 'bump-after-update' => [ + static function ($val): bool { + return in_array($val, ['dev', 'no-dev', 'true', 'false', '1', '0'], true); + }, + static function ($val) { + if ('dev' === $val || 'no-dev' === $val) { + return $val; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'cafile' => [ + static function ($val): bool { + return file_exists($val) && Filesystem::isReadable($val); + }, + static function ($val) { + return $val === 'null' ? null : $val; + }, + ], + 'capath' => [ + static function ($val): bool { + return is_dir($val) && Filesystem::isReadable($val); + }, + static function ($val) { + return $val === 'null' ? null : $val; + }, + ], + 'github-expose-hostname' => [$booleanValidator, $booleanNormalizer], + 'htaccess-protect' => [$booleanValidator, $booleanNormalizer], + 'lock' => [$booleanValidator, $booleanNormalizer], + 'allow-plugins' => [$booleanValidator, $booleanNormalizer], + 'platform-check' => [ + static function ($val): bool { + return in_array($val, ['php-only', 'true', 'false', '1', '0'], true); + }, + static function ($val) { + if ('php-only' === $val) { + return 'php-only'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'use-parent-dir' => [ + static function ($val): bool { + return in_array($val, ['true', 'false', 'prompt'], true); + }, + static function ($val) { + if ('prompt' === $val) { + return 'prompt'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'audit.abandoned' => [$auditValidator, $keepAsIsNormalizer], + 'audit.ignore-unreachable' => [$booleanValidator, $booleanNormalizer], + 'audit.block-insecure' => [$booleanValidator, $booleanNormalizer], + 'audit.block-abandoned' => [$booleanValidator, $booleanNormalizer], + 'policy.advisories.block' => [$booleanValidator, $booleanNormalizer], + 'policy.advisories.audit' => [$auditValidator, $keepAsIsNormalizer], + 'policy.malware.block' => [$booleanValidator, $booleanNormalizer], + 'policy.malware.block-scope' => [$blockScopeValidator, $keepAsIsNormalizer], + 'policy.malware.audit' => [$auditValidator, $keepAsIsNormalizer], + 'policy.abandoned.block' => [$booleanValidator, $booleanNormalizer], + 'policy.abandoned.audit' => [$auditValidator, $keepAsIsNormalizer], + 'policy.cooldown.block' => [$booleanValidator, $booleanNormalizer], + 'policy.cooldown.audit' => [$auditValidator, $keepAsIsNormalizer], + 'policy.cooldown.period' => [ + static function ($val): bool { + if ($val === 'null' || $val === '') { + return true; + } + try { + CooldownPolicyConfig::parseDuration($val); + + return true; + } catch (\RuntimeException $e) { + return false; + } + }, + static function ($val) { + if ($val === 'null' || $val === '') { + return null; + } + + return is_numeric($val) ? (int) $val : $val; + }, + ], + 'policy.ignore-unreachable' => [$booleanValidator, $booleanNormalizer], + ]; + $multiConfigValues = [ + 'github-protocols' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + foreach ($vals as $val) { + if (!in_array($val, ['git', 'https', 'ssh'])) { + return 'valid protocols include: git, https, ssh'; + } + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'github-domains' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'gitlab-domains' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'audit.ignore-severity' => $ignoreSeverityValidatorAndNormalizer = [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + foreach ($vals as $val) { + if (!in_array($val, ['low', 'medium', 'high', 'critical'], true)) { + return 'valid severities include: low, medium, high, critical'; + } + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'policy.advisories.ignore-severity' => $ignoreSeverityValidatorAndNormalizer, + 'policy.malware.ignore-source' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + foreach ($vals as $val) { + if (!is_string($val)) { + return 'string values expected'; + } + } + + return true; + }, + $keepAsIsNormalizer, + ], + ]; + + // unsetting any audit/policy.* key resolves to the same removeConfigSetting call + // (JsonConfigSource handles the cascade-cleanup of empty ancestors for policy.*), + // so handle them all in one place rather than repeating the check in each branch below. + if ($input->getOption('unset') && ($settingKey === 'audit' || $settingKey === 'policy' || strpos($settingKey, 'policy.') === 0)) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + // handle policy.*.ignore / policy.advisories.ignore-id with --json + --merge support (mirrors audit.ignore) + $policyJsonMergeKeys = ['policy.advisories.ignore-id']; + foreach (PolicyConfig::BUILTIN_LIST_NAMES as $listName) { + $policyJsonMergeKeys[] = 'policy.'.$listName.'.ignore'; + } + $nonCustomPolicyKeys = array_merge(PolicyConfig::BUILTIN_LIST_NAMES, PolicyConfig::NON_LIST_KEYS); + $nonCustomAlternation = implode('|', array_map(static function (string $name): string { + return preg_quote($name, '/'); + }, $nonCustomPolicyKeys)); + $isCustomPolicyIgnore = Preg::isMatch('/^policy\.(?!(?:'.$nonCustomAlternation.')$)([^.]+)\.ignore$/', $settingKey, $customIgnoreMatches); + if (in_array($settingKey, $policyJsonMergeKeys, true) || $isCustomPolicyIgnore) { + if ($isCustomPolicyIgnore) { + $reservedError = PolicyConfig::getFutureReservedListNameError((string) $customIgnoreMatches[1]); + if ($reservedError !== null) { + throw new \RuntimeException('Invalid dependency policy name: '.$reservedError); + } + } + + $value = $values; + if ($input->getOption('json')) { + $value = JsonFile::parseJson($values[0]); + if (!is_array($value)) { + throw new \RuntimeException('Expected an array or object for '.$settingKey); + } + } + + if ($input->getOption('merge')) { + $currentConfig = $this->configFile->read(); + $currentValue = $currentConfig['config'] ?? null; + foreach (explode('.', $settingKey) as $bit) { + if (!is_array($currentValue) || !isset($currentValue[$bit])) { + $currentValue = null; + break; + } + $currentValue = $currentValue[$bit]; + } + + if ($currentValue !== null && is_array($currentValue) && is_array($value)) { + if (array_is_list($currentValue) && array_is_list($value)) { + $value = array_merge($currentValue, $value); + } elseif (!array_is_list($currentValue) && !array_is_list($value)) { + $value = $value + $currentValue; + } else { + throw new \RuntimeException('Cannot merge array and object for '.$settingKey); + } + } + } + + $this->configSource->addConfigSetting($settingKey, $value); + + return 0; + } + + // handle policy.ignore-unreachable array form. Accepts: + // --json '["update","install"]' (canonical JSON) + // policy.ignore-unreachable update install (positional enum values) + // The boolean form (true/false) falls through to $uniqueConfigValues. + if ($settingKey === 'policy.ignore-unreachable') { + if ($input->getOption('json')) { + $value = JsonFile::parseJson($values[0]); + if (!is_array($value)) { + throw new \RuntimeException('Expected a boolean or array for '.$settingKey); + } + foreach ($value as $v) { + if (!in_array($v, IgnoreUnreachable::SCOPES, true)) { + throw new \RuntimeException('valid values for '.$settingKey.' include: '.implode(', ', IgnoreUnreachable::SCOPES)); + } + } + + $this->configSource->addConfigSetting($settingKey, $value); + + return 0; + } + + // Positional enum values: accept e.g. `composer config policy.ignore-unreachable update install`. + // Triggers only when the first value is one of the allowed scope strings, so `true`/`false` still + // fall through to the boolean validator below. + if (count($values) > 0 && in_array($values[0], IgnoreUnreachable::SCOPES, true)) { + foreach ($values as $v) { + if (!in_array($v, IgnoreUnreachable::SCOPES, true)) { + throw new \RuntimeException('valid values for '.$settingKey.' include: '.implode(', ', IgnoreUnreachable::SCOPES)); + } + } + + $this->configSource->addConfigSetting($settingKey, $values); + + return 0; + } + } + + // handle policy. = true|false (enable/disable an entire list) for built-in and custom lists; + // policy.ignore-unreachable is excluded because it already has its own scalar/array handling via $uniqueConfigValues + if (Preg::isMatch('/^policy\.([^.]+)$/', $settingKey, $matches) + && !in_array($matches[1], PolicyConfig::NON_LIST_KEYS, true) + ) { + $reservedError = PolicyConfig::getFutureReservedListNameError($matches[1]); + if ($reservedError !== null) { + throw new \RuntimeException('Invalid dependency policy name: '.$reservedError); + } + if (!$booleanValidator($values[0])) { + throw new \RuntimeException(sprintf('"%s" is an invalid value for %s, expected a boolean', $values[0], $settingKey)); + } + $this->configSource->addConfigSetting($settingKey, $booleanNormalizer($values[0])); + + return 0; + } + + // handle custom policy lists: policy..block / policy..block-scope / policy..audit + if (Preg::isMatch('/^policy\.([^.]+)\.(block|block-scope|audit)$/', $settingKey, $matches) + && !in_array($matches[1], $nonCustomPolicyKeys, true) + ) { + $reservedError = PolicyConfig::getFutureReservedListNameError($matches[1]); + if ($reservedError !== null) { + throw new \RuntimeException('Invalid dependency policy name: '.$reservedError); + } + if ($matches[2] === 'block') { + if (!$booleanValidator($values[0])) { + throw new \RuntimeException(sprintf('"%s" is an invalid value for %s, expected a boolean', $values[0], $settingKey)); + } + $this->configSource->addConfigSetting($settingKey, $booleanNormalizer($values[0])); + } elseif ($matches[2] === 'block-scope') { + if (!$blockScopeValidator($values[0])) { + throw new \RuntimeException(sprintf('"%s" is an invalid value for %s, must be one of: all, update, install', $values[0], $settingKey)); + } + $this->configSource->addConfigSetting($settingKey, $values[0]); + } else { + if (!$auditValidator($values[0])) { + throw new \RuntimeException(sprintf('"%s" is an invalid value for %s, must be one of: ignore, report, fail', $values[0], $settingKey)); + } + $this->configSource->addConfigSetting($settingKey, $values[0]); + } + + return 0; + } + + // policy..sources is managed via the dedicated `composer policy` command rather than + // `composer config`, since sources have structured shape (type/url) and validation rules. + if (Preg::isMatch('/^policy\.([^.]+)\.sources$/', $settingKey, $matches)) { + throw new \RuntimeException(sprintf( + 'Setting dependency policy sources is not supported by `composer config`. Use `composer policy add-source %s url ` instead.', + $matches[1] + )); + } + + if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) { + if ($settingKey === 'disable-tls' && $this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); + } + + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + if (isset($uniqueConfigValues[$settingKey])) { + $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting'); + + return 0; + } + if (isset($multiConfigValues[$settingKey])) { + $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting'); + + return 0; + } + // handle preferred-install per-package config + if (Preg::isMatch('/^preferred-install\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + [$validator] = $uniqueConfigValues['preferred-install']; + if (!$validator($values[0])) { + throw new \RuntimeException('Invalid value for '.$settingKey.'. Should be one of: auto, source, or dist'); + } + + $this->configSource->addConfigSetting($settingKey, $values[0]); + + return 0; + } + + // handle allow-plugins config setting elements true or false to add/remove + if (Preg::isMatch('{^allow-plugins\.([a-zA-Z0-9/*-]+)}', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + if (true !== $booleanValidator($values[0])) { + throw new \RuntimeException(sprintf( + '"%s" is an invalid value', + $values[0] + )); + } + + $normalizedValue = $booleanNormalizer($values[0]); + + $this->configSource->addConfigSetting($settingKey, $normalizedValue); + + return 0; + } + + // handle properties + $uniqueProps = [ + 'name' => ['is_string', static function ($val) { + return $val; + }], + 'type' => ['is_string', static function ($val) { + return $val; + }], + 'description' => ['is_string', static function ($val) { + return $val; + }], + 'homepage' => ['is_string', static function ($val) { + return $val; + }], + 'version' => ['is_string', static function ($val) { + return $val; + }], + 'minimum-stability' => [ + static function ($val): bool { + return isset(BasePackage::STABILITIES[VersionParser::normalizeStability($val)]); + }, + static function ($val): string { + return VersionParser::normalizeStability($val); + }, + ], + 'prefer-stable' => [$booleanValidator, $booleanNormalizer], + ]; + $multiProps = [ + 'keywords' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'license' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + ]; + + if ($input->getOption('global') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]) || strpos($settingKey, 'extra.') === 0)) { + throw new \InvalidArgumentException('The ' . $settingKey . ' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json'); + } + if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + if (isset($uniqueProps[$settingKey])) { + $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty'); + + return 0; + } + if (isset($multiProps[$settingKey])) { + $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty'); + + return 0; + } + + // handle repositories + if (Preg::isMatchStrictGroups('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeRepository($matches[1]); + + return 0; + } + + if (2 === count($values)) { + $this->configSource->addRepository($matches[1], [ + 'type' => $values[0], + 'url' => $values[1], + ], $input->getOption('append')); + + return 0; + } + + if (1 === count($values)) { + $value = strtolower($values[0]); + if (true === $booleanValidator($value)) { + if (false === $booleanNormalizer($value)) { + $this->configSource->addRepository($matches[1], false, $input->getOption('append')); + + return 0; + } + } else { + $value = JsonFile::parseJson($values[0]); + $this->configSource->addRepository($matches[1], $value, $input->getOption('append')); + + return 0; + } + } + + throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com'); + } + + // handle extra + if (Preg::isMatch('/^extra\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + $value = $values[0]; + if ($input->getOption('json')) { + $value = JsonFile::parseJson($value); + if ($input->getOption('merge')) { + $currentValue = $this->configFile->read(); + $bits = explode('.', $settingKey); + foreach ($bits as $bit) { + $currentValue = $currentValue[$bit] ?? null; + } + if (is_array($currentValue) && is_array($value)) { + if (array_is_list($currentValue) && array_is_list($value)) { + $value = array_merge($currentValue, $value); + } else { + $value = $value + $currentValue; + } + } + } + } + $this->configSource->addProperty($settingKey, $value); + + return 0; + } + + // handle suggest + if (Preg::isMatch('/^suggest\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + $this->configSource->addProperty($settingKey, implode(' ', $values)); + + return 0; + } + + // handle unsetting extra/suggest + if (in_array($settingKey, ['suggest', 'extra'], true) && $input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + // handle platform + if (Preg::isMatch('/^platform\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + $this->configSource->addConfigSetting($settingKey, $values[0] === 'false' ? false : $values[0]); + + return 0; + } + + // handle unsetting platform + if ($settingKey === 'platform' && $input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + // handle audit.ignore and audit.ignore-abandoned with --merge support + if (in_array($settingKey, ['audit.ignore', 'audit.ignore-abandoned'], true)) { + if ($input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + $value = $values; + if ($input->getOption('json')) { + $value = JsonFile::parseJson($values[0]); + if (!is_array($value)) { + throw new \RuntimeException('Expected an array or object for '.$settingKey); + } + } + + if ($input->getOption('merge')) { + $currentConfig = $this->configFile->read(); + $currentValue = $currentConfig['config']['audit'][str_replace('audit.', '', $settingKey)] ?? null; + + if ($currentValue !== null && is_array($currentValue) && is_array($value)) { + if (array_is_list($currentValue) && array_is_list($value)) { + // Both are lists, merge them + $value = array_merge($currentValue, $value); + } elseif (!array_is_list($currentValue) && !array_is_list($value)) { + // Both are associative arrays (objects), merge them + $value = $value + $currentValue; + } else { + throw new \RuntimeException('Cannot merge array and object for '.$settingKey); + } + } + } + + $this->configSource->addConfigSetting($settingKey, $value); + + return 0; + } + + // handle auth + if (Preg::isMatch('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|custom-headers|bearer|forgejo-token)\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + + return 0; + } + + if ($matches[1] === 'bitbucket-oauth') { + if (2 !== count($values)) { + throw new \RuntimeException('Expected two arguments (consumer-key, consumer-secret), got '.count($values)); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['consumer-key' => $values[0], 'consumer-secret' => $values[1]]); + } elseif ($matches[1] === 'gitlab-token' && 2 === count($values)) { + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'token' => $values[1]]); + } elseif (in_array($matches[1], ['github-oauth', 'gitlab-oauth', 'gitlab-token', 'bearer'], true)) { + if (1 !== count($values)) { + throw new \RuntimeException('Too many arguments, expected only one token'); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]); + } elseif ($matches[1] === 'http-basic') { + if (2 !== count($values)) { + throw new \RuntimeException('Expected two arguments (username, password), got '.count($values)); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'password' => $values[1]]); + } elseif ($matches[1] === 'custom-headers') { + if (count($values) === 0) { + throw new \RuntimeException('Expected at least one argument (header), got none'); + } + + // Validate headers format + $formattedHeaders = []; + foreach ($values as $header) { + if (!is_string($header)) { + throw new \RuntimeException('Headers must be strings in "Header-Name: Header-Value" format'); + } + + // Check if the header is in correct "Name: Value" format + if (!Preg::isMatch('/^[^:]+:\s*.+$/', $header, $headerParts)) { + throw new \RuntimeException('Header "' . $header . '" is not in "Header-Name: Header-Value" format'); + } + + $formattedHeaders[] = $header; + } + + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $formattedHeaders); + } elseif ($matches[1] === 'forgejo-token') { + if (2 !== count($values)) { + throw new \RuntimeException('Expected two arguments (username, access token), got '.count($values)); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'token' => $values[1]]); + } + + return 0; + } + + // handle script + if (Preg::isMatch('/^scripts\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]); + + return 0; + } + + // handle unsetting other top level properties + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command'); + } + + /** + * @param array{callable, callable} $callbacks Validator and normalizer callbacks + * @param array $values + */ + protected function handleSingleValue(string $key, array $callbacks, array $values, string $method): void + { + [$validator, $normalizer] = $callbacks; + if (1 !== count($values)) { + throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300'); + } + + if (true !== $validation = $validator($values[0])) { + throw new \RuntimeException(sprintf( + '"%s" is an invalid value'.($validation ? ' ('.$validation.')' : ''), + $values[0] + )); + } + + $normalizedValue = $normalizer($values[0]); + + if ($key === 'disable-tls') { + if (!$normalizedValue && $this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); + } elseif ($normalizedValue && !$this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection disabled.'); + } + } + + call_user_func([$this->configSource, $method], $key, $normalizedValue); + } + + /** + * @param array{callable, callable} $callbacks Validator and normalizer callbacks + * @param array $values + */ + protected function handleMultiValue(string $key, array $callbacks, array $values, string $method): void + { + [$validator, $normalizer] = $callbacks; + if (true !== $validation = $validator($values)) { + throw new \RuntimeException(sprintf( + '%s is an invalid value'.($validation ? ' ('.$validation.')' : ''), + json_encode($values) + )); + } + + call_user_func([$this->configSource, $method], $key, $normalizer($values)); + } + + /** + * Display the contents of the file in a pretty formatted way + * + * @param array $contents + * @param array $rawContents + */ + protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, ?string $k = null, bool $showSource = false): void + { + $origK = $k; + $io = $this->getIO(); + foreach ($contents as $key => $value) { + if ($k === null && !in_array($key, ['config', 'repositories'])) { + continue; + } + + $rawVal = $rawContents[$key] ?? null; + + if (is_array($value) && (!is_numeric(key($value)) || ($key === 'repositories' && null === $k))) { + $k .= Preg::replace('{^config\.}', '', $key . '.'); + $this->listConfiguration($value, $rawVal, $output, $k, $showSource); + $k = $origK; + + continue; + } + + if (is_array($value)) { + $value = array_map(static function ($val) { + return is_array($val) ? json_encode($val) : $val; + }, $value); + + $value = '['.implode(', ', $value).']'; + } + + if (is_bool($value)) { + $value = var_export($value, true); + } + + $source = ''; + if ($showSource) { + $source = ' (' . $this->config->getSourceOfValue($k . $key) . ')'; + } + + if (null !== $k && 0 === strpos($k, 'repositories')) { + $link = 'https://getcomposer.org/doc/05-repositories.md'; + } else { + $id = Preg::replace('{\..*$}', '', $k === '' || $k === null ? (string) $key : $k); + $id = Preg::replace('{[^a-z0-9]}i', '-', strtolower(trim($id))); + $id = Preg::replace('{-+}', '-', $id); + $link = 'https://getcomposer.org/doc/06-config.md#' . $id; + } + if (is_string($rawVal) && $rawVal !== $value) { + $io->write('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')' . $source, true, IOInterface::QUIET); + } else { + $io->write('[' . $k . $key . '] ' . $value . '' . $source, true, IOInterface::QUIET); + } + } + } + + /** + * Suggest setting-keys, while taking given options in account. + */ + private function suggestSettingKeys(): \Closure + { + return function (CompletionInput $input): array { + if ($input->getOption('list') || $input->getOption('editor') || $input->getOption('auth')) { + return []; + } + + // initialize configuration + $config = Factory::createConfig(); + + // load configuration + $configFile = new JsonFile($this->getComposerConfigFile($input, $config)); + if ($configFile->exists()) { + $config->merge($configFile->read(), $configFile->getPath()); + } + + // load auth-configuration + $authConfigFile = new JsonFile($this->getAuthConfigFile($input, $config)); + if ($authConfigFile->exists()) { + $config->merge(['config' => $authConfigFile->read()], $authConfigFile->getPath()); + } + + // collect all configuration setting-keys + $rawConfig = $config->raw(); + $keys = array_merge( + $this->flattenSettingKeys($rawConfig['config']), + $this->flattenSettingKeys($rawConfig['repositories'], 'repositories.') + ); + + // if unsetting … + if ($input->getOption('unset')) { + // … keep only the currently customized setting-keys … + $sources = [$configFile->getPath(), $authConfigFile->getPath()]; + $keys = array_filter( + $keys, + static function (string $key) use ($config, $sources): bool { + return in_array($config->getSourceOfValue($key), $sources, true); + } + ); + + // … else if showing or setting a value … + } else { + // … add all configurable package-properties, no matter if it exist + $keys = array_merge($keys, self::CONFIGURABLE_PACKAGE_PROPERTIES); + + // it would be nice to distinguish between showing and setting + // a value, but that makes the implementation much more complex + // and partially impossible because symfony's implementation + // does not complete arguments followed by other arguments + } + + // add all existing configurable package-properties + if ($configFile->exists()) { + $properties = array_filter( + $configFile->read(), + static function (string $key): bool { + return in_array($key, self::CONFIGURABLE_PACKAGE_PROPERTIES, true); + }, + ARRAY_FILTER_USE_KEY + ); + + $keys = array_merge( + $keys, + $this->flattenSettingKeys($properties) + ); + } + + // filter settings-keys by completion value + $completionValue = $input->getCompletionValue(); + + if ($completionValue !== '') { + $keys = array_filter( + $keys, + static function (string $key) use ($completionValue): bool { + return str_starts_with($key, $completionValue); + } + ); + } + + sort($keys); + + return array_unique($keys); + }; + } + + /** + * build a flat list of dot-separated setting-keys from given config + * + * @param array $config + * @return string[] + */ + private function flattenSettingKeys(array $config, string $prefix = ''): array + { + $keys = []; + foreach ($config as $key => $value) { + $keys[] = [$prefix . $key]; + // array-lists must not be added to completion + // sub-keys of repository-keys must not be added to completion + if (is_array($value) && !array_is_list($value) && $prefix !== 'repositories.') { + $keys[] = $this->flattenSettingKeys($value, $prefix . $key . '.'); + } + } + + return array_merge(...$keys); + } +}