Pārlūkot izejas kodu

Extracting logic into a new class related to selecting the latest version

Also refactored InitCommand slightly so that you can use this "latest version"
functionality when searching for a package as well.
Ryan Weaver 11 gadi atpakaļ
vecāks
revīzija
aea2e901a9

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

@@ -21,6 +21,7 @@ use Composer\IO\IOInterface;
 use Composer\Package\BasePackage;
 use Composer\DependencyResolver\Pool;
 use Composer\DependencyResolver\Operation\InstallOperation;
+use Composer\Package\Version\VersionSelector;
 use Composer\Repository\ComposerRepository;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\FilesystemRepository;
@@ -265,23 +266,14 @@ EOT
         $pool = new Pool($stability);
         $pool->addRepository($sourceRepo);
 
-        $constraint = $packageVersion ? $parser->parseConstraints($packageVersion) : null;
-        $candidates = $pool->whatProvides($name, $constraint, true);
+        // find the latest version if there are multiple
+        $versionSelector = new VersionSelector($pool);
+        $package = $versionSelector->findBestCandidate($name, $packageVersion);
 
-        if (!$candidates) {
+        if (!$package) {
             throw new \InvalidArgumentException("Could not find package $name" . ($packageVersion ? " with version $packageVersion." : " with stability $stability."));
         }
 
-        // select highest version if we have many
-        // logic is repeated in InitCommand
-        $package = reset($candidates);
-        foreach ($candidates as $candidate) {
-            if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
-                $package = $candidate;
-            }
-        }
-        unset($candidates);
-
         if (null === $directory) {
             $parts = explode("/", $name, 2);
             $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts);

+ 54 - 39
src/Composer/Command/InitCommand.php

@@ -16,6 +16,7 @@ use Composer\DependencyResolver\Pool;
 use Composer\Json\JsonFile;
 use Composer\Factory;
 use Composer\Package\BasePackage;
+use Composer\Package\Version\VersionSelector;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Package\Version\VersionParser;
@@ -323,42 +324,8 @@ EOT
             foreach ($requires as $requirement) {
                 if (!isset($requirement['version'])) {
 
-                    $candidates = $this->getPool()->whatProvides($requirement['name'], null, true);
-
-                    if (!$candidates) {
-                        throw new \InvalidArgumentException(sprintf(
-                            'Could not find any versions for package "%s". Perhaps the name is wrong?',
-                            $requirement['name']
-                        ));
-                    }
-
-                    // select highest version if we have many
-                    // logic is repeated in CreateProjectCommand
-                    $package = reset($candidates);
-                    foreach ($candidates as $candidate) {
-                        if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
-                            $package = $candidate;
-                        }
-                    }
-
-                    if (!$package) {
-                        throw new \Exception(sprintf(
-                            'No version of the package "%s" could be found that meets your minimum stability requirements of "%s"',
-                            $requirement['name'],
-                            $this->getComposer()->getPackage()->getMinimumStability()
-                        ));
-                    }
-
-                    $version = $package->getPrettyVersion();
-                    if (!$package->isDev()) {
-                        // remove the v prefix if there is one
-                        if (substr($version, 0, 1) == 'v') {
-                            $version = substr($version, 1);
-                        }
-
-                        // 2.1.0 -> ~2.1.0, 2.0-beta.1 -> ~2.0-beta.1
-                        $version = '~'.$version;
-                    }
+                    // determine the best version automatically
+                    $version = $this->findBestVersionForPackage($requirement['name']);
                     $requirement['version'] = $version;
 
                     $output->writeln(sprintf(
@@ -420,7 +387,7 @@ EOT
                     $package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add, or the complete package name if it is not listed', false, ':'), $validator, 3);
                 }
 
-                // no constraint yet, prompt user
+                // no constraint yet, determine the best version automatically
                 if (false !== $package && false === strpos($package, ' ')) {
                     $validator = function ($input) {
                         $input = trim($input);
@@ -428,9 +395,20 @@ EOT
                         return $input ?: false;
                     };
 
-                    $constraint = $dialog->askAndValidate($output, $dialog->getQuestion('Enter the version constraint to require', false, ':'), $validator, 3);
+                    $constraint = $dialog->askAndValidate(
+                        $output,
+                        $dialog->getQuestion('Enter the version constraint to require (or leave blank to use the latest version)', false, ':'),
+                        $validator,
+                        3)
+                    ;
                     if (false === $constraint) {
-                        continue;
+                        $constraint = $this->findBestVersionForPackage($package);
+
+                        $output->writeln(sprintf(
+                            'Using version <info>%s</info> for <info>%s</info>',
+                            $constraint,
+                            $package
+                        ));
                     }
 
                     $package .= ' '.$constraint;
@@ -555,4 +533,41 @@ EOT
 
         return false !== filter_var($email, FILTER_VALIDATE_EMAIL);
     }
+
+    /**
+     * Given a package name, this determines the best version to use in the require key.
+     *
+     * This returns a version with the ~ operator prefixed when possible.
+     *
+     * @param string $name
+     * @return string
+     * @throws \InvalidArgumentException
+     */
+    protected function findBestVersionForPackage($name)
+    {
+        // find the latest version allowed in this pool
+        $versionSelector = new VersionSelector($this->getPool());
+        $package = $versionSelector->findBestCandidate($name);
+
+        if (!$package) {
+            throw new \InvalidArgumentException(sprintf(
+                'Could not find package %s at any version for your minimum-stability (%s). Check the package spelling or your minimum-stability',
+                $name,
+                $this->getComposer()->getPackage()->getMinimumStability()
+            ));
+        }
+
+        $version = $package->getPrettyVersion();
+        if (!$package->isDev()) {
+            // remove the v prefix if there is one
+            if (substr($version, 0, 1) == 'v') {
+                $version = substr($version, 1);
+            }
+
+            // 2.1.0 -> ~2.1.0, 2.0-beta.1 -> ~2.0-beta.1
+            $version = '~'.$version;
+        }
+
+        return $version;
+    }
 }

+ 71 - 0
src/Composer/Package/Version/VersionSelector.php

@@ -0,0 +1,71 @@
+<?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\Package\Version;
+
+use Composer\DependencyResolver\Pool;
+use Composer\Package\PackageInterface;
+
+/**
+ * Selects the best possible version for a package
+ *
+ * @author Ryan Weaver <ryan@knpuniversity.com>
+ */
+class VersionSelector
+{
+    private $pool;
+
+    private $parser;
+
+    public function __construct(Pool $pool)
+    {
+        $this->pool = $pool;
+    }
+
+    /**
+     * Given a package name and optional version, returns the latest PackageInterface
+     * that matches.
+     *
+     * @param string    $packageName
+     * @param string    $targetPackageVersion
+     * @return PackageInterface|bool
+     */
+    public function findBestCandidate($packageName, $targetPackageVersion = null)
+    {
+        $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null;
+        $candidates = $this->pool->whatProvides($packageName, $constraint, true);
+
+        if (!$candidates) {
+            return false;
+        }
+
+        // select highest version if we have many
+        // logic is repeated in InitCommand
+        $package = reset($candidates);
+        foreach ($candidates as $candidate) {
+            if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
+                $package = $candidate;
+            }
+        }
+
+        return $package;
+    }
+
+    private function getParser()
+    {
+        if ($this->parser === null) {
+            $this->parser = new VersionParser();
+        }
+
+        return $this->parser;
+    }
+}

+ 71 - 0
tests/Composer/Test/Package/Version/VersionSelectorTest.php

@@ -0,0 +1,71 @@
+<?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\Package\Version;
+
+use Composer\Package\Version\VersionSelector;
+
+class VersionSelectorTest extends \PHPUnit_Framework_TestCase
+{
+    // A) multiple versions, get the latest one
+    // B) targetPackageVersion will pass to pool
+    // C) No results, throw exception
+
+    public function testLatestVersionIsReturned()
+    {
+        $packageName = 'foobar';
+
+        $package1 = $this->createMockPackage('1.2.1');
+        $package2 = $this->createMockPackage('1.2.2');
+        $package3 = $this->createMockPackage('1.2.0');
+        $packages = array($package1, $package2, $package3);
+
+        $pool = $this->createMockPool();
+        $pool->expects($this->once())
+            ->method('whatProvides')
+            ->with($packageName, null, true)
+            ->will($this->returnValue($packages));
+
+        $versionSelector = new VersionSelector($pool);
+        $best = $versionSelector->findBestCandidate($packageName);
+
+        // 1.2.2 should be returned because it's the latest of the returned versions
+        $this->assertEquals($package2, $best, 'Latest version should be 1.2.2');
+    }
+
+    public function testFalseReturnedOnNoPackages()
+    {
+        $pool = $this->createMockPool();
+        $pool->expects($this->once())
+            ->method('whatProvides')
+            ->will($this->returnValue(array()));
+
+        $versionSelector = new VersionSelector($pool);
+        $best = $versionSelector->findBestCandidate('foobaz');
+        $this->assertFalse($best, 'No versions are available returns false');
+    }
+
+    private function createMockPackage($version)
+    {
+        $package = $this->getMock('\Composer\Package\PackageInterface');
+        $package->expects($this->any())
+            ->method('getVersion')
+            ->will($this->returnValue($version));
+
+        return $package;
+    }
+
+    private function createMockPool()
+    {
+        return $this->getMock('Composer\DependencyResolver\Pool', array(), array(), '', true);
+    }
+}