composer で replace してるパッケージを削除して元のパッケージに戻そうとしたら面倒だった

とあるプロジェクトで phpstan/phpstan-shim を過去に一時的に入れていたことがあって、もう使わないので削除しようとしたところ、composer.json からは消えていたものの実際には削除出来ていませんでした。

こんな感じの依存関係になっています(これは説明のための例なので実際のプロジェクトのものではありません)。

$ composer why phpstan/phpstan-shim -t
phpstan/phpstan-shim 0.11.19 PHPStan Phar distribution
├──phpstan/phpstan-shim 0.11.19 (replaces nikic/php-parser ^4.0.2)
│  ├──jeremeamia/superclosure 2.4.0 (requires nikic/php-parser ^1.2|^2.0|^3.0|^4.0)
│  │  └──php-di/php-di 6.1.0 (requires jeremeamia/superclosure ^2.0)
│  │     └──__root__ dev-master (requires php-di/php-di 6.1.0)
│  ├──php-di/php-di 6.1.0 (requires nikic/php-parser ^2.0|^3.0|^4.0)
│  │  └──__root__ dev-master (requires php-di/php-di 6.1.0)
│  ├──phpstan/phpstan-shim 0.11.19 (replaces nikic/php-parser ^4.0.2) (circular dependency aborted here)
│  ├──phpunit/php-code-coverage 9.1.4 (requires nikic/php-parser ^4.8)
│  │  └──phpunit/phpunit 9.3.7 (requires phpunit/php-code-coverage ^9.1.1)
│  │     └──__root__ dev-master (requires (for development) phpunit/phpunit ^9.3)
│  ├──sebastian/complexity 2.0.0 (requires nikic/php-parser ^4.7)
│  │  └──phpunit/php-code-coverage 9.1.4 (requires sebastian/complexity ^2.0)
│  │     └──phpunit/phpunit 9.3.7 (requires phpunit/php-code-coverage ^9.1.1)
│  │        └──__root__ dev-master (requires (for development) phpunit/phpunit ^9.3)
│  └──sebastian/lines-of-code 1.0.0 (requires nikic/php-parser ^4.6)
│     └──phpunit/php-code-coverage 9.1.4 (requires sebastian/lines-of-code ^1.0)
│        └──phpunit/phpunit 9.3.7 (requires phpunit/php-code-coverage ^9.1.1)
│           └──__root__ dev-master (requires (for development) phpunit/phpunit ^9.3)
├──phpstan/phpstan-shim 0.11.19 (replaces phpstan/phpdoc-parser ^0.3.3)
│  └──phpstan/phpstan-shim 0.11.19 (replaces phpstan/phpdoc-parser ^0.3.3) (circular dependency aborted here)
├──phpstan/phpstan-shim 0.11.19 (replaces phpstan/phpstan self.version)
│  └──phpstan/phpstan-shim 0.11.19 (replaces phpstan/phpstan self.version) (circular dependency aborted here)
├──phpstan/phpstan-shim 0.11.19 (replaces nikic/php-parser ^4.0.2) (circular dependency aborted here)
├──phpstan/phpstan-shim 0.11.19 (replaces phpstan/phpdoc-parser ^0.3.3) (circular dependency aborted here)
├──phpstan/phpstan-shim 0.11.19 (replaces phpstan/phpstan self.version) (circular dependency aborted here)
├──phpunit/php-code-coverage 9.1.4 (requires nikic/php-parser ^4.8)
│  └──phpunit/phpunit 9.3.7 (requires phpunit/php-code-coverage ^9.1.1)
│     └──__root__ dev-master (requires (for development) phpunit/phpunit ^9.3)
├──sebastian/complexity 2.0.0 (requires nikic/php-parser ^4.7)
│  └──phpunit/php-code-coverage 9.1.4 (requires sebastian/complexity ^2.0)
│     └──phpunit/phpunit 9.3.7 (requires phpunit/php-code-coverage ^9.1.1)
│        └──__root__ dev-master (requires (for development) phpunit/phpunit ^9.3)
└──sebastian/lines-of-code 1.0.0 (requires nikic/php-parser ^4.6)
   └──phpunit/php-code-coverage 9.1.4 (requires sebastian/lines-of-code ^1.0)
      └──phpunit/phpunit 9.3.7 (requires phpunit/php-code-coverage ^9.1.1)
         └──__root__ dev-master (requires (for development) phpunit/phpunit ^9.3)

ルートパッケージで php-di/php-di と phpunit/phpunit を require していて、それが間接的に nikic/php-parser に依存しており、phpstan/phpstan-shim が nikic/php-parser を replade しているので nikic/php-parser ではなく phpstan/phpstan-shim に依存しています。

もう phpstan/phpstan-shim は要らないので、replace される前の本来の nikic/php-parser に戻したいのですが、この状態からだと phpstan/phpstan-shim を削除できないし、nikic/php-parser を追加することも出来ません。

$ composer remove phpstan/phpstan-shim
phpstan/phpstan-shim is not required in your composer.json and has not been removed
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
:

$ composer require nikic/php-parser
Using version ^4.8 for nikic/php-parser
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
:

php-di/php-di と phpunit/phpunit を一旦削除すれば phpstan/phpstan-shim も削除されるので、その後で php-di/php-di と phpunit/phpunit を入れ直せば nikic/php-parser が入りますが、その手順だと不必要にパッケージが更新されてしまうので避けたい。

解決?

phpstan/phpstan-shim はもう abandoned になっていて最新版の 0.12.0 だとなにも replace していませんでした。

ので、一旦 phpstan/phpstan-shim を 0.12.0 にすれば nikic/php-parser が戻ってくるので、その後で phpstan/phpstan-shim だけ削除すれば OK です。

$ composer require phpstan/phpstan-shim
Using version ^0.12.0 for phpstan/phpstan-shim
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 1 update, 0 removals
  - Updating phpstan/phpstan-shim (0.11.19 => 0.12.0): Loading from cache
  - Installing nikic/php-parser (v4.8.0): Loading from cache
:

$ composer remove phpstan/phpstan-shim
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 0 installs, 0 updates, 1 removal
  - Removing phpstan/phpstan-shim (0.12.0)
:

もしくは phpstan/phpstan が phpstan/phpstan-shim と conflict しているので phpstan/phpstan を入れれば phpstan/phpstan-shim を追い出せます。

$ composer require phpstan/phpstan
Using version ^0.12.37 for phpstan/phpstan
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 1 removal
  - Removing phpstan/phpstan-shim (0.11.19)
  - Installing phpstan/phpstan (0.12.37): Loading from cache
  - Installing nikic/php-parser (v4.8.0): Loading from cache
:

$ composer remove phpstan/phpstan
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 0 installs, 0 updates, 1 removal
  - Removing phpstan/phpstan (0.12.37)
:

あるいは、ルートパッケージに composer.json に次のように conflict を追記し、

{
   :
    "conflict": {
        "phpstan/phpstan-shim": "*"
    }
}

composer update すれば phpstan/phpstan-shim を追い出せます。

$ composer update phpstan/phpstan-shim
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 1 removal
  - Removing phpstan/phpstan-shim (0.11.19)
  - Installing nikic/php-parser (v4.8.0): Loading from cache
: