VersionSelector.php 5.6 KB

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