Pool.php 8.9 KB

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