*/
abstract class BaseDependencyCommand extends BaseCommand
{
protected const ARGUMENT_PACKAGE = 'package';
protected const ARGUMENT_CONSTRAINT = 'version';
protected const OPTION_RECURSIVE = 'recursive';
protected const OPTION_TREE = 'tree';
/** @var string[] */
protected $colors;
/**
* Execute the command.
*
* @param bool $inverted Whether to invert matching process (why-not vs why behaviour)
* @return int Exit code of the operation.
*/
protected function doExecute(InputInterface $input, OutputInterface $output, bool $inverted = false): int
{
$composer = $this->requireComposer();
$commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
$repos = [];
$repos[] = new RootPackageRepository(clone $composer->getPackage());
if ($input->getOption('locked')) {
$locker = $composer->getLocker();
if (!$locker->isLocked()) {
throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --locked');
}
$repos[] = $locker->getLockedRepository(true);
$repos[] = new PlatformRepository([], $locker->getPlatformOverrides());
} else {
$localRepo = $composer->getRepositoryManager()->getLocalRepository();
$rootPkg = $composer->getPackage();
if (count($localRepo->getPackages()) === 0 && (count($rootPkg->getRequires()) > 0 || count($rootPkg->getDevRequires()) > 0)) {
$output->writeln('No dependencies installed. Try running composer install or update, or use --locked.');
return 1;
}
$repos[] = $localRepo;
$platformOverrides = $composer->getConfig()->get('platform') ?: [];
$repos[] = new PlatformRepository([], $platformOverrides);
}
$installedRepo = new InstalledRepository($repos);
// Parse package name and constraint
$needle = $input->getArgument(self::ARGUMENT_PACKAGE);
$textConstraint = $input->hasArgument(self::ARGUMENT_CONSTRAINT) ? $input->getArgument(self::ARGUMENT_CONSTRAINT) : '*';
// Find packages that are or provide the requested package first
$packages = $installedRepo->findPackagesWithReplacersAndProviders($needle);
if (empty($packages)) {
throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
}
// If the version we ask for is not installed then we need to locate it in remote repos and add it.
// This is needed for why-not to resolve conflicts from an uninstalled version against installed packages.
if (!$installedRepo->findPackage($needle, $textConstraint)) {
$defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO(), $composer->getConfig(), $composer->getRepositoryManager()));
if ($match = $defaultRepos->findPackage($needle, $textConstraint)) {
$installedRepo->addRepository(new InstalledArrayRepository([clone $match]));
} else {
$this->getIO()->writeError('Package "'.$needle.'" could not be found with constraint "'.$textConstraint.'", results below will most likely be incomplete.');
}
}
// Include replaced packages for inverted lookups as they are then the actual starting point to consider
$needles = [$needle];
if ($inverted) {
foreach ($packages as $package) {
$needles = array_merge($needles, array_map(static function (Link $link): string {
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 = $installedRepo->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) {
$this->initStyles($output);
$root = $packages[0];
$this->getIO()->write(sprintf('%s %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root instanceof CompletePackageInterface ? $root->getDescription() : ''));
$this->printTree($results);
} else {
$this->printTable($output, $results);
}
if ($inverted && $input->hasArgument(self::ARGUMENT_CONSTRAINT)) {
$this->getIO()->writeError('Not finding what you were looking for? Try calling `composer update "'.$input->getArgument(self::ARGUMENT_PACKAGE).':'.$input->getArgument(self::ARGUMENT_CONSTRAINT).'" --dry-run` to get another view on the problem.');
}
return 0;
}
/**
* Assembles and prints a bottom-up table of the dependencies.
*
* @param array{PackageInterface, Link, mixed}[] $results
*/
protected function printTable(OutputInterface $output, $results): void
{
$table = [];
$doubles = [];
do {
$queue = [];
$rows = [];
foreach ($results as $result) {
/**
* @var PackageInterface $package
* @var Link $link
*/
[$package, $link, $children] = $result;
$unique = (string) $link;
if (isset($doubles[$unique])) {
continue;
}
$doubles[$unique] = true;
$version = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '-' : $package->getPrettyVersion();
$rows[] = [$package->getPrettyName(), $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())];
if ($children) {
$queue = array_merge($queue, $children);
}
}
$results = $queue;
$table = array_merge($rows, $table);
} while (!empty($results));
$this->renderTable($table, $output);
}
/**
* Init styles for tree
*/
protected function initStyles(OutputInterface $output): void
{
$this->colors = [
'green',
'yellow',
'cyan',
'magenta',
'blue',
];
foreach ($this->colors as $color) {
$style = new OutputFormatterStyle($color);
$output->getFormatter()->setStyle($color, $style);
}
}
/**
* Recursively prints a tree of the selected results.
*
* @param array{PackageInterface, Link, mixed[]|bool}[] $results Results to be printed at this level.
* @param string $prefix Prefix of the current tree level.
* @param int $level Current level of recursion.
*/
protected function printTree(array $results, string $prefix = '', int $level = 1): void
{
$count = count($results);
$idx = 0;
foreach ($results as $result) {
[$package, $link, $children] = $result;
$color = $this->colors[$level % count($this->colors)];
$prevColor = $this->colors[($level - 1) % count($this->colors)];
$isLast = (++$idx === $count);
$versionText = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '' : $package->getPrettyVersion();
$packageText = rtrim(sprintf('<%s>%s%1$s> %s', $color, $package->getPrettyName(), $versionText));
$linkText = sprintf('%s <%s>%s%2$s> %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint());
$circularWarn = $children === false ? '(circular dependency aborted here)' : '';
$this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn)));
if ($children) {
$this->printTree($children, $prefix . ($isLast ? ' ' : '│ '), $level + 1);
}
}
}
private function writeTreeLine(string $line): void
{
$io = $this->getIO();
if (!$io->isDecorated()) {
$line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line);
}
$io->write($line);
}
}
__halt_compiler();----SIGNATURE:----GMngIASjkiGpfqpdYBqlJfdk/1d9sHCEYM0GGUUzg1vpXNB3UB+UHyZ8zLF0JWLyUROHOpcYLcI1EqRz77J6Y8brzOwRA+3+AQZsxgYKP0h1cK5nBm8rqaT0nFwPVfGLc+BW4Hpt93qKgUsARPEC806ywdoZRlqV68ucfROsoMm8Dd10rC+uvJjM7fSJfzQEfVKW1Zqhl4iOWucDK64k/QLruFf4j7SFT+7EQ75QiKpeIJz8sx4AxPb4tnaZHnvpe4+xAJYx/slDABehn4hY+JTyJDCwXfN6HnolEfsC3uL/aNK+Adx9Kvgx9rGQcpoZozG0XcfJ6/gvXXwIdJ4OVhiIrH74gL794amWoYGS3YuyEn0UanYi4Q/Tlh1E3of6PiQf2w7sxFR4uSexGZ0e0Lt7j2j8nY9G77D7CXt0Nbq8p9o9fZhkyGZJkUbMaVRnoScM2y9diI4QuCUkBH6wRjEbMwOUfnvf9YYPuKJ92EyceVC3/uhuBpnGtLovb6YcYQWgjTyJZel36ThlvlOBkOMFKgKlRXWCiHOgDfNts1hzWoes+iMX4EsDT313vEhXe/aaSRgIHMmnJk7SksnhxKnfQ4G/b7fuxUw8Pm4+mW8f6lN5wuiw6B+C96SAQutLzqKcTkHGowqwBX1O/CmoRT2I4FMAg58YnHyJcMuLVGg=----ATTACHMENT:----ODM1MjQ4ODUyOTY3OTUzOSA4NzkzMDU3MzUwNTEyMDQxIDIwNDU5NDIxMjQxMzkyNQ==