VersionSelector.php 4.3 KB

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