diff --git a/doc/03-cli.md b/doc/03-cli.md index 9babc61cc..b5714d442 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -328,34 +328,67 @@ to limit output to suggestions made by those packages only. ## depends The `depends` command tells you which other packages depend on a certain -package. You can specify which link types (`require`, `require-dev`) -should be included in the listing. By default both are used. +package. As with installation `require-dev` relationships are only considered +for the root package. ```sh -php composer.phar depends --link-type=require monolog/monolog - -nrk/monolog-fluent requires monolog/monolog (~1.8) -poc/poc requires monolog/monolog (^1.6) -propel/propel requires monolog/monolog (1.*) -symfony/monolog-bridge requires monolog/monolog (>=1.2) -symfony/symfony requires monolog/monolog (~1) +php composer.phar depends doctrine/lexer + doctrine/annotations v1.2.7 requires doctrine/lexer (1.*) + doctrine/common v2.6.1 requires doctrine/lexer (1.*) ``` -If you want, for example, find any installed package that is **not** allowing -Symfony version 3 or one of its components, you can run the following command: +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: ```sh -php composer.phar depends symfony/symfony --with-replaces -im ^3.0 +php composer.phar depends psr/log -t +psr/log 1.0.0 Common interface for logging libraries +|- aboutyou/app-sdk 2.6.11 (requires psr/log 1.0.*) +| `- __root__ (requires aboutyou/app-sdk ^2.6) +|- monolog/monolog 1.17.2 (requires psr/log ~1.0) +| `- laravel/framework v5.2.16 (requires monolog/monolog ~1.11) +| `- __root__ (requires laravel/framework ^5.2) +`- symfony/symfony v3.0.2 (requires psr/log ~1.0) + `- __root__ (requires symfony/symfony ^3.0) ``` ### Options -* **--link-type:** The link types to match on, can be specified multiple - times. -* **--match-constraint (-m):** Filters the dependencies shown using this constraint. -* **--invert-match-constraint (-i):** Turns --match-constraint around into a blacklist - instead of a whitelist. -* **--with-replaces:** Search for replaced packages as well. +* **--recursive (-r):** Recursively resolves up to the root package. +* **--tree (-t):** Prints the results as a nested tree, implies -r. + +## prohibits + +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: + +```sh +php composer.phar prohibits symfony/symfony 3.1 + 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: + +```sh +php composer.phar prohibits php:8 + 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 diff --git a/src/Composer/Command/AboutCommand.php b/src/Composer/Command/AboutCommand.php index ea924f678..d6800ee15 100644 --- a/src/Composer/Command/AboutCommand.php +++ b/src/Composer/Command/AboutCommand.php @@ -18,7 +18,7 @@ use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ -class AboutCommand extends Command +class AboutCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index 2fea529a6..104877d8d 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -30,7 +30,7 @@ use Symfony\Component\Console\Output\OutputInterface; * * @author Nils Adermann */ -class ArchiveCommand extends Command +class ArchiveCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/Command.php b/src/Composer/Command/BaseCommand.php similarity index 96% rename from src/Composer/Command/Command.php rename to src/Composer/Command/BaseCommand.php index 6c5226c6a..2a70b584d 100644 --- a/src/Composer/Command/Command.php +++ b/src/Composer/Command/BaseCommand.php @@ -18,7 +18,7 @@ use Composer\IO\IOInterface; use Composer\IO\NullIO; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Command\Command as BaseCommand; +use Symfony\Component\Console\Command\Command; /** * Base class for Composer commands @@ -26,7 +26,7 @@ use Symfony\Component\Console\Command\Command as BaseCommand; * @author Ryan Weaver * @author Konstantin Kudryashov */ -abstract class Command extends BaseCommand +abstract class BaseCommand extends Command { /** * @var Composer diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php new file mode 100644 index 000000000..1a9aa005b --- /dev/null +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -0,0 +1,190 @@ + + * 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\DependencyResolver\Pool; +use Composer\Package\Link; +use Composer\Package\PackageInterface; +use Composer\Repository\ArrayRepository; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Package\Version\VersionParser; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Base implementation for commands mapping dependency relationships. + * + * @author Niels Keurentjes + */ +class BaseDependencyCommand extends BaseCommand +{ + const ARGUMENT_PACKAGE = 'package'; + const ARGUMENT_CONSTRAINT = 'constraint'; + const OPTION_RECURSIVE = 'recursive'; + const OPTION_TREE = 'tree'; + + /** + * Set common options and arguments. + */ + protected function configure() + { + $this->setDefinition(array( + new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect'), + new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::OPTIONAL, 'Optional version constraint', '*'), + new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), + new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), + )); + } + + /** + * Execute the command. + * + * @param InputInterface $input + * @param OutputInterface $output + * @param bool $inverted Whether to invert matching process (why-not vs why behaviour) + * @return int|null Exit code of the operation. + */ + protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false) + { + // Emit command event on startup + $composer = $this->getComposer(); + $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + // Prepare repositories and set up a pool + $platformOverrides = $composer->getConfig()->get('platform') ?: array(); + $repository = new CompositeRepository(array( + new ArrayRepository(array($composer->getPackage())), + $composer->getRepositoryManager()->getLocalRepository(), + new PlatformRepository(array(), $platformOverrides), + )); + $pool = new Pool(); + $pool->addRepository($repository); + + // Parse package name and constraint + list($needle, $textConstraint) = array_pad(explode(':', $input->getArgument(self::ARGUMENT_PACKAGE)), + 2, $input->getArgument(self::ARGUMENT_CONSTRAINT)); + + // Find packages that are or provide the requested package first + $packages = $pool->whatProvides($needle); + if (empty($packages)) { + throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); + } + + // Include replaced packages for inverted lookups as they are then the actual starting point to consider + $needles = array($needle); + if ($inverted) { + foreach ($packages as $package) { + $needles = array_merge($needles, array_map(function (Link $link) { + return $link->getTarget(); + }, $package->getReplaces())); + } + } + + // Parse constraint if one was supplied + if ('*' !== $textConstraint) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($textConstraint); + } else { + $constraint = null; + } + + // Parse rendering options + $renderTree = $input->getOption(self::OPTION_TREE); + $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE); + + // Resolve dependencies + $results = $repository->getDependents($needles, $constraint, $inverted, $recursive); + if (empty($results)) { + $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : ''; + $this->getIO()->writeError(sprintf('There is no installed package depending on "%s"%s', + $needle, $extra)); + } elseif ($renderTree) { + $root = $packages[0]; + $this->getIO()->write(sprintf('%s %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root->getDescription())); + $this->printTree($output, $results); + } else { + $this->printTable($output, $results); + } + return 0; + } + + /** + * Assembles and prints a bottom-up table of the dependencies. + * + * @param OutputInterface $output + * @param array $results + */ + protected function printTable(OutputInterface $output, $results) + { + $table = array(); + $doubles = array(); + do { + $queue = array(); + $rows = array(); + foreach($results as $result) { + /** + * @var PackageInterface $package + * @var Link $link + */ + list($package, $link, $children) = $result; + $unique = (string)$link; + if (isset($doubles[$unique])) { + continue; + } + $doubles[$unique] = true; + $version = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion(); + $rows[] = array($package->getPrettyName(), $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())); + $queue = array_merge($queue, $children); + } + $results = $queue; + $table = array_merge($rows, $table); + } while(!empty($results)); + + // Render table + $renderer = new Table($output); + $renderer->setStyle('compact')->setRows($table)->render(); + } + + /** + * Recursively prints a tree of the selected results. + * + * @param OutputInterface $output + * @param array $results + * @param string $prefix + */ + protected function printTree(OutputInterface $output, $results, $prefix = '') + { + $count = count($results); + $idx = 0; + foreach($results as $key => $result) { + /** + * @var PackageInterface $package + * @var Link $link + */ + list($package, $link, $children) = $result; + $isLast = (++$idx == $count); + $versionText = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '' : $package->getPrettyVersion(); + $packageText = rtrim(sprintf('%s %s', $package->getPrettyName(), $versionText)); + $linkText = implode(' ', array($link->getDescription(), $link->getTarget(), $link->getPrettyConstraint())); + $output->write(sprintf("%s%s %s (%s)\n", $prefix, $isLast ? '`-' : '|-', $packageText, $linkText)); + $this->printTree($output, $children, $prefix . ($isLast ? ' ' : '| ')); + } + } +} diff --git a/src/Composer/Command/ClearCacheCommand.php b/src/Composer/Command/ClearCacheCommand.php index 818b8ee30..7ab17806d 100644 --- a/src/Composer/Command/ClearCacheCommand.php +++ b/src/Composer/Command/ClearCacheCommand.php @@ -20,7 +20,7 @@ use Symfony\Component\Console\Output\OutputInterface; /** * @author David Neilsen */ -class ClearCacheCommand extends Command +class ClearCacheCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 0f4637ef3..eef4521b6 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -27,7 +27,7 @@ use Composer\Json\JsonFile; * @author Joshua Estes * @author Jordi Boggiano */ -class ConfigCommand extends Command +class ConfigCommand extends BaseCommand { /** * @var Config diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 6fcc42b30..44c04e0c8 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -46,7 +46,7 @@ use Composer\Package\Version\VersionParser; * @author Tobias Munk * @author Nils Adermann */ -class CreateProjectCommand extends Command +class CreateProjectCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index 81cf51c95..0cfb5b83d 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -12,43 +12,25 @@ namespace Composer\Command; -use Composer\DependencyResolver\Pool; -use Composer\Package\Link; -use Composer\Package\PackageInterface; -use Composer\Repository\ArrayRepository; -use Composer\Repository\CompositeRepository; -use Composer\Repository\PlatformRepository; -use Composer\Plugin\CommandEvent; -use Composer\Plugin\PluginEvents; -use Composer\Package\Version\VersionParser; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** - * @author Justin Rainbow - * @author Jordi Boggiano + * @author Niels Keurentjes */ -class DependsCommand extends Command +class DependsCommand extends BaseDependencyCommand { - protected $linkTypes = array( - 'require' => array('requires', 'requires'), - 'require-dev' => array('devRequires', 'requires (dev)'), - ); - + /** + * Configure command metadata. + */ protected function configure() { + parent::configure(); + $this ->setName('depends') - ->setDescription('Shows which packages depend on the given package') - ->setDefinition(array( - new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'), - new InputOption('link-type', '', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Link types to show (require, require-dev)', array_keys($this->linkTypes)), - new InputOption('match-constraint', 'm', InputOption::VALUE_REQUIRED, 'Filters the dependencies shown using this constraint', '*'), - new InputOption('invert-match-constraint', 'i', InputOption::VALUE_NONE, 'Turns --match-constraint around into a blacklist instead of whitelist'), - new InputOption('with-replaces', '', InputOption::VALUE_NONE, 'Search for replaced packages as well'), - )) + ->setAliases(array('why')) + ->setDescription('Shows which packages cause the given package to be installed') ->setHelp(<<getComposer(); - - $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'depends', $input, $output); - $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); - - $platformOverrides = $composer->getConfig()->get('platform') ?: array(); - $repo = new CompositeRepository(array( - new ArrayRepository(array($composer->getPackage())), - $composer->getRepositoryManager()->getLocalRepository(), - new PlatformRepository(array(), $platformOverrides), - )); - $needle = $input->getArgument('package'); - - $pool = new Pool(); - $pool->addRepository($repo); - - $packages = $pool->whatProvides($needle); - if (empty($packages)) { - throw new \InvalidArgumentException('Could not find package "'.$needle.'" in your project.'); - } - - $linkTypes = $this->linkTypes; - - $types = array_map(function ($type) use ($linkTypes) { - $type = rtrim($type, 's'); - if (!isset($linkTypes[$type])) { - throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', array_keys($linkTypes))); - } - - return $type; - }, $input->getOption('link-type')); - - $versionParser = new VersionParser(); - $constraint = $versionParser->parseConstraints($input->getOption('match-constraint')); - $matchInvert = $input->getOption('invert-match-constraint'); - - $needles = array($needle); - if (true === $input->getOption('with-replaces')) { - foreach ($packages as $package) { - $needles = array_merge($needles, array_map(function (Link $link) { - return $link->getTarget(); - }, $package->getReplaces())); - } - } - - $messages = array(); - $outputPackages = array(); - $io = $this->getIO(); - /** @var PackageInterface $package */ - foreach ($repo->getPackages() as $package) { - foreach ($types as $type) { - /** @var Link $link */ - foreach ($package->{'get'.$linkTypes[$type][0]}() as $link) { - foreach ($needles as $needle) { - if ($link->getTarget() === $needle && ($link->getConstraint()->matches($constraint) ? !$matchInvert : $matchInvert)) { - if (!isset($outputPackages[$package->getName()])) { - $messages[] = ''.$package->getPrettyName() . ' ' . $linkTypes[$type][1] . ' ' . $needle .' (' . $link->getPrettyConstraint() . ')'; - $outputPackages[$package->getName()] = true; - } - } - } - } - } - } - - if ($messages) { - sort($messages); - $io->write($messages); - } else { - $matchText = ''; - if ($input->getOption('match-constraint') !== '*') { - $matchText = ' in versions '.($matchInvert ? 'not ' : '').'matching ' . $input->getOption('match-constraint'); - } - $io->writeError('There is no installed package depending on "'.$needle.'"'.$matchText.'.'); - } + return parent::doExecute($input, $output, false); } } diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 87ba2cc0e..e2301c813 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -29,7 +29,7 @@ use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ -class DiagnoseCommand extends Command +class DiagnoseCommand extends BaseCommand { /** @var RemoteFileSystem */ protected $rfs; diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index d47dd7284..94e2b4f6c 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -21,7 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ -class DumpAutoloadCommand extends Command +class DumpAutoloadCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/GlobalCommand.php b/src/Composer/Command/GlobalCommand.php index 502ce5e8a..86baeeade 100644 --- a/src/Composer/Command/GlobalCommand.php +++ b/src/Composer/Command/GlobalCommand.php @@ -21,7 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ -class GlobalCommand extends Command +class GlobalCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/HomeCommand.php b/src/Composer/Command/HomeCommand.php index 150b8c71b..706308bc4 100644 --- a/src/Composer/Command/HomeCommand.php +++ b/src/Composer/Command/HomeCommand.php @@ -26,7 +26,7 @@ use Symfony\Component\Console\Output\OutputInterface; /** * @author Robert Schönthal */ -class HomeCommand extends Command +class HomeCommand extends BaseCommand { /** * {@inheritDoc} diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index cd3f7395d..b16fa21b3 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -31,7 +31,7 @@ use Symfony\Component\Process\ExecutableFinder; * @author Justin Rainbow * @author Jordi Boggiano */ -class InitCommand extends Command +class InitCommand extends BaseCommand { /** @var CompositeRepository */ protected $repos; diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 3ae00c228..11abdfaf3 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -26,7 +26,7 @@ use Symfony\Component\Console\Output\OutputInterface; * @author Konstantin Kudryashov * @author Nils Adermann */ -class InstallCommand extends Command +class InstallCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php index e6e52d481..7cb250607 100644 --- a/src/Composer/Command/LicensesCommand.php +++ b/src/Composer/Command/LicensesCommand.php @@ -25,7 +25,7 @@ use Symfony\Component\Console\Output\OutputInterface; /** * @author Benoît Merlet */ -class LicensesCommand extends Command +class LicensesCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/ProhibitsCommand.php b/src/Composer/Command/ProhibitsCommand.php new file mode 100644 index 000000000..fb717eb7e --- /dev/null +++ b/src/Composer/Command/ProhibitsCommand.php @@ -0,0 +1,55 @@ + + * 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 Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Niels Keurentjes + */ +class ProhibitsCommand extends BaseDependencyCommand +{ + /** + * Configure command metadata. + */ + protected function configure() + { + parent::configure(); + + $this + ->setName('prohibits') + ->setAliases(array('why-not')) + ->setDescription('Shows which packages prevent the given package from being installed') + ->setHelp(<<php composer.phar prohibits composer/composer + +EOT + ) + ; + } + + /** + * Execute the function. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return int|null + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + return parent::doExecute($input, $output, true); + } +} diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index e9bd610b4..759103c8a 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -27,7 +27,7 @@ use Symfony\Component\Console\Output\OutputInterface; * @author Pierre du Plessis * @author Jordi Boggiano */ -class RemoveCommand extends Command +class RemoveCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/RunScriptCommand.php b/src/Composer/Command/RunScriptCommand.php index 008058880..da1d36132 100644 --- a/src/Composer/Command/RunScriptCommand.php +++ b/src/Composer/Command/RunScriptCommand.php @@ -23,7 +23,7 @@ use Symfony\Component\Console\Output\OutputInterface; /** * @author Fabien Potencier */ -class RunScriptCommand extends Command +class RunScriptCommand extends BaseCommand { /** * @var array Array with command events diff --git a/src/Composer/Command/ScriptAliasCommand.php b/src/Composer/Command/ScriptAliasCommand.php index aeb83b0a4..010643dbb 100644 --- a/src/Composer/Command/ScriptAliasCommand.php +++ b/src/Composer/Command/ScriptAliasCommand.php @@ -20,7 +20,7 @@ use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ -class ScriptAliasCommand extends Command +class ScriptAliasCommand extends BaseCommand { private $script; diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index e636fc4d0..d79feed26 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -26,7 +26,7 @@ use Composer\Plugin\PluginEvents; /** * @author Robert Schönthal */ -class SearchCommand extends Command +class SearchCommand extends BaseCommand { protected $matches; protected $lowMatches = array(); diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 16b98020a..030c6bebd 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -30,7 +30,7 @@ use Symfony\Component\Finder\Finder; * @author Kevin Ran * @author Jordi Boggiano */ -class SelfUpdateCommand extends Command +class SelfUpdateCommand extends BaseCommand { const HOMEPAGE = 'getcomposer.org'; const OLD_INSTALL_EXT = '-old.phar'; diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 38cc4ea68..be974c58e 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -38,7 +38,7 @@ use Composer\Spdx\SpdxLicenses; * @author Jordi Boggiano * @author Jérémy Romey */ -class ShowCommand extends Command +class ShowCommand extends BaseCommand { protected $versionParser; protected $colors; diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 7455e84b8..918301cc6 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -24,7 +24,7 @@ use Composer\Script\ScriptEvents; * @author Tiago Ribeiro * @author Rui Marinho */ -class StatusCommand extends Command +class StatusCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/SuggestsCommand.php b/src/Composer/Command/SuggestsCommand.php index d59ef1dc6..e1508b81b 100644 --- a/src/Composer/Command/SuggestsCommand.php +++ b/src/Composer/Command/SuggestsCommand.php @@ -17,7 +17,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -class SuggestsCommand extends Command +class SuggestsCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 118a04fa1..233714cdc 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -28,7 +28,7 @@ use Symfony\Component\Console\Question\Question; * @author Jordi Boggiano * @author Nils Adermann */ -class UpdateCommand extends Command +class UpdateCommand extends BaseCommand { protected function configure() { diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index 321453f18..87158d2bb 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -29,7 +29,7 @@ use Symfony\Component\Console\Output\OutputInterface; * @author Robert Schönthal * @author Jordi Boggiano */ -class ValidateCommand extends Command +class ValidateCommand extends BaseCommand { /** * configure diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 07d517eae..51db89e89 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -279,33 +279,35 @@ class Application extends BaseApplication } /** - * Initializes all the composer commands + * Initializes all the composer commands. */ protected function getDefaultCommands() { - $commands = parent::getDefaultCommands(); - $commands[] = new Command\AboutCommand(); - $commands[] = new Command\ConfigCommand(); - $commands[] = new Command\DependsCommand(); - $commands[] = new Command\InitCommand(); - $commands[] = new Command\InstallCommand(); - $commands[] = new Command\CreateProjectCommand(); - $commands[] = new Command\UpdateCommand(); - $commands[] = new Command\SearchCommand(); - $commands[] = new Command\ValidateCommand(); - $commands[] = new Command\ShowCommand(); - $commands[] = new Command\SuggestsCommand(); - $commands[] = new Command\RequireCommand(); - $commands[] = new Command\DumpAutoloadCommand(); - $commands[] = new Command\StatusCommand(); - $commands[] = new Command\ArchiveCommand(); - $commands[] = new Command\DiagnoseCommand(); - $commands[] = new Command\RunScriptCommand(); - $commands[] = new Command\LicensesCommand(); - $commands[] = new Command\GlobalCommand(); - $commands[] = new Command\ClearCacheCommand(); - $commands[] = new Command\RemoveCommand(); - $commands[] = new Command\HomeCommand(); + $commands = array_merge(parent::getDefaultCommands(), array( + new Command\AboutCommand(), + new Command\ConfigCommand(), + new Command\DependsCommand(), + new Command\ProhibitsCommand(), + new Command\InitCommand(), + new Command\InstallCommand(), + new Command\CreateProjectCommand(), + new Command\UpdateCommand(), + new Command\SearchCommand(), + new Command\ValidateCommand(), + new Command\ShowCommand(), + new Command\SuggestsCommand(), + new Command\RequireCommand(), + new Command\DumpAutoloadCommand(), + new Command\StatusCommand(), + new Command\ArchiveCommand(), + new Command\DiagnoseCommand(), + new Command\RunScriptCommand(), + new Command\LicensesCommand(), + new Command\GlobalCommand(), + new Command\ClearCacheCommand(), + new Command\RemoveCommand(), + new Command\HomeCommand(), + )); if ('phar:' === substr(__FILE__, 0, 5)) { $commands[] = new Command\SelfUpdateCommand(); diff --git a/src/Composer/Package/Link.php b/src/Composer/Package/Link.php index e695822fd..217da0713 100644 --- a/src/Composer/Package/Link.php +++ b/src/Composer/Package/Link.php @@ -64,6 +64,14 @@ class Link $this->prettyConstraint = $prettyConstraint; } + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + /** * @return string */ diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 80fe6890d..852515493 100644 --- a/src/Composer/Repository/ArrayRepository.php +++ b/src/Composer/Repository/ArrayRepository.php @@ -24,7 +24,7 @@ use Composer\Semver\Constraint\Constraint; * * @author Nils Adermann */ -class ArrayRepository implements RepositoryInterface +class ArrayRepository extends BaseRepository { /** @var PackageInterface[] */ protected $packages; diff --git a/src/Composer/Repository/BaseRepository.php b/src/Composer/Repository/BaseRepository.php new file mode 100644 index 000000000..3b0b81b28 --- /dev/null +++ b/src/Composer/Repository/BaseRepository.php @@ -0,0 +1,70 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\RootPackageInterface; +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * Common ancestor class for generic repository functionality. + * + * @author Niels Keurentjes + */ +abstract class BaseRepository implements RepositoryInterface +{ + /** + * Returns a list of links causing the requested needle packages to be installed, as an associative array with the + * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship + * as values. If recursive lookup was requested a third value is returned containing an identically formed array up + * to the root package. + * + * @param string|string[] $needle The package name(s) to inspect. + * @param ConstraintInterface|null $constraint Optional constraint to filter by. + * @param bool $invert Whether to invert matches to discover reasons for the package *NOT* to be installed. + * @param bool $recurse Whether to recursively expand the requirement tree up to the root package. + * @return array An associative array of arrays as described above. + */ + public function getDependents($needle, $constraint = null, $invert = false, $recurse = true) + { + $needles = is_array($needle) ? $needle : array($needle); + $results = array(); + + // Loop over all currently installed packages. + foreach ($this->getPackages() as $package) { + $links = $package->getRequires(); + + // Replacements are considered valid reasons for a package to be installed during forward resolution + if (!$invert) { + $links += $package->getReplaces(); + } + + // Require-dev is only relevant for the root package + if ($package instanceof RootPackageInterface) { + $links += $package->getDevRequires(); + } + + // Cross-reference all discovered links to the needles + foreach ($links as $link) { + foreach ($needles as $needle) { + if ($link->getTarget() === $needle) { + if (is_null($constraint) || (($link->getConstraint()->matches($constraint) === !$invert))) { + $results[$link->getSource()] = array($package, $link, $recurse ? $this->getDependents($link->getSource(), null, false, true) : array()); + } + } + } + } + } + ksort($results); + return $results; + } +} diff --git a/src/Composer/Repository/CompositeRepository.php b/src/Composer/Repository/CompositeRepository.php index 7f29385bf..a9342a5d1 100644 --- a/src/Composer/Repository/CompositeRepository.php +++ b/src/Composer/Repository/CompositeRepository.php @@ -19,7 +19,7 @@ use Composer\Package\PackageInterface; * * @author Beau Simensen */ -class CompositeRepository implements RepositoryInterface +class CompositeRepository extends BaseRepository { /** * List of repositories