diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 28ae38c55..817bcf4cd 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -276,7 +276,6 @@ EOT $pool = new Pool($stability); $pool->addRepository($sourceRepo); - $pool->loadRecursively(array($name)); // find the latest version if there are multiple $versionSelector = new VersionSelector($pool); diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 222c414cb..000d63805 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -99,9 +99,40 @@ class Pool if ($repo instanceof ComposerRepository && $repo->hasProviders()) { $this->providerRepos[] = $repo; $repo->setRootAliases($rootAliases); + $repo->resetPackageIds(); } else { foreach ($repo->getPackages() as $package) { - $this->loadPackage($package, $rootAliases, $exempt); + $names = $package->getNames(); + $stability = $package->getStability(); + if ($exempt || $this->isPackageAcceptable($names, $stability)) { + $package->setId($this->id++); + $this->packages[] = $package; + $this->packageByExactName[$package->getName()][$package->id] = $package; + + foreach ($names as $provided) { + $this->packageByName[$provided][] = $package; + } + + // handle root package aliases + $name = $package->getName(); + if (isset($rootAliases[$name][$package->getVersion()])) { + $alias = $rootAliases[$name][$package->getVersion()]; + if ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); + $aliasPackage->setRootPackageAlias(true); + $aliasPackage->setId($this->id++); + + $package->getRepository()->addPackage($aliasPackage); + $this->packages[] = $aliasPackage; + $this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage; + + foreach ($aliasPackage->getNames() as $name) { + $this->packageByName[$name][] = $aliasPackage; + } + } + } } } } @@ -129,38 +160,6 @@ class Pool return $this->packages[$id - 1]; } - /** - * Ensures that all given names and their requirements are loaded. - * - * @param array $packageNames A list of names that need to be available - */ - public function loadRecursively(array $packageNames) - { - $loadedMap = array(); - do { - $newPackageNames = array(); - $loadedCount = count($loadedMap); - - foreach ($this->providerRepos as $repo) { - $packages = $repo->loadRecursively( - $packageNames, - array($this, 'isPackageAcceptable') - ); - - foreach ($packages as $package) { - $name = $package->getName(); - if (!isset($loadedMap[$name])) { - $loadedMap[$name] = true; - $newPackageNames[] = $name; - } - $this->loadPackage($package, $repo->getRootAliases()); - } - } - - $packageNames = $newPackageNames; - } while (count($loadedMap) > $loadedCount); - } - /** * Searches all packages providing the given package name and match the constraint * @@ -181,44 +180,6 @@ class Pool return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName); } - private function loadPackage(PackageInterface $package, array $rootAliases, $acceptableExemption = false) - { - $names = $package->getNames(); - $stability = $package->getStability(); - - if (!$acceptableExemption && !$this->isPackageAcceptable($names, $stability)) { - return; - } - - $package->setId($this->id++); - $this->packages[] = $package; - $this->packageByExactName[$package->getName()][$package->id] = $package; - - foreach ($names as $provided) { - $this->packageByName[$provided][] = $package; - } - - // handle root package aliases - $name = $package->getName(); - if (isset($rootAliases[$name][$package->getVersion()])) { - $alias = $rootAliases[$name][$package->getVersion()]; - if ($package instanceof AliasPackage) { - $package = $package->getAliasOf(); - } - $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); - $aliasPackage->setRootPackageAlias(true); - $aliasPackage->setId($this->id++); - - $package->getRepository()->addPackage($aliasPackage); - $this->packages[] = $aliasPackage; - $this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage; - - foreach ($aliasPackage->getNames() as $name) { - $this->packageByName[$name][] = $aliasPackage; - } - } - } - /** * @see whatProvides */ @@ -226,12 +187,25 @@ class Pool { $candidates = array(); + foreach ($this->providerRepos as $repo) { + foreach ($repo->whatProvides($this, $name) as $candidate) { + $candidates[] = $candidate; + if ($candidate->id < 1) { + $candidate->setId($this->id++); + $this->packages[$this->id - 2] = $candidate; + } + } + } + if ($mustMatchName) { + $candidates = array_filter($candidates, function ($candidate) use ($name) { + return $candidate->getName() == $name; + }); if (isset($this->packageByExactName[$name])) { - $candidates = $this->packageByExactName[$name]; + $candidates = array_merge($candidates, $this->packageByExactName[$name]); } } elseif (isset($this->packageByName[$name])) { - $candidates = $this->packageByName[$name]; + $candidates = array_merge($candidates, $this->packageByName[$name]); } $matches = $provideMatches = array(); diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index bcfd08388..6975df2cd 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -169,18 +169,6 @@ class Solver $this->jobs = $request->getJobs(); $this->setupInstalledMap(); - - $packageNames = array(); - foreach ($this->jobs as $job) { - switch ($job['cmd']) { - case 'install': - $packageNames[$job['packageName']] = true; - break; - } - } - - $this->pool->loadRecursively(array_keys($packageNames)); - $this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs); $this->checkForRootRequireProblems($ignorePlatformReqs); $this->decisions = new Decisions($this->pool); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 619dc65c8..47b215c74 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -43,9 +43,10 @@ class ComposerRepository extends ArrayRepository protected $searchUrl; protected $hasProviders = false; protected $providersUrl; - protected $loadedMap = array(); protected $lazyProvidersUrl; protected $providerListing; + protected $providers = array(); + protected $providersByUid = array(); protected $loader; protected $rootAliases; protected $allowSslDowngrade = false; @@ -95,51 +96,6 @@ class ComposerRepository extends ArrayRepository $this->rootAliases = $rootAliases; } - public function getRootAliases() - { - return $this->rootAliases; - } - - /** - * Load all packages with given names and dependencies - * - * @param array $packageNames - * @param callable|null $acceptableCallback Callback to filter packages - * - * @return array The loaded package objects - */ - public function loadRecursively(array $packageNames, $acceptableCallback) - { - $workQueue = new \SplQueue; - - foreach ($packageNames as $packageName) { - $workQueue->enqueue($packageName); - } - - $loadedPackages = array(); - - while (!$workQueue->isEmpty()) { - $packageName = $workQueue->dequeue(); - if (isset($this->loadedMap[$packageName])) { - continue; - } - - $this->loadedMap[$packageName] = true; - - $packages = $this->loadName($packageName, $acceptableCallback); - - foreach ($packages as $package) { - $loadedPackages[] = $package; - $requires = $package->getRequires(); - foreach ($requires as $link) { - $workQueue->enqueue($link->getTarget()); - } - } - } - - return $loadedPackages; - } - /** * {@inheritDoc} */ @@ -155,7 +111,7 @@ class ComposerRepository extends ArrayRepository foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { - $packages = $this->loadName($providerName, null, false); + $packages = $this->whatProvides(new Pool('dev'), $providerName); foreach ($packages as $package) { if ($name == $package->getName() && $version === $package->getVersion()) { return $package; @@ -186,7 +142,7 @@ class ComposerRepository extends ArrayRepository foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { - $packages = $this->loadName($providerName, null, false); + $packages = $this->whatProvides(new Pool('dev'), $providerName); foreach ($packages as $package) { if ($name == $package->getName() && (null === $version || $version === $package->getVersion())) { $packages[] = $package; @@ -284,17 +240,22 @@ class ComposerRepository extends ArrayRepository return $this->hasProviders; } - /** - * Loads package data for a given package name or provider name - * - * @param string $name - * @param callable|null $acceptableCallback A callback to check if a package should be loaded - * @param bool $exactMatch Whether packages only providing the name should be ignored - * - * @return array All packages that were loaded - */ - protected function loadName($name, $acceptableCallback, $exactMatch = true) + public function resetPackageIds() { + foreach ($this->providersByUid as $package) { + if ($package instanceof AliasPackage) { + $package->getAliasOf()->setId(-1); + } + $package->setId(-1); + } + } + + public function whatProvides(Pool $pool, $name) + { + if (isset($this->providers[$name])) { + return $this->providers[$name]; + } + // skip platform packages if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name) { return array(); @@ -335,32 +296,69 @@ class ComposerRepository extends ArrayRepository $packages = $this->fetchFile($url, $cacheKey, $hash); } - $loadedPackages = array(); - foreach ($packages['packages'] as $packageName => $versions) { - if ($exactMatch && $packageName !== $name) { - continue; - } - + $this->providers[$name] = array(); + foreach ($packages['packages'] as $versions) { foreach ($versions as $version) { - if ($acceptableCallback && !call_user_func( - $acceptableCallback, strtolower($version['name']), VersionParser::parseStability($version['version']) - )) { - continue; - } + // avoid loading the same objects twice + if (isset($this->providersByUid[$version['uid']])) { + // skip if already assigned + if (!isset($this->providers[$name][$version['uid']])) { + // expand alias in two packages + if ($this->providersByUid[$version['uid']] instanceof AliasPackage) { + $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']]->getAliasOf(); + $this->providers[$name][$version['uid'].'-alias'] = $this->providersByUid[$version['uid']]; + } else { + $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']]; + } + // check for root aliases + if (isset($this->providersByUid[$version['uid'].'-root'])) { + $this->providers[$name][$version['uid'].'-root'] = $this->providersByUid[$version['uid'].'-root']; + } + } + } else { + if (!$pool->isPackageAcceptable(strtolower($version['name']), VersionParser::parseStability($version['version']))) { + continue; + } - // load acceptable packages in the providers - $package = $this->createPackage($version, 'Composer\Package\Package'); - $this->addPackage($package); + // load acceptable packages in the providers + $package = $this->createPackage($version, 'Composer\Package\Package'); + $package->setRepository($this); - $loadedPackages[] = $package; + if ($package instanceof AliasPackage) { + $aliased = $package->getAliasOf(); + $aliased->setRepository($this); - if ($package instanceof AliasPackage) { - $loadedPackages[] = $package->getAliasOf(); + $this->providers[$name][$version['uid']] = $aliased; + $this->providers[$name][$version['uid'].'-alias'] = $package; + + // override provider with its alias so it can be expanded in the if block above + $this->providersByUid[$version['uid']] = $package; + } else { + $this->providers[$name][$version['uid']] = $package; + $this->providersByUid[$version['uid']] = $package; + } + + // handle root package aliases + unset($rootAliasData); + + if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) { + $rootAliasData = $this->rootAliases[$package->getName()][$package->getVersion()]; + } elseif ($package instanceof AliasPackage && isset($this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()])) { + $rootAliasData = $this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()]; + } + + if (isset($rootAliasData)) { + $alias = $this->createAliasPackage($package, $rootAliasData['alias_normalized'], $rootAliasData['alias']); + $alias->setRepository($this); + + $this->providers[$name][$version['uid'].'-root'] = $alias; + $this->providersByUid[$version['uid'].'-root'] = $alias; + } } } } - return $loadedPackages; + return $this->providers[$name]; } /** diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 02d546068..5109ee41f 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -96,7 +96,7 @@ class ComposerRepositoryTest extends TestCase ); } - public function testLoadRecursively() + public function testWhatProvides() { $repo = $this->getMockBuilder('Composer\Repository\ComposerRepository') ->disableOriginalConstructor() @@ -124,41 +124,45 @@ class ComposerRepositoryTest extends TestCase ->method('fetchFile') ->will($this->returnValue(array( 'packages' => array( - 'a' => array( - 'dev-master' => array( - 'uid' => 1, - 'name' => 'a', - 'version' => 'dev-master', - 'extra' => array('branch-alias' => array('dev-master' => '1.0.x-dev')), - ), - 'dev-develop' => array( - 'uid' => 2, - 'name' => 'a', - 'version' => 'dev-develop', - 'extra' => array('branch-alias' => array('dev-develop' => '1.1.x-dev')), - ), - '0.6' => array( - 'uid' => 3, - 'name' => 'a', - 'version' => '0.6', - ), - ), + array(array( + 'uid' => 1, + 'name' => 'a', + 'version' => 'dev-master', + 'extra' => array('branch-alias' => array('dev-master' => '1.0.x-dev')), + )), + array(array( + 'uid' => 2, + 'name' => 'a', + 'version' => 'dev-develop', + 'extra' => array('branch-alias' => array('dev-develop' => '1.1.x-dev')), + )), + array(array( + 'uid' => 3, + 'name' => 'a', + 'version' => '0.6', + )), ) ))); + $pool = $this->getMock('Composer\DependencyResolver\Pool'); + $pool->expects($this->any()) + ->method('isPackageAcceptable') + ->will($this->returnValue(true)); + $versionParser = new VersionParser(); + $repo->setRootAliases(array( + 'a' => array( + $versionParser->normalize('0.6') => array('alias' => 'dev-feature', 'alias_normalized' => $versionParser->normalize('dev-feature')), + $versionParser->normalize('1.1.x-dev') => array('alias' => '1.0', 'alias_normalized' => $versionParser->normalize('1.0')), + ), + )); - $that = $this; - $packages = $repo->loadRecursively(array('a'), function ($name, $stability) use ($that) { - $that->assertEquals('a', $name); - return true; - }); + $packages = $repo->whatProvides($pool, 'a'); - $this->assertCount(5, $packages); - $this->assertEquals(array('1.0.x-dev', 'dev-master', '1.1.x-dev', 'dev-develop', '0.6'), array_map(function ($p) { - return $p->getPrettyVersion(); - }, $packages)); - $this->assertInstanceOf('Composer\Package\AliasPackage', $packages[2]); - $this->assertSame($packages[3], $packages[2]->getAliasOf()); + $this->assertCount(7, $packages); + $this->assertEquals(array('1', '1-alias', '2', '2-alias', '2-root', '3', '3-root'), array_keys($packages)); + $this->assertInstanceOf('Composer\Package\AliasPackage', $packages['2-root']); + $this->assertSame($packages['2'], $packages['2-root']->getAliasOf()); + $this->assertSame($packages['2'], $packages['2-alias']->getAliasOf()); } }