BaseRepository.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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\AliasPackage;
  13. use Composer\Package\RootPackageInterface;
  14. use Composer\Semver\Constraint\ConstraintInterface;
  15. use Composer\Semver\Constraint\Constraint;
  16. use Composer\Package\Link;
  17. /**
  18. * Common ancestor class for generic repository functionality.
  19. *
  20. * @author Niels Keurentjes <niels.keurentjes@omines.com>
  21. */
  22. abstract class BaseRepository implements RepositoryInterface
  23. {
  24. /**
  25. * Returns a list of links causing the requested needle packages to be installed, as an associative array with the
  26. * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship
  27. * as values. If recursive lookup was requested a third value is returned containing an identically formed array up
  28. * to the root package. That third value will be false in case a circular recursion was detected.
  29. *
  30. * @param string|string[] $needle The package name(s) to inspect.
  31. * @param ConstraintInterface|null $constraint Optional constraint to filter by.
  32. * @param bool $invert Whether to invert matches to discover reasons for the package *NOT* to be installed.
  33. * @param bool $recurse Whether to recursively expand the requirement tree up to the root package.
  34. * @param string[] $packagesFound Used internally when recurring
  35. * @return array An associative array of arrays as described above.
  36. */
  37. public function getDependents($needle, $constraint = null, $invert = false, $recurse = true, $packagesFound = null)
  38. {
  39. $needles = array_map('strtolower', (array) $needle);
  40. $results = array();
  41. // initialize the array with the needles before any recursion occurs
  42. if (null === $packagesFound) {
  43. $packagesFound = $needles;
  44. }
  45. // locate root package for use below
  46. $rootPackage = null;
  47. foreach ($this->getPackages() as $package) {
  48. if ($package instanceof RootPackageInterface) {
  49. $rootPackage = $package;
  50. break;
  51. }
  52. }
  53. // Loop over all currently installed packages.
  54. foreach ($this->getPackages() as $package) {
  55. $links = $package->getRequires();
  56. // each loop needs its own "tree" as we want to show the complete dependent set of every needle
  57. // without warning all the time about finding circular deps
  58. $packagesInTree = $packagesFound;
  59. // Replacements are considered valid reasons for a package to be installed during forward resolution
  60. if (!$invert) {
  61. $links += $package->getReplaces();
  62. // On forward search, check if any replaced package was required and add the replaced
  63. // packages to the list of needles. Contrary to the cross-reference link check below,
  64. // replaced packages are the target of links.
  65. foreach ($package->getReplaces() as $link) {
  66. foreach ($needles as $needle) {
  67. if ($link->getSource() === $needle) {
  68. if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
  69. // already displayed this node's dependencies, cutting short
  70. if (in_array($link->getTarget(), $packagesInTree)) {
  71. $results[] = array($package, $link, false);
  72. continue;
  73. }
  74. $packagesInTree[] = $link->getTarget();
  75. $dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $packagesInTree) : array();
  76. $results[] = array($package, $link, $dependents);
  77. $needles[] = $link->getTarget();
  78. }
  79. }
  80. }
  81. }
  82. }
  83. // Require-dev is only relevant for the root package
  84. if ($package instanceof RootPackageInterface) {
  85. $links += $package->getDevRequires();
  86. }
  87. // Cross-reference all discovered links to the needles
  88. foreach ($links as $link) {
  89. foreach ($needles as $needle) {
  90. if ($link->getTarget() === $needle) {
  91. if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
  92. // already displayed this node's dependencies, cutting short
  93. if (in_array($link->getSource(), $packagesInTree)) {
  94. $results[] = array($package, $link, false);
  95. continue;
  96. }
  97. $packagesInTree[] = $link->getSource();
  98. $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : array();
  99. $results[] = array($package, $link, $dependents);
  100. }
  101. }
  102. }
  103. }
  104. // When inverting, we need to check for conflicts of the needles against installed packages
  105. if ($invert && in_array($package->getName(), $needles)) {
  106. foreach ($package->getConflicts() as $link) {
  107. foreach ($this->findPackages($link->getTarget()) as $pkg) {
  108. $version = new Constraint('=', $pkg->getVersion());
  109. if ($link->getConstraint()->matches($version) === $invert) {
  110. $results[] = array($package, $link, false);
  111. }
  112. }
  113. }
  114. }
  115. // When inverting, we need to check for conflicts of the needles' requirements against installed packages
  116. if ($invert && $constraint && in_array($package->getName(), $needles) && $constraint->matches(new Constraint('=', $package->getVersion()))) {
  117. foreach ($package->getRequires() as $link) {
  118. if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
  119. if ($this->findPackage($link->getTarget(), $link->getConstraint())) {
  120. continue;
  121. }
  122. $platformPkg = $this->findPackage($link->getTarget(), '*');
  123. $description = $platformPkg ? 'but '.$platformPkg->getPrettyVersion().' is installed' : 'but it is missing';
  124. $results[] = array($package, new Link($package->getName(), $link->getTarget(), null, 'requires', $link->getPrettyConstraint().' '.$description), false);
  125. continue;
  126. }
  127. foreach ($this->getPackages() as $pkg) {
  128. if (!in_array($link->getTarget(), $pkg->getNames())) {
  129. continue;
  130. }
  131. $version = new Constraint('=', $pkg->getVersion());
  132. if (!$link->getConstraint()->matches($version)) {
  133. // if we have a root package (we should but can not guarantee..) we show
  134. // the root requires as well to perhaps allow to find an issue there
  135. if ($rootPackage) {
  136. foreach (array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()) as $rootReq) {
  137. if (in_array($rootReq->getTarget(), $pkg->getNames()) && !$rootReq->getConstraint()->matches($link->getConstraint())) {
  138. $results[] = array($package, $link, false);
  139. $results[] = array($rootPackage, $rootReq, false);
  140. continue 3;
  141. }
  142. }
  143. $results[] = array($package, $link, false);
  144. $results[] = array($rootPackage, new Link($rootPackage->getName(), $link->getTarget(), null, 'does not require', 'but ' . $pkg->getPrettyVersion() . ' is installed'), false);
  145. } else {
  146. // no root so let's just print whatever we found
  147. $results[] = array($package, $link, false);
  148. }
  149. }
  150. continue 2;
  151. }
  152. }
  153. }
  154. }
  155. ksort($results);
  156. return $results;
  157. }
  158. }