Эх сурвалжийг харах

List project suggestions in create-project command

Resolves https://github.com/composer/composer/issues/2752
Haralan Dobrev 9 жил өмнө
parent
commit
cc389d6c1a

+ 13 - 1
src/Composer/Command/CreateProjectCommand.php

@@ -17,6 +17,7 @@ use Composer\Factory;
 use Composer\Installer;
 use Composer\Installer\ProjectInstaller;
 use Composer\Installer\InstallationManager;
+use Composer\Installer\SuggestedPackagesReporter;
 use Composer\IO\IOInterface;
 use Composer\Package\BasePackage;
 use Composer\DependencyResolver\Pool;
@@ -47,6 +48,11 @@ use Composer\Package\Version\VersionParser;
  */
 class CreateProjectCommand extends BaseCommand
 {
+    /**
+     * @var SuggestedPackagesReporter
+     */
+    protected $suggestedPackagesReporter;
+
     protected function configure()
     {
         $this
@@ -142,6 +148,8 @@ EOT
         // we need to manually load the configuration to pass the auth credentials to the io interface!
         $io->loadConfiguration($config);
 
+        $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io);
+
         if ($packageName !== null) {
             $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repository, $disablePlugins, $noScripts, $keepVcs, $noProgress);
         } else {
@@ -168,7 +176,8 @@ EOT
                 ->setPreferDist($preferDist)
                 ->setDevMode($installDevPackages)
                 ->setRunScripts(!$noScripts)
-                ->setIgnorePlatformRequirements($ignorePlatformReqs);
+                ->setIgnorePlatformRequirements($ignorePlatformReqs)
+                ->setSuggestedPackagesReporter($this->suggestedPackagesReporter);
 
             if ($disablePlugins) {
                 $installer->disablePlugins();
@@ -318,6 +327,9 @@ EOT
         $im->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package));
         $im->notifyInstalls($io);
 
+        // collect suggestions
+        $this->suggestedPackagesReporter->addSuggestionsFromPackage($package);
+
         $installedFromVcs = 'source' === $package->getInstallationSource();
 
         $io->writeError('<info>Created project in ' . $directory . '</info>');

+ 20 - 20
src/Composer/Installer.php

@@ -29,6 +29,7 @@ use Composer\EventDispatcher\EventDispatcher;
 use Composer\Installer\InstallationManager;
 use Composer\Installer\InstallerEvents;
 use Composer\Installer\NoopInstaller;
+use Composer\Installer\SuggestedPackagesReporter;
 use Composer\IO\IOInterface;
 use Composer\Package\AliasPackage;
 use Composer\Package\CompletePackage;
@@ -120,9 +121,9 @@ class Installer
     protected $whitelistDependencies = false;
 
     /**
-     * @var array
+     * @var SuggestedPackagesReporter
      */
-    protected $suggestedPackages;
+    protected $suggestedPackagesReporter;
 
     /**
      * @var RepositoryInterface
@@ -214,8 +215,11 @@ class Installer
         $aliases = $this->getRootAliases();
         $this->aliasPlatformPackages($platformRepo, $aliases);
 
+        if (!$this->suggestedPackagesReporter) {
+            $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io);
+        }
+
         try {
-            $this->suggestedPackages = array();
             $res = $this->doInstall($localRepo, $installedRepo, $platformRepo, $aliases, $this->devMode);
             if ($res !== 0) {
                 return $res;
@@ -233,16 +237,7 @@ class Installer
 
         // output suggestions if we're in dev mode
         if ($this->devMode) {
-            foreach ($this->suggestedPackages as $suggestion) {
-                $target = $suggestion['target'];
-                foreach ($installedRepo->getPackages() as $package) {
-                    if (in_array($target, $package->getNames())) {
-                        continue 2;
-                    }
-                }
-
-                $this->io->writeError($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')');
-            }
+            $this->suggestedPackagesReporter->output($installedRepo);
         }
 
         # Find abandoned packages and warn user
@@ -538,13 +533,7 @@ class Installer
         foreach ($operations as $operation) {
             // collect suggestions
             if ('install' === $operation->getJobType()) {
-                foreach ($operation->getPackage()->getSuggests() as $target => $reason) {
-                    $this->suggestedPackages[] = array(
-                        'source' => $operation->getPackage()->getPrettyName(),
-                        'target' => $target,
-                        'reason' => $reason,
-                    );
-                }
+                $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage());
             }
 
             // not installing from lock, force dev packages' references if they're in root package refs
@@ -1508,4 +1497,15 @@ class Installer
 
         return $this;
     }
+
+    /**
+     * @param SuggestedPackagesReporter $suggestedPackagesReporter
+     * @return Installer
+     */
+    public function setSuggestedPackagesReporter(SuggestedPackagesReporter $suggestedPackagesReporter)
+    {
+        $this->suggestedPackagesReporter = $suggestedPackagesReporter;
+
+        return $this;
+    }
 }

+ 126 - 0
src/Composer/Installer/SuggestedPackagesReporter.php

@@ -0,0 +1,126 @@
+<?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\Installer;
+
+use Composer\IO\IOInterface;
+use Composer\Package\PackageInterface;
+use Composer\Repository\RepositoryInterface;
+
+/**
+ * Add suggested packages from different places to output them in the end.
+ *
+ * @author Haralan Dobrev <hkdobrev@gmail.com>
+ */
+class SuggestedPackagesReporter
+{
+    /**
+     * @var array
+     */
+    protected $suggestedPackages = array();
+
+    /**
+     * @var Composer\IO\IOInterface
+     */
+    private $io;
+
+    public function __construct(IOInterface $io)
+    {
+        $this->io = $io;
+    }
+
+    /**
+     * @return array Suggested packages with source, target and reason keys.
+     */
+    public function getPackages()
+    {
+        return $this->suggestedPackages;
+    }
+
+    /**
+     * Add suggested packages to be listed after install
+     *
+     * Could be used to add suggested packages both from the installer
+     * or from CreateProjectCommand.
+     *
+     * @param string $source Source package which made the suggestion
+     * @param string $target Target package to be suggested
+     * @param string $reason Reason the target package to be suggested
+     * @return SuggestedPackagesReporter
+     */
+    public function addPackage($source, $target, $reason)
+    {
+        $this->suggestedPackages[] = array(
+            'source' => $source,
+            'target' => $target,
+            'reason' => $reason,
+        );
+
+        return $this;
+    }
+
+    /**
+     * Add all suggestions from a package.
+     *
+     * @param PackageInterface $package
+     * @return SuggestedPackagesReporter
+     */
+    public function addSuggestionsFromPackage(PackageInterface $package)
+    {
+        $source = $package->getPrettyName();
+        foreach ($package->getSuggests() as $target => $reason) {
+            $this->addPackage(
+                $source,
+                $target,
+                $reason
+            );
+        }
+
+        return $this;
+    }
+
+    /**
+     * Output suggested packages.
+     * Do not list the ones already installed if installed repository provided.
+     *
+     * @param RepositoryInterface $installedRepo Installed packages
+     * @return SuggestedPackagesReporter
+     */
+    public function output(RepositoryInterface $installedRepo = null)
+    {
+        $suggestedPackages = $this->getPackages();
+        $installedPackages = array();
+        if (null !== $installedRepo && ! empty($suggestedPackages)) {
+            foreach ($installedRepo->getPackages() as $package) {
+                $installedPackages = array_merge(
+                    $installedPackages,
+                    $package->getNames()
+                );
+            }
+        }
+
+        foreach ($suggestedPackages as $suggestion) {
+            if (in_array($suggestion['target'], $installedPackages)) {
+                continue;
+            }
+
+            $this->io->writeError(sprintf(
+                '%s suggests installing %s (%s)',
+                $suggestion['source'],
+                $suggestion['target'],
+                $suggestion['reason']
+            ));
+        }
+
+        return $this;
+    }
+}

+ 227 - 0
tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php

@@ -0,0 +1,227 @@
+<?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\Test\Installer;
+
+use Composer\Installer\SuggestedPackagesReporter;
+
+/**
+ * @coversDefaultClass Composer\Installer\SuggestedPackagesReporter
+ */
+class SuggestedPackagesReporterTest extends \PHPUnit_Framework_TestCase
+{
+    private $io;
+    private $suggestedPackagesReporter;
+
+    protected function setUp()
+    {
+        $this->io = $this->getMock('Composer\IO\IOInterface');
+
+        $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io);
+    }
+
+    /**
+     * @covers ::__construct
+     */
+    public function testContrsuctor()
+    {
+        $this->io->expects($this->once())
+            ->method('writeError');
+
+        $suggestedPackagesReporter = new SuggestedPackagesReporter($this->io);
+        $suggestedPackagesReporter->addPackage('a', 'b', 'c');
+        $suggestedPackagesReporter->output();
+    }
+
+    /**
+     * @covers ::getPackages
+     */
+    public function testGetPackagesEmptyByDefault()
+    {
+        $this->assertSame(
+            array(),
+            $this->suggestedPackagesReporter->getPackages()
+        );
+    }
+
+    /**
+     * @covers ::getPackages
+     * @covers ::addPackage
+     */
+    public function testGetPackages()
+    {
+        $suggestedPackage = $this->getSuggestedPackageArray();
+        $this->suggestedPackagesReporter->addPackage(
+            $suggestedPackage['source'],
+            $suggestedPackage['target'],
+            $suggestedPackage['reason']
+        );
+        $this->assertSame(
+            array($suggestedPackage),
+            $this->suggestedPackagesReporter->getPackages()
+        );
+    }
+
+    /**
+     * Test addPackage appends packages.
+     * Also test targets can be duplicated.
+     *
+     * @covers ::addPackage
+     */
+    public function testAddPackageAppends()
+    {
+        $suggestedPackageA = $this->getSuggestedPackageArray();
+        $suggestedPackageB = $this->getSuggestedPackageArray();
+        $suggestedPackageB['source'] = 'different source';
+        $suggestedPackageB['reason'] = 'different reason';
+        $this->suggestedPackagesReporter->addPackage(
+            $suggestedPackageA['source'],
+            $suggestedPackageA['target'],
+            $suggestedPackageA['reason']
+        );
+        $this->suggestedPackagesReporter->addPackage(
+            $suggestedPackageB['source'],
+            $suggestedPackageB['target'],
+            $suggestedPackageB['reason']
+        );
+        $this->assertSame(
+            array($suggestedPackageA, $suggestedPackageB),
+            $this->suggestedPackagesReporter->getPackages()
+        );
+    }
+
+    /**
+     * @covers ::addSuggestionsFromPackage
+     */
+    public function testAddSuggestionsFromPackage()
+    {
+        $package = $this->createPackageMock();
+        $package->expects($this->once())
+            ->method('getSuggests')
+            ->will($this->returnValue(array(
+                'target-a' => 'reason-a',
+                'target-b' => 'reason-b',
+            )));
+        $package->expects($this->once())
+            ->method('getPrettyName')
+            ->will($this->returnValue('package-pretty-name'));
+
+        $this->suggestedPackagesReporter->addSuggestionsFromPackage($package);
+        $this->assertSame(array(
+            array(
+                'source' => 'package-pretty-name',
+                'target' => 'target-a',
+                'reason' => 'reason-a',
+            ),
+            array(
+                'source' => 'package-pretty-name',
+                'target' => 'target-b',
+                'reason' => 'reason-b',
+            ),
+        ), $this->suggestedPackagesReporter->getPackages());
+    }
+
+    /**
+     * @covers ::output
+     */
+    public function testOutput()
+    {
+        $this->suggestedPackagesReporter->addPackage('a', 'b', 'c');
+
+        $this->io->expects($this->once())
+            ->method('writeError')
+            ->with('a suggests installing b (c)');
+
+        $this->suggestedPackagesReporter->output();
+    }
+
+    /**
+     * @covers ::output
+     */
+    public function testOutputMultiplePackages()
+    {
+        $this->suggestedPackagesReporter->addPackage('a', 'b', 'c');
+        $this->suggestedPackagesReporter->addPackage('source package', 'target', 'because reasons');
+
+        $this->io->expects($this->at(0))
+            ->method('writeError')
+            ->with('a suggests installing b (c)');
+
+        $this->io->expects($this->at(1))
+            ->method('writeError')
+            ->with('source package suggests installing target (because reasons)');
+
+        $this->suggestedPackagesReporter->output();
+    }
+
+    /**
+     * @covers ::output
+     */
+    public function testOutputSkipInstalledPackages()
+    {
+        $repository = $this->getMock('Composer\Repository\RepositoryInterface');
+        $package1 = $this->getMock('Composer\Package\PackageInterface');
+        $package2 = $this->getMock('Composer\Package\PackageInterface');
+
+        $package1->expects($this->once())
+            ->method('getNames')
+            ->will($this->returnValue(array('x', 'y')));
+
+        $package2->expects($this->once())
+            ->method('getNames')
+            ->will($this->returnValue(array('b')));
+
+        $repository->expects($this->once())
+            ->method('getPackages')
+            ->will($this->returnValue(array(
+                $package1,
+                $package2,
+            )));
+
+        $this->suggestedPackagesReporter->addPackage('a', 'b', 'c');
+        $this->suggestedPackagesReporter->addPackage('source package', 'target', 'because reasons');
+
+        $this->io->expects($this->once())
+            ->method('writeError')
+            ->with('source package suggests installing target (because reasons)');
+
+        $this->suggestedPackagesReporter->output($repository);
+    }
+
+    /**
+     * @covers ::output
+     */
+    public function testOutputNotGettingInstalledPackagesWhenNoSuggestions()
+    {
+        $repository = $this->getMock('Composer\Repository\RepositoryInterface');
+        $repository->expects($this->exactly(0))
+            ->method('getPackages');
+
+        $this->suggestedPackagesReporter->output($repository);
+    }
+
+    private function getSuggestedPackageArray()
+    {
+        return array(
+            'source' => 'a',
+            'target' => 'b',
+            'reason' => 'c',
+        );
+    }
+
+    private function createPackageMock()
+    {
+        return $this->getMockBuilder('Composer\Package\Package')
+            ->setConstructorArgs(array(md5(mt_rand()), '1.0.0.0', '1.0.0'))
+            ->getMock();
+    }
+}