From e38fe673339dd5d846de34ab251fcb34f6bc8e52 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Mon, 15 Feb 2016 00:49:00 +0100 Subject: [PATCH 01/11] Rewrote DependsCommand --- src/Composer/Command/DependsCommand.php | 171 ++++++++++++++++-------- src/Composer/Package/Link.php | 8 ++ 2 files changed, 122 insertions(+), 57 deletions(-) diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index abdce5a86..185d3bb7e 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -15,12 +15,15 @@ namespace Composer\Command; use Composer\DependencyResolver\Pool; use Composer\Package\Link; use Composer\Package\PackageInterface; +use Composer\Package\RootPackage; use Composer\Repository\ArrayRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; +use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\VersionParser; +use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -32,22 +35,19 @@ use Symfony\Component\Console\Output\OutputInterface; */ class DependsCommand extends Command { - protected $linkTypes = array( - 'require' => array('requires', 'requires'), - 'require-dev' => array('devRequires', 'requires (dev)'), - ); + /** @var CompositeRepository */ + private $repository; protected function configure() { $this ->setName('depends') + ->setAliases(array('why')) ->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'), )) ->setHelp(<<getComposer(); - $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'depends', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + // Prepare repositories and set up a pool $platformOverrides = $composer->getConfig()->get('platform') ?: array(); - $repo = new CompositeRepository(array( + $this->repository = 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); + $pool->addRepository($this->repository); + // Find packages that are or provide the requested package first + $needle = $input->getArgument('package'); $packages = $pool->whatProvides($needle); if (empty($packages)) { - throw new \InvalidArgumentException('Could not find package "'.$needle.'" in your project.'); + throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); } - $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')); + // Parse options that are only relevant for the initial needle(s) + if ('*' !== ($textConstraint = $input->getOption('match-constraint'))) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($textConstraint); + } else { + $constraint = null; + } $matchInvert = $input->getOption('invert-match-constraint'); + $recursive = true; + $tree = true; - $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())); - } + // Resolve dependencies + $results = $this->getDependers($needle, $constraint, $matchInvert, $recursive); + if (empty($results)) { + $extra = isset($constraint) ? sprintf(' in versions %smatching %s', $matchInvert ? 'not ' : '', $textConstraint) : ''; + $this->getIO()->writeError(sprintf('There is no installed package depending on "%s"%s', + $needle, $extra)); + } elseif ($tree) { + $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); } + } - $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; - } + private 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; + $realVersion = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion(); + $rows[] = array($package->getPrettyName(), $realVersion, $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(); + } + + public 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); + $output->write(sprintf("%s%s %s %s %s (%s)\n", $prefix, $isLast ? '`-' : '|-', $link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint())); + $this->printTree($output, $children, $prefix . ($isLast ? ' ' : '| ')); + } + } + + /** + * @param string $needle The package to inspect. + * @param ConstraintInterface|null $constraint Optional constraint to filter by. + * @param bool $invert Whether to invert matches on the previous constraint. + * @param bool $recurse Whether to recursively expand the requirement tree. + * @return array An array with dependers as key, and as values an array containing the source package and the link respectively + */ + private function getDependers($needle, $constraint = null, $invert = false, $recurse = true) + { + $needles = is_array($needle) ? $needle : array($needle); + $results = array(); + + /** + * Loop over all currently installed packages. + * @var PackageInterface $package + */ + foreach ($this->repository->getPackages() as $package) { + // Retrieve all requirements, but dev only for the root package + $links = $package->getRequires(); + $links += $package->getReplaces(); + if ($package instanceof RootPackage) { + $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->getDependers($link->getSource(), null, false, true) : array()); } } } } } - - 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.'.'); - } + ksort($results); + return $results; } } 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 */ From 9b41495353ab00ce467bf614ed480c9c6c62ccf4 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Mon, 15 Feb 2016 01:08:24 +0100 Subject: [PATCH 02/11] Options added and cleaned up. --- src/Composer/Command/DependsCommand.php | 30 ++++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index 185d3bb7e..ab35f9ce2 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -46,6 +46,8 @@ class DependsCommand extends Command ->setDescription('Shows which packages depend on the given package') ->setDefinition(array( new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'), + new InputOption('recursive', 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), + new InputOption('tree', 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), 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'), )) @@ -91,8 +93,8 @@ EOT $constraint = null; } $matchInvert = $input->getOption('invert-match-constraint'); - $recursive = true; - $tree = true; + $renderTree = $input->getOption('tree'); + $recursive = $renderTree || $input->getOption('recursive'); // Resolve dependencies $results = $this->getDependers($needle, $constraint, $matchInvert, $recursive); @@ -100,7 +102,7 @@ EOT $extra = isset($constraint) ? sprintf(' in versions %smatching %s', $matchInvert ? 'not ' : '', $textConstraint) : ''; $this->getIO()->writeError(sprintf('There is no installed package depending on "%s"%s', $needle, $extra)); - } elseif ($tree) { + } elseif ($renderTree) { $root = $packages[0]; $this->getIO()->write(sprintf('%s %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root->getDescription())); $this->printTree($output, $results); @@ -109,6 +111,12 @@ EOT } } + /** + * Assembles and prints a bottom-up table of the dependencies. + * + * @param OutputInterface $output + * @param array $results + */ private function printTable(OutputInterface $output, $results) { $table = array(); @@ -127,8 +135,8 @@ EOT continue; } $doubles[$unique] = true; - $realVersion = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion(); - $rows[] = array($package->getPrettyName(), $realVersion, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())); + $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; @@ -140,6 +148,13 @@ EOT $renderer->setStyle('compact')->setRows($table)->render(); } + /** + * Recursively prints a tree of the selected results. + * + * @param OutputInterface $output + * @param array $results + * @param string $prefix + */ public function printTree(OutputInterface $output, $results, $prefix = '') { $count = count($results); @@ -151,7 +166,10 @@ EOT */ list($package, $link, $children) = $result; $isLast = (++$idx == $count); - $output->write(sprintf("%s%s %s %s %s (%s)\n", $prefix, $isLast ? '`-' : '|-', $link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint())); + $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 ? ' ' : '| ')); } } From 21e262cb8a179f6018fe12e1bc5cae07b5b768d4 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Mon, 15 Feb 2016 01:11:24 +0100 Subject: [PATCH 03/11] Documentation updated for new depends parameters. --- doc/03-cli.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index b35d8f805..0afb9f304 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -350,12 +350,11 @@ php composer.phar depends symfony/symfony --with-replaces -im ^3.0 ### Options -* **--link-type:** The link types to match on, can be specified multiple - times. +* **--recursive (-r):** Recursively resolves up to the root package. +* **--tree (-t):** Prints the results as a nested tree, implies -r. * **--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. ## validate From 86374034b678691891fcb1be88e0531404b7fc70 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Mon, 15 Feb 2016 12:48:33 +0100 Subject: [PATCH 04/11] Removed unnecessary state from Depends command. --- src/Composer/Command/DependsCommand.php | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index ab35f9ce2..32441bb0c 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -35,9 +35,6 @@ use Symfony\Component\Console\Output\OutputInterface; */ class DependsCommand extends Command { - /** @var CompositeRepository */ - private $repository; - protected function configure() { $this @@ -70,13 +67,13 @@ EOT // Prepare repositories and set up a pool $platformOverrides = $composer->getConfig()->get('platform') ?: array(); - $this->repository = new CompositeRepository(array( + $repository = new CompositeRepository(array( new ArrayRepository(array($composer->getPackage())), $composer->getRepositoryManager()->getLocalRepository(), new PlatformRepository(array(), $platformOverrides), )); $pool = new Pool(); - $pool->addRepository($this->repository); + $pool->addRepository($repository); // Find packages that are or provide the requested package first $needle = $input->getArgument('package'); @@ -97,7 +94,7 @@ EOT $recursive = $renderTree || $input->getOption('recursive'); // Resolve dependencies - $results = $this->getDependers($needle, $constraint, $matchInvert, $recursive); + $results = $this->getDependents($needle, $repository->getPackages(), $constraint, $matchInvert, $recursive); if (empty($results)) { $extra = isset($constraint) ? sprintf(' in versions %smatching %s', $matchInvert ? 'not ' : '', $textConstraint) : ''; $this->getIO()->writeError(sprintf('There is no installed package depending on "%s"%s', @@ -176,21 +173,21 @@ EOT /** * @param string $needle The package to inspect. + * @param PackageInterface[] $packages List of installed packages. * @param ConstraintInterface|null $constraint Optional constraint to filter by. * @param bool $invert Whether to invert matches on the previous constraint. * @param bool $recurse Whether to recursively expand the requirement tree. * @return array An array with dependers as key, and as values an array containing the source package and the link respectively */ - private function getDependers($needle, $constraint = null, $invert = false, $recurse = true) + private function getDependents($needle, $packages, $constraint = null, $invert = false, $recurse = true) { $needles = is_array($needle) ? $needle : array($needle); $results = array(); /** * Loop over all currently installed packages. - * @var PackageInterface $package */ - foreach ($this->repository->getPackages() as $package) { + foreach ($packages as $package) { // Retrieve all requirements, but dev only for the root package $links = $package->getRequires(); $links += $package->getReplaces(); @@ -203,7 +200,7 @@ EOT 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->getDependers($link->getSource(), null, false, true) : array()); + $results[$link->getSource()] = array($package, $link, $recurse ? $this->getDependents($link->getSource(), $packages, null, false, true) : array()); } } } From 53b64177072a62cb19cc851b5a81b082aad78ced Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Tue, 16 Feb 2016 16:50:06 +0100 Subject: [PATCH 05/11] Missed some docs on composer depends. --- doc/03-cli.md | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 0afb9f304..b73395a46 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -328,24 +328,36 @@ 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: +If you want, for example, to find any installed package that is **not** +allowing Monolog to be upgraded to version 1.17 , try this: ```sh -php composer.phar depends symfony/symfony --with-replaces -im ^3.0 +php composer.phar depends monolog/monolog -im ^1.17 +There is no installed package depending on "monolog/monolog" in versions not matching 1.17 +``` + +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 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 From 9bc2ee02e4a994522fc6761faa191e14921b569a Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Thu, 18 Feb 2016 22:26:44 +0100 Subject: [PATCH 06/11] Process feedback, code cleanup. --- src/Composer/Command/DependsCommand.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index 32441bb0c..bc39d0c3e 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -15,7 +15,7 @@ namespace Composer\Command; use Composer\DependencyResolver\Pool; use Composer\Package\Link; use Composer\Package\PackageInterface; -use Composer\Package\RootPackage; +use Composer\Package\RootPackageInterface; use Composer\Repository\ArrayRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; @@ -90,13 +90,15 @@ EOT $constraint = null; } $matchInvert = $input->getOption('invert-match-constraint'); + + // Parse rendering options $renderTree = $input->getOption('tree'); $recursive = $renderTree || $input->getOption('recursive'); // Resolve dependencies $results = $this->getDependents($needle, $repository->getPackages(), $constraint, $matchInvert, $recursive); if (empty($results)) { - $extra = isset($constraint) ? sprintf(' in versions %smatching %s', $matchInvert ? 'not ' : '', $textConstraint) : ''; + $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $matchInvert ? 'not ' : '', $textConstraint) : ''; $this->getIO()->writeError(sprintf('There is no installed package depending on "%s"%s', $needle, $extra)); } elseif ($renderTree) { @@ -172,7 +174,7 @@ EOT } /** - * @param string $needle The package to inspect. + * @param string|string[] $needle The package(s) to inspect. * @param PackageInterface[] $packages List of installed packages. * @param ConstraintInterface|null $constraint Optional constraint to filter by. * @param bool $invert Whether to invert matches on the previous constraint. @@ -184,14 +186,13 @@ EOT $needles = is_array($needle) ? $needle : array($needle); $results = array(); - /** - * Loop over all currently installed packages. - */ + // Loop over all currently installed packages. foreach ($packages as $package) { - // Retrieve all requirements, but dev only for the root package - $links = $package->getRequires(); - $links += $package->getReplaces(); - if ($package instanceof RootPackage) { + // Requirements and replaces are both considered valid reasons for a package to be installed + $links = $package->getRequires() + $package->getReplaces(); + + // Require-dev is only relevant for the root package + if ($package instanceof RootPackageInterface) { $links += $package->getDevRequires(); } From 5c98421ae8127d6b1ac279a56d878b844819be21 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Fri, 19 Feb 2016 23:47:33 +0100 Subject: [PATCH 07/11] Introduce BaseRepository common ancestor class for all repositories to offload command logic. --- src/Composer/Repository/ArrayRepository.php | 2 +- src/Composer/Repository/BaseRepository.php | 66 +++++++++++++++++++ .../Repository/CompositeRepository.php | 2 +- 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/Composer/Repository/BaseRepository.php diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 5f9eaf406..a78ee8f2f 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..c28ffa791 --- /dev/null +++ b/src/Composer/Repository/BaseRepository.php @@ -0,0 +1,66 @@ + + * 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. + */ + private 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) { + // Requirements and replaces are both considered valid reasons for a package to be installed + $links = $package->getRequires() + $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 From 692a3ed300daaede8f4fcf4176eb8ca2048c6d87 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Fri, 19 Feb 2016 23:56:46 +0100 Subject: [PATCH 08/11] Renamed Composer\Console\Command to BaseCommand for consistency with other abstract base classes. --- src/Composer/Command/AboutCommand.php | 2 +- src/Composer/Command/ArchiveCommand.php | 2 +- .../Command/{Command.php => BaseCommand.php} | 4 +- src/Composer/Command/ClearCacheCommand.php | 2 +- src/Composer/Command/ConfigCommand.php | 2 +- src/Composer/Command/CreateProjectCommand.php | 2 +- src/Composer/Command/DependsCommand.php | 43 +------------------ src/Composer/Command/DiagnoseCommand.php | 2 +- src/Composer/Command/DumpAutoloadCommand.php | 2 +- src/Composer/Command/GlobalCommand.php | 2 +- src/Composer/Command/HomeCommand.php | 2 +- src/Composer/Command/InitCommand.php | 2 +- src/Composer/Command/InstallCommand.php | 2 +- src/Composer/Command/LicensesCommand.php | 2 +- src/Composer/Command/RemoveCommand.php | 2 +- src/Composer/Command/RunScriptCommand.php | 2 +- src/Composer/Command/ScriptAliasCommand.php | 2 +- src/Composer/Command/SearchCommand.php | 2 +- src/Composer/Command/SelfUpdateCommand.php | 2 +- src/Composer/Command/ShowCommand.php | 2 +- src/Composer/Command/StatusCommand.php | 2 +- src/Composer/Command/SuggestsCommand.php | 2 +- src/Composer/Command/UpdateCommand.php | 2 +- src/Composer/Command/ValidateCommand.php | 2 +- 24 files changed, 26 insertions(+), 65 deletions(-) rename src/Composer/Command/{Command.php => BaseCommand.php} (96%) 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/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 ace946f3a..50c067de6 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 bc39d0c3e..f08b00521 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -15,13 +15,11 @@ namespace Composer\Command; use Composer\DependencyResolver\Pool; use Composer\Package\Link; use Composer\Package\PackageInterface; -use Composer\Package\RootPackageInterface; use Composer\Repository\ArrayRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; -use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\VersionParser; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; @@ -30,10 +28,9 @@ 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 BaseCommand { protected function configure() { @@ -173,41 +170,5 @@ EOT } } - /** - * @param string|string[] $needle The package(s) to inspect. - * @param PackageInterface[] $packages List of installed packages. - * @param ConstraintInterface|null $constraint Optional constraint to filter by. - * @param bool $invert Whether to invert matches on the previous constraint. - * @param bool $recurse Whether to recursively expand the requirement tree. - * @return array An array with dependers as key, and as values an array containing the source package and the link respectively - */ - private function getDependents($needle, $packages, $constraint = null, $invert = false, $recurse = true) - { - $needles = is_array($needle) ? $needle : array($needle); - $results = array(); - // Loop over all currently installed packages. - foreach ($packages as $package) { - // Requirements and replaces are both considered valid reasons for a package to be installed - $links = $package->getRequires() + $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(), $packages, null, false, true) : array()); - } - } - } - } - } - ksort($results); - return $results; - } } diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 2b1927f6b..0ee606f4a 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/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 e1cf17892..9ac558939 100644 --- a/src/Composer/Command/RunScriptCommand.php +++ b/src/Composer/Command/RunScriptCommand.php @@ -22,7 +22,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 5ddce2954..af7593fe1 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 From 75bb0d9b10be8b6b5ccf5aadc825608cd9a35e3f Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Sat, 20 Feb 2016 01:39:24 +0100 Subject: [PATCH 09/11] Implemented Prohibits and Depends correctly now. --- .../Command/BaseDependencyCommand.php | 188 ++++++++++++++++++ src/Composer/Command/DependsCommand.php | 149 ++------------ src/Composer/Command/ProhibitsCommand.php | 55 +++++ src/Composer/Console/Application.php | 50 ++--- src/Composer/Repository/BaseRepository.php | 10 +- 5 files changed, 291 insertions(+), 161 deletions(-) create mode 100644 src/Composer/Command/BaseDependencyCommand.php create mode 100644 src/Composer/Command/ProhibitsCommand.php diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php new file mode 100644 index 000000000..a84c62bc3 --- /dev/null +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -0,0 +1,188 @@ + + * 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\Semver\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 + */ + 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); + } + } + + /** + * 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/DependsCommand.php b/src/Composer/Command/DependsCommand.php index f08b00521..3da8d991b 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -12,39 +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\Semver\VersionParser; -use Symfony\Component\Console\Helper\Table; 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 Niels Keurentjes */ -class DependsCommand extends BaseCommand +class DependsCommand extends BaseDependencyCommand { + /** + * Configure command metadata. + */ protected function configure() { + parent::configure(); + $this ->setName('depends') ->setAliases(array('why')) - ->setDescription('Shows which packages depend on the given package') - ->setDefinition(array( - new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'), - new InputOption('recursive', 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), - new InputOption('tree', 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), - 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'), - )) + ->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); - - // 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); - - // Find packages that are or provide the requested package first - $needle = $input->getArgument('package'); - $packages = $pool->whatProvides($needle); - if (empty($packages)) { - throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); - } - - // Parse options that are only relevant for the initial needle(s) - if ('*' !== ($textConstraint = $input->getOption('match-constraint'))) { - $versionParser = new VersionParser(); - $constraint = $versionParser->parseConstraints($textConstraint); - } else { - $constraint = null; - } - $matchInvert = $input->getOption('invert-match-constraint'); - - // Parse rendering options - $renderTree = $input->getOption('tree'); - $recursive = $renderTree || $input->getOption('recursive'); - - // Resolve dependencies - $results = $this->getDependents($needle, $repository->getPackages(), $constraint, $matchInvert, $recursive); - if (empty($results)) { - $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $matchInvert ? '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); - } + parent::doExecute($input, $output, false); } - - /** - * Assembles and prints a bottom-up table of the dependencies. - * - * @param OutputInterface $output - * @param array $results - */ - private 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 - */ - public 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/ProhibitsCommand.php b/src/Composer/Command/ProhibitsCommand.php new file mode 100644 index 000000000..3f0ea15a5 --- /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) + { + parent::doExecute($input, $output, true); + } +} 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/Repository/BaseRepository.php b/src/Composer/Repository/BaseRepository.php index c28ffa791..3b0b81b28 100644 --- a/src/Composer/Repository/BaseRepository.php +++ b/src/Composer/Repository/BaseRepository.php @@ -34,15 +34,19 @@ abstract class BaseRepository implements RepositoryInterface * @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. */ - private function getDependents($needle, $constraint = null, $invert = false, $recurse = true) + 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) { - // Requirements and replaces are both considered valid reasons for a package to be installed - $links = $package->getRequires() + $package->getReplaces(); + $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) { From 47da91d99830f8136ba151456245673447c3c564 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Sat, 20 Feb 2016 01:53:51 +0100 Subject: [PATCH 10/11] Updated documentation for depends/prohibits. --- doc/03-cli.md | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index b73395a46..c84a43d29 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -337,16 +337,11 @@ php composer.phar depends doctrine/lexer doctrine/common v2.6.1 requires doctrine/lexer (1.*) ``` -If you want, for example, to find any installed package that is **not** -allowing Monolog to be upgraded to version 1.17 , try this: +You can optionally specify a version constraint after the package to limit the +search. -```sh -php composer.phar depends monolog/monolog -im ^1.17 -There is no installed package depending on "monolog/monolog" in versions not matching 1.17 -``` - -Add the `--tree` or `-t` flag to show a recursive tree of why the package is depended -upon, for example: +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 psr/log -t @@ -364,9 +359,36 @@ psr/log 1.0.0 Common interface for logging libraries * **--recursive (-r):** Recursively resolves up to the root package. * **--tree (-t):** Prints the results as a nested tree, implies -r. -* **--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. + +## 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 From 22a6f6c612a7f201465d803ac2b1fb5dfcdb9cf0 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Sat, 20 Feb 2016 13:33:24 +0100 Subject: [PATCH 11/11] phpdoc consistency. --- src/Composer/Command/BaseDependencyCommand.php | 4 +++- src/Composer/Command/DependsCommand.php | 2 +- src/Composer/Command/ProhibitsCommand.php | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index a84c62bc3..1e4d2e715 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -57,7 +57,8 @@ class BaseDependencyCommand extends BaseCommand * * @param InputInterface $input * @param OutputInterface $output - * @param bool @inverted Whether + * @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) { @@ -121,6 +122,7 @@ class BaseDependencyCommand extends BaseCommand } else { $this->printTable($output, $results); } + return 0; } /** diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index 3da8d991b..0cfb5b83d 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -50,6 +50,6 @@ EOT */ protected function execute(InputInterface $input, OutputInterface $output) { - parent::doExecute($input, $output, false); + return parent::doExecute($input, $output, false); } } diff --git a/src/Composer/Command/ProhibitsCommand.php b/src/Composer/Command/ProhibitsCommand.php index 3f0ea15a5..fb717eb7e 100644 --- a/src/Composer/Command/ProhibitsCommand.php +++ b/src/Composer/Command/ProhibitsCommand.php @@ -50,6 +50,6 @@ EOT */ protected function execute(InputInterface $input, OutputInterface $output) { - parent::doExecute($input, $output, true); + return parent::doExecute($input, $output, true); } }