diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php
index e8201cb2b..9928692eb 100644
--- a/src/Composer/Command/CreateProjectCommand.php
+++ b/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);
diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php
index f0f41a006..b0ce567a8 100644
--- a/src/Composer/Command/InitCommand.php
+++ b/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 %s for %s',
+ $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;
+ }
}
diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php
new file mode 100644
index 000000000..ad9c2ab71
--- /dev/null
+++ b/src/Composer/Package/Version/VersionSelector.php
@@ -0,0 +1,71 @@
+
+ * Jordi Boggiano
+ *
+ * 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
+ */
+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;
+ }
+}
diff --git a/tests/Composer/Test/Package/Version/VersionSelectorTest.php b/tests/Composer/Test/Package/Version/VersionSelectorTest.php
new file mode 100644
index 000000000..636645807
--- /dev/null
+++ b/tests/Composer/Test/Package/Version/VersionSelectorTest.php
@@ -0,0 +1,71 @@
+
+ * Jordi Boggiano
+ *
+ * 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);
+ }
+}