Pool.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  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\DependencyResolver;
  12. use Composer\Package\AliasPackage;
  13. use Composer\Package\Version\VersionParser;
  14. use Composer\Semver\Constraint\ConstraintInterface;
  15. use Composer\Semver\Constraint\Constraint;
  16. use Composer\Semver\Constraint\EmptyConstraint;
  17. use Composer\Package\PackageInterface;
  18. /**
  19. * A package pool contains all packages for dependency resolution
  20. *
  21. * @author Nils Adermann <naderman@naderman.de>
  22. * @author Jordi Boggiano <j.boggiano@seld.be>
  23. */
  24. class Pool implements \Countable
  25. {
  26. const MATCH_NAME = -1;
  27. const MATCH_NONE = 0;
  28. const MATCH = 1;
  29. const MATCH_PROVIDE = 2;
  30. const MATCH_REPLACE = 3;
  31. protected $packages = array();
  32. protected $packageByName = array();
  33. protected $packageByExactName = array();
  34. protected $versionParser;
  35. protected $providerCache = array();
  36. protected $unacceptableFixedPackages;
  37. public function __construct(array $packages = array(), array $unacceptableFixedPackages = array())
  38. {
  39. $this->versionParser = new VersionParser;
  40. $this->setPackages($packages);
  41. $this->unacceptableFixedPackages = $unacceptableFixedPackages;
  42. }
  43. private function setPackages(array $packages)
  44. {
  45. $id = 1;
  46. foreach ($packages as $package) {
  47. $this->packages[] = $package;
  48. $package->id = $id++;
  49. $this->packageByExactName[$package->getName()][$package->id] = $package;
  50. foreach ($package->getNames() as $provided) {
  51. $this->packageByName[$provided][] = $package;
  52. }
  53. }
  54. }
  55. /**
  56. * Retrieves the package object for a given package id.
  57. *
  58. * @param int $id
  59. * @return PackageInterface
  60. */
  61. public function packageById($id)
  62. {
  63. return $this->packages[$id - 1];
  64. }
  65. /**
  66. * Returns how many packages have been loaded into the pool
  67. */
  68. public function count()
  69. {
  70. return count($this->packages);
  71. }
  72. /**
  73. * Searches all packages providing the given package name and match the constraint
  74. *
  75. * @param string $name The package name to be searched for
  76. * @param ConstraintInterface $constraint A constraint that all returned
  77. * packages must match or null to return all
  78. * @param bool $mustMatchName Whether the name of returned packages
  79. * must match the given name
  80. * @return PackageInterface[] A set of packages
  81. */
  82. public function whatProvides($name, ConstraintInterface $constraint = null, $mustMatchName = false)
  83. {
  84. $key = ((int) $mustMatchName).$constraint;
  85. if (isset($this->providerCache[$name][$key])) {
  86. return $this->providerCache[$name][$key];
  87. }
  88. return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName);
  89. }
  90. /**
  91. * @see whatProvides
  92. */
  93. private function computeWhatProvides($name, $constraint, $mustMatchName = false)
  94. {
  95. $candidates = array();
  96. if ($mustMatchName) {
  97. if (isset($this->packageByExactName[$name])) {
  98. $candidates = $this->packageByExactName[$name];
  99. }
  100. } elseif (isset($this->packageByName[$name])) {
  101. $candidates = $this->packageByName[$name];
  102. }
  103. $matches = $provideMatches = array();
  104. $nameMatch = false;
  105. foreach ($candidates as $candidate) {
  106. switch ($this->match($candidate, $name, $constraint)) {
  107. case self::MATCH_NONE:
  108. break;
  109. case self::MATCH_NAME:
  110. $nameMatch = true;
  111. break;
  112. case self::MATCH:
  113. $nameMatch = true;
  114. $matches[] = $candidate;
  115. break;
  116. case self::MATCH_PROVIDE:
  117. $provideMatches[] = $candidate;
  118. break;
  119. case self::MATCH_REPLACE:
  120. $matches[] = $candidate;
  121. break;
  122. default:
  123. throw new \UnexpectedValueException('Unexpected match type');
  124. }
  125. }
  126. // if a package with the required name exists, we ignore providers
  127. if ($nameMatch) {
  128. return $matches;
  129. }
  130. return array_merge($matches, $provideMatches);
  131. }
  132. public function literalToPackage($literal)
  133. {
  134. $packageId = abs($literal);
  135. return $this->packageById($packageId);
  136. }
  137. public function literalToPrettyString($literal, $installedMap)
  138. {
  139. $package = $this->literalToPackage($literal);
  140. if (isset($installedMap[$package->id])) {
  141. $prefix = ($literal > 0 ? 'keep' : 'remove');
  142. } else {
  143. $prefix = ($literal > 0 ? 'install' : 'don\'t install');
  144. }
  145. return $prefix.' '.$package->getPrettyString();
  146. }
  147. /**
  148. * Checks if the package matches the given constraint directly or through
  149. * provided or replaced packages
  150. *
  151. * @param PackageInterface $candidate
  152. * @param string $name Name of the package to be matched
  153. * @param ConstraintInterface $constraint The constraint to verify
  154. * @return int One of the MATCH* constants of this class or 0 if there is no match
  155. */
  156. public function match($candidate, $name, ConstraintInterface $constraint = null)
  157. {
  158. $candidateName = $candidate->getName();
  159. $candidateVersion = $candidate->getVersion();
  160. if ($candidateName === $name) {
  161. $pkgConstraint = new Constraint('==', $candidateVersion);
  162. if ($constraint === null || $constraint->matches($pkgConstraint)) {
  163. return self::MATCH;
  164. }
  165. return self::MATCH_NAME;
  166. }
  167. $provides = $candidate->getProvides();
  168. $replaces = $candidate->getReplaces();
  169. // aliases create multiple replaces/provides for one target so they can not use the shortcut below
  170. if (isset($replaces[0]) || isset($provides[0])) {
  171. foreach ($provides as $link) {
  172. if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
  173. return self::MATCH_PROVIDE;
  174. }
  175. }
  176. foreach ($replaces as $link) {
  177. if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
  178. return self::MATCH_REPLACE;
  179. }
  180. }
  181. return self::MATCH_NONE;
  182. }
  183. if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) {
  184. return self::MATCH_PROVIDE;
  185. }
  186. if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) {
  187. return self::MATCH_REPLACE;
  188. }
  189. return self::MATCH_NONE;
  190. }
  191. public function isUnacceptableFixedPackage(PackageInterface $package)
  192. {
  193. return in_array($package, $this->unacceptableFixedPackages, true);
  194. }
  195. }