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