Browse Source

Implemented Prohibits and Depends correctly now.

Niels Keurentjes 9 years ago
parent
commit
75bb0d9b10

+ 188 - 0
src/Composer/Command/BaseDependencyCommand.php

@@ -0,0 +1,188 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <niels.keurentjes@omines.com>
+ */
+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('<info>There is no installed package depending on "%s"%s</info>',
+                $needle, $extra));
+        } elseif ($renderTree) {
+            $root = $packages[0];
+            $this->getIO()->write(sprintf('<info>%s</info> %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 ? '   ' : '|  '));
+        }
+    }
+}

+ 12 - 131
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 <niels.keurentjes@omines.com>
  */
-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(<<<EOT
 Displays detailed information about where a package is referenced.
 
@@ -55,120 +41,15 @@ EOT
         ;
     }
 
-    protected function execute(InputInterface $input, OutputInterface $output)
-    {
-        // Emit command event on startup
-        $composer = $this->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('<info>There is no installed package depending on "%s"%s</info>',
-                        $needle, $extra));
-        } elseif ($renderTree) {
-            $root = $packages[0];
-            $this->getIO()->write(sprintf('<info>%s</info> %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.
+     * Execute the function.
      *
+     * @param InputInterface $input
      * @param OutputInterface $output
-     * @param array $results
+     * @return int|null
      */
-    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 = '')
+    protected function execute(InputInterface $input, OutputInterface $output)
     {
-        $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 ? '   ' : '|  '));
-        }
+        parent::doExecute($input, $output, false);
     }
-
-
 }

+ 55 - 0
src/Composer/Command/ProhibitsCommand.php

@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <niels.keurentjes@omines.com>
+ */
+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(<<<EOT
+Displays detailed information about why a package cannot be installed.
+
+<info>php composer.phar prohibits composer/composer</info>
+
+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);
+    }
+}

+ 26 - 24
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();

+ 7 - 3
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) {