InstalledRepository.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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\Repository;
  12. use Composer\Package\Version\VersionParser;
  13. use Composer\Semver\Constraint\ConstraintInterface;
  14. use Composer\Semver\Constraint\Constraint;
  15. use Composer\Package\AliasPackage;
  16. use Composer\Package\RootPackageInterface;
  17. use Composer\Package\Link;
  18. /**
  19. * Installed repository is a composite of all installed repo types.
  20. *
  21. * The main use case is tagging a repo as an "installed" repository, and offering a way to get providers/replacers easily.
  22. *
  23. * Installed repos are LockArrayRepository, InstalledRepositoryInterface, RootPackageRepository and PlatformRepository
  24. *
  25. * @author Jordi Boggiano <j.boggiano@seld.be>
  26. */
  27. class InstalledRepository extends CompositeRepository
  28. {
  29. public function findPackagesWithReplacersAndProviders($name, $constraint = null)
  30. {
  31. $name = strtolower($name);
  32. if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
  33. $versionParser = new VersionParser();
  34. $constraint = $versionParser->parseConstraints($constraint);
  35. }
  36. $matches = array();
  37. foreach ($this->getRepositories() as $repo) {
  38. foreach ($repo->getPackages() as $candidate) {
  39. if (in_array($name, $candidate->getNames(), true)) {
  40. if (null === $constraint) {
  41. $matches[] = $candidate;
  42. continue;
  43. }
  44. if ($name === $candidate->getName() && $constraint->matches(new Constraint('==', $candidate->getVersion()))) {
  45. $matches[] = $candidate;
  46. continue;
  47. }
  48. foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) {
  49. if ($name === $link->getTarget() && ($link->getConstraint() === null || $constraint->matches($link->getConstraint()))) {
  50. $matches[] = $candidate;
  51. continue;
  52. }
  53. }
  54. }
  55. }
  56. }
  57. return $matches;
  58. }
  59. /**
  60. * Returns a list of links causing the requested needle packages to be installed, as an associative array with the
  61. * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship
  62. * as values. If recursive lookup was requested a third value is returned containing an identically formed array up
  63. * to the root package. That third value will be false in case a circular recursion was detected.
  64. *
  65. * @param string|string[] $needle The package name(s) to inspect.
  66. * @param ConstraintInterface|null $constraint Optional constraint to filter by.
  67. * @param bool $invert Whether to invert matches to discover reasons for the package *NOT* to be installed.
  68. * @param bool $recurse Whether to recursively expand the requirement tree up to the root package.
  69. * @param string[] $packagesFound Used internally when recurring
  70. * @return array An associative array of arrays as described above.
  71. */
  72. public function getDependents($needle, $constraint = null, $invert = false, $recurse = true, $packagesFound = null)
  73. {
  74. $needles = array_map('strtolower', (array) $needle);
  75. $results = array();
  76. // initialize the array with the needles before any recursion occurs
  77. if (null === $packagesFound) {
  78. $packagesFound = $needles;
  79. }
  80. // locate root package for use below
  81. $rootPackage = null;
  82. foreach ($this->getPackages() as $package) {
  83. if ($package instanceof RootPackageInterface) {
  84. $rootPackage = $package;
  85. break;
  86. }
  87. }
  88. // Loop over all currently installed packages.
  89. foreach ($this->getPackages() as $package) {
  90. $links = $package->getRequires();
  91. // each loop needs its own "tree" as we want to show the complete dependent set of every needle
  92. // without warning all the time about finding circular deps
  93. $packagesInTree = $packagesFound;
  94. // Replacements are considered valid reasons for a package to be installed during forward resolution
  95. if (!$invert) {
  96. $links += $package->getReplaces();
  97. // On forward search, check if any replaced package was required and add the replaced
  98. // packages to the list of needles. Contrary to the cross-reference link check below,
  99. // replaced packages are the target of links.
  100. foreach ($package->getReplaces() as $link) {
  101. foreach ($needles as $needle) {
  102. if ($link->getSource() === $needle) {
  103. if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
  104. // already displayed this node's dependencies, cutting short
  105. if (in_array($link->getTarget(), $packagesInTree)) {
  106. $results[] = array($package, $link, false);
  107. continue;
  108. }
  109. $packagesInTree[] = $link->getTarget();
  110. $dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $packagesInTree) : array();
  111. $results[] = array($package, $link, $dependents);
  112. $needles[] = $link->getTarget();
  113. }
  114. }
  115. }
  116. }
  117. }
  118. // Require-dev is only relevant for the root package
  119. if ($package instanceof RootPackageInterface) {
  120. $links += $package->getDevRequires();
  121. }
  122. // Cross-reference all discovered links to the needles
  123. foreach ($links as $link) {
  124. foreach ($needles as $needle) {
  125. if ($link->getTarget() === $needle) {
  126. if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
  127. // already displayed this node's dependencies, cutting short
  128. if (in_array($link->getSource(), $packagesInTree)) {
  129. $results[] = array($package, $link, false);
  130. continue;
  131. }
  132. $packagesInTree[] = $link->getSource();
  133. $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : array();
  134. $results[] = array($package, $link, $dependents);
  135. }
  136. }
  137. }
  138. }
  139. // When inverting, we need to check for conflicts of the needles against installed packages
  140. if ($invert && in_array($package->getName(), $needles)) {
  141. foreach ($package->getConflicts() as $link) {
  142. foreach ($this->findPackages($link->getTarget()) as $pkg) {
  143. $version = new Constraint('=', $pkg->getVersion());
  144. if ($link->getConstraint()->matches($version) === $invert) {
  145. $results[] = array($package, $link, false);
  146. }
  147. }
  148. }
  149. }
  150. // When inverting, we need to check for conflicts of the needles' requirements against installed packages
  151. if ($invert && $constraint && in_array($package->getName(), $needles) && $constraint->matches(new Constraint('=', $package->getVersion()))) {
  152. foreach ($package->getRequires() as $link) {
  153. if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
  154. if ($this->findPackage($link->getTarget(), $link->getConstraint())) {
  155. continue;
  156. }
  157. $platformPkg = $this->findPackage($link->getTarget(), '*');
  158. $description = $platformPkg ? 'but '.$platformPkg->getPrettyVersion().' is installed' : 'but it is missing';
  159. $results[] = array($package, new Link($package->getName(), $link->getTarget(), null, 'requires', $link->getPrettyConstraint().' '.$description), false);
  160. continue;
  161. }
  162. foreach ($this->getPackages() as $pkg) {
  163. if (!in_array($link->getTarget(), $pkg->getNames())) {
  164. continue;
  165. }
  166. $version = new Constraint('=', $pkg->getVersion());
  167. if (!$link->getConstraint()->matches($version)) {
  168. // if we have a root package (we should but can not guarantee..) we show
  169. // the root requires as well to perhaps allow to find an issue there
  170. if ($rootPackage) {
  171. foreach (array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()) as $rootReq) {
  172. if (in_array($rootReq->getTarget(), $pkg->getNames()) && !$rootReq->getConstraint()->matches($link->getConstraint())) {
  173. $results[] = array($package, $link, false);
  174. $results[] = array($rootPackage, $rootReq, false);
  175. continue 3;
  176. }
  177. }
  178. $results[] = array($package, $link, false);
  179. $results[] = array($rootPackage, new Link($rootPackage->getName(), $link->getTarget(), null, 'does not require', 'but ' . $pkg->getPrettyVersion() . ' is installed'), false);
  180. } else {
  181. // no root so let's just print whatever we found
  182. $results[] = array($package, $link, false);
  183. }
  184. }
  185. continue 2;
  186. }
  187. }
  188. }
  189. }
  190. ksort($results);
  191. return $results;
  192. }
  193. public function getRepoName()
  194. {
  195. return 'installed repo ('.implode(', ', array_map(function ($repo) { return $repo->getRepoName(); }, $this->getRepositories())).')';
  196. }
  197. /**
  198. * Add a repository.
  199. * @param RepositoryInterface $repository
  200. */
  201. public function addRepository(RepositoryInterface $repository)
  202. {
  203. if (
  204. $repository instanceof LockArrayRepository
  205. || $repository instanceof InstalledRepositoryInterface
  206. || $repository instanceof RootPackageRepository
  207. || $repository instanceof PlatformRepository
  208. ) {
  209. return parent::addRepository($repository);
  210. }
  211. throw new \LogicException('An InstalledRepository can not contain a repository of type '.get_class($repository).' ('.$repository->getRepoName().')');
  212. }
  213. }