BaseDependencyCommand.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Command;
  12. use Composer\DependencyResolver\Pool;
  13. use Composer\Package\Link;
  14. use Composer\Package\PackageInterface;
  15. use Composer\Repository\ArrayRepository;
  16. use Composer\Repository\CompositeRepository;
  17. use Composer\Repository\PlatformRepository;
  18. use Composer\Plugin\CommandEvent;
  19. use Composer\Plugin\PluginEvents;
  20. use Composer\Semver\VersionParser;
  21. use Symfony\Component\Console\Helper\Table;
  22. use Symfony\Component\Console\Input\InputArgument;
  23. use Symfony\Component\Console\Input\InputInterface;
  24. use Symfony\Component\Console\Input\InputOption;
  25. use Symfony\Component\Console\Output\OutputInterface;
  26. /**
  27. * Base implementation for commands mapping dependency relationships.
  28. *
  29. * @author Niels Keurentjes <niels.keurentjes@omines.com>
  30. */
  31. class BaseDependencyCommand extends BaseCommand
  32. {
  33. const ARGUMENT_PACKAGE = 'package';
  34. const ARGUMENT_CONSTRAINT = 'constraint';
  35. const OPTION_RECURSIVE = 'recursive';
  36. const OPTION_TREE = 'tree';
  37. /**
  38. * Set common options and arguments.
  39. */
  40. protected function configure()
  41. {
  42. $this->setDefinition(array(
  43. new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect'),
  44. new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::OPTIONAL, 'Optional version constraint', '*'),
  45. new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'),
  46. new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'),
  47. ));
  48. }
  49. /**
  50. * Execute the command.
  51. *
  52. * @param InputInterface $input
  53. * @param OutputInterface $output
  54. * @param bool @inverted Whether
  55. */
  56. protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false)
  57. {
  58. // Emit command event on startup
  59. $composer = $this->getComposer();
  60. $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output);
  61. $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
  62. // Prepare repositories and set up a pool
  63. $platformOverrides = $composer->getConfig()->get('platform') ?: array();
  64. $repository = new CompositeRepository(array(
  65. new ArrayRepository(array($composer->getPackage())),
  66. $composer->getRepositoryManager()->getLocalRepository(),
  67. new PlatformRepository(array(), $platformOverrides),
  68. ));
  69. $pool = new Pool();
  70. $pool->addRepository($repository);
  71. // Parse package name and constraint
  72. list($needle, $textConstraint) = array_pad(explode(':', $input->getArgument(self::ARGUMENT_PACKAGE)),
  73. 2, $input->getArgument(self::ARGUMENT_CONSTRAINT));
  74. // Find packages that are or provide the requested package first
  75. $packages = $pool->whatProvides($needle);
  76. if (empty($packages)) {
  77. throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
  78. }
  79. // Include replaced packages for inverted lookups as they are then the actual starting point to consider
  80. $needles = array($needle);
  81. if ($inverted) {
  82. foreach ($packages as $package) {
  83. $needles = array_merge($needles, array_map(function (Link $link) {
  84. return $link->getTarget();
  85. }, $package->getReplaces()));
  86. }
  87. }
  88. // Parse constraint if one was supplied
  89. if ('*' !== $textConstraint) {
  90. $versionParser = new VersionParser();
  91. $constraint = $versionParser->parseConstraints($textConstraint);
  92. } else {
  93. $constraint = null;
  94. }
  95. // Parse rendering options
  96. $renderTree = $input->getOption(self::OPTION_TREE);
  97. $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE);
  98. // Resolve dependencies
  99. $results = $repository->getDependents($needles, $constraint, $inverted, $recursive);
  100. if (empty($results)) {
  101. $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : '';
  102. $this->getIO()->writeError(sprintf('<info>There is no installed package depending on "%s"%s</info>',
  103. $needle, $extra));
  104. } elseif ($renderTree) {
  105. $root = $packages[0];
  106. $this->getIO()->write(sprintf('<info>%s</info> %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root->getDescription()));
  107. $this->printTree($output, $results);
  108. } else {
  109. $this->printTable($output, $results);
  110. }
  111. }
  112. /**
  113. * Assembles and prints a bottom-up table of the dependencies.
  114. *
  115. * @param OutputInterface $output
  116. * @param array $results
  117. */
  118. protected function printTable(OutputInterface $output, $results)
  119. {
  120. $table = array();
  121. $doubles = array();
  122. do {
  123. $queue = array();
  124. $rows = array();
  125. foreach($results as $result) {
  126. /**
  127. * @var PackageInterface $package
  128. * @var Link $link
  129. */
  130. list($package, $link, $children) = $result;
  131. $unique = (string)$link;
  132. if (isset($doubles[$unique])) {
  133. continue;
  134. }
  135. $doubles[$unique] = true;
  136. $version = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion();
  137. $rows[] = array($package->getPrettyName(), $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint()));
  138. $queue = array_merge($queue, $children);
  139. }
  140. $results = $queue;
  141. $table = array_merge($rows, $table);
  142. } while(!empty($results));
  143. // Render table
  144. $renderer = new Table($output);
  145. $renderer->setStyle('compact')->setRows($table)->render();
  146. }
  147. /**
  148. * Recursively prints a tree of the selected results.
  149. *
  150. * @param OutputInterface $output
  151. * @param array $results
  152. * @param string $prefix
  153. */
  154. protected function printTree(OutputInterface $output, $results, $prefix = '')
  155. {
  156. $count = count($results);
  157. $idx = 0;
  158. foreach($results as $key => $result) {
  159. /**
  160. * @var PackageInterface $package
  161. * @var Link $link
  162. */
  163. list($package, $link, $children) = $result;
  164. $isLast = (++$idx == $count);
  165. $versionText = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '' : $package->getPrettyVersion();
  166. $packageText = rtrim(sprintf('%s %s', $package->getPrettyName(), $versionText));
  167. $linkText = implode(' ', array($link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()));
  168. $output->write(sprintf("%s%s %s (%s)\n", $prefix, $isLast ? '`-' : '|-', $packageText, $linkText));
  169. $this->printTree($output, $children, $prefix . ($isLast ? ' ' : '| '));
  170. }
  171. }
  172. }