VersionSelector.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Package\Version;
  12. use Composer\DependencyResolver\Pool;
  13. use Composer\Package\BasePackage;
  14. use Composer\Package\PackageInterface;
  15. use Composer\Package\Loader\ArrayLoader;
  16. use Composer\Package\Dumper\ArrayDumper;
  17. use Composer\Repository\RepositorySet;
  18. use Composer\Semver\Constraint\Constraint;
  19. /**
  20. * Selects the best possible version for a package
  21. *
  22. * @author Ryan Weaver <ryan@knpuniversity.com>
  23. * @author Jordi Boggiano <j.boggiano@seld.be>
  24. */
  25. class VersionSelector
  26. {
  27. private $repositorySet;
  28. private $parser;
  29. public function __construct(RepositorySet $repositorySet)
  30. {
  31. $this->repositorySet = $repositorySet;
  32. }
  33. /**
  34. * Given a package name and optional version, returns the latest PackageInterface
  35. * that matches.
  36. *
  37. * @param string $packageName
  38. * @param string $targetPackageVersion
  39. * @param string $targetPhpVersion
  40. * @param string $preferredStability
  41. * @return PackageInterface|false
  42. */
  43. public function findBestCandidate($packageName, $targetPackageVersion = null, $targetPhpVersion = null, $preferredStability = 'stable')
  44. {
  45. $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null;
  46. $candidates = $this->repositorySet->findPackages(strtolower($packageName), $constraint);
  47. if ($targetPhpVersion) {
  48. $phpConstraint = new Constraint('==', $this->getParser()->normalize($targetPhpVersion));
  49. $candidates = array_filter($candidates, function ($pkg) use ($phpConstraint) {
  50. $reqs = $pkg->getRequires();
  51. return !isset($reqs['php']) || $reqs['php']->getConstraint()->matches($phpConstraint);
  52. });
  53. }
  54. if (!$candidates) {
  55. return false;
  56. }
  57. // select highest version if we have many
  58. $package = reset($candidates);
  59. $minPriority = BasePackage::$stabilities[$preferredStability];
  60. foreach ($candidates as $candidate) {
  61. $candidatePriority = $candidate->getStabilityPriority();
  62. $currentPriority = $package->getStabilityPriority();
  63. // candidate is less stable than our preferred stability,
  64. // and current package is more stable than candidate, skip it
  65. if ($minPriority < $candidatePriority && $currentPriority < $candidatePriority) {
  66. continue;
  67. }
  68. // candidate is less stable than our preferred stability,
  69. // and current package is less stable than candidate, select candidate
  70. if ($minPriority < $candidatePriority && $candidatePriority < $currentPriority) {
  71. $package = $candidate;
  72. continue;
  73. }
  74. // candidate is more stable than our preferred stability,
  75. // and current package is less stable than preferred stability, select candidate
  76. if ($minPriority >= $candidatePriority && $minPriority < $currentPriority) {
  77. $package = $candidate;
  78. continue;
  79. }
  80. // select highest version of the two
  81. if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
  82. $package = $candidate;
  83. }
  84. }
  85. return $package;
  86. }
  87. /**
  88. * Given a concrete version, this returns a ~ constraint (when possible)
  89. * that should be used, for example, in composer.json.
  90. *
  91. * For example:
  92. * * 1.2.1 -> ^1.2
  93. * * 1.2 -> ^1.2
  94. * * v3.2.1 -> ^3.2
  95. * * 2.0-beta.1 -> ^2.0@beta
  96. * * dev-master -> ^2.1@dev (dev version with alias)
  97. * * dev-master -> dev-master (dev versions are untouched)
  98. *
  99. * @param PackageInterface $package
  100. * @return string
  101. */
  102. public function findRecommendedRequireVersion(PackageInterface $package)
  103. {
  104. $version = $package->getVersion();
  105. if (!$package->isDev()) {
  106. return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability());
  107. }
  108. $loader = new ArrayLoader($this->getParser());
  109. $dumper = new ArrayDumper();
  110. $extra = $loader->getBranchAlias($dumper->dump($package));
  111. if ($extra) {
  112. $extra = preg_replace('{^(\d+\.\d+\.\d+)(\.9999999)-dev$}', '$1.0', $extra, -1, $count);
  113. if ($count) {
  114. $extra = str_replace('.9999999', '.0', $extra);
  115. return $this->transformVersion($extra, $extra, 'dev');
  116. }
  117. }
  118. return $package->getPrettyVersion();
  119. }
  120. private function transformVersion($version, $prettyVersion, $stability)
  121. {
  122. // attempt to transform 2.1.1 to 2.1
  123. // this allows you to upgrade through minor versions
  124. $semanticVersionParts = explode('.', $version);
  125. // check to see if we have a semver-looking version
  126. if (count($semanticVersionParts) == 4 && preg_match('{^0\D?}', $semanticVersionParts[3])) {
  127. // remove the last parts (i.e. the patch version number and any extra)
  128. if ($semanticVersionParts[0] === '0') {
  129. unset($semanticVersionParts[3]);
  130. } else {
  131. unset($semanticVersionParts[2], $semanticVersionParts[3]);
  132. }
  133. $version = implode('.', $semanticVersionParts);
  134. } else {
  135. return $prettyVersion;
  136. }
  137. // append stability flag if not default
  138. if ($stability != 'stable') {
  139. $version .= '@'.$stability;
  140. }
  141. // 2.1 -> ^2.1
  142. return '^' . $version;
  143. }
  144. private function getParser()
  145. {
  146. if ($this->parser === null) {
  147. $this->parser = new VersionParser();
  148. }
  149. return $this->parser;
  150. }
  151. }