Refactor ComposerRepository to work with combined repos having lazy providers and partial packages
parent
14d6bcedda
commit
b47330adf1
|
@ -317,8 +317,8 @@ EOT
|
||||||
} else {
|
} else {
|
||||||
$type = 'available';
|
$type = 'available';
|
||||||
}
|
}
|
||||||
if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
|
if ($repo instanceof ComposerRepository) {
|
||||||
foreach ($repo->getProviderNames() as $name) {
|
foreach ($repo->getPackageNames() as $name) {
|
||||||
if (!$packageFilter || preg_match($packageFilter, $name)) {
|
if (!$packageFilter || preg_match($packageFilter, $name)) {
|
||||||
$packages[$type][$name] = $name;
|
$packages[$type][$name] = $name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,13 +35,13 @@ use Composer\Semver\Constraint\EmptyConstraint;
|
||||||
*/
|
*/
|
||||||
class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface
|
class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface
|
||||||
{
|
{
|
||||||
protected $config;
|
private $config;
|
||||||
protected $repoConfig;
|
private $repoConfig;
|
||||||
protected $options;
|
private $options;
|
||||||
protected $url;
|
private $url;
|
||||||
protected $baseUrl;
|
private $baseUrl;
|
||||||
protected $io;
|
private $io;
|
||||||
protected $httpDownloader;
|
private $httpDownloader;
|
||||||
protected $cache;
|
protected $cache;
|
||||||
protected $notifyUrl;
|
protected $notifyUrl;
|
||||||
protected $searchUrl;
|
protected $searchUrl;
|
||||||
|
@ -49,14 +49,14 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
protected $providersUrl;
|
protected $providersUrl;
|
||||||
protected $lazyProvidersUrl;
|
protected $lazyProvidersUrl;
|
||||||
protected $providerListing;
|
protected $providerListing;
|
||||||
protected $providers = array();
|
private $providers = array();
|
||||||
protected $providersByUid = array();
|
private $providersByUid = array();
|
||||||
protected $loader;
|
protected $loader;
|
||||||
protected $rootAliases;
|
private $rootAliases;
|
||||||
protected $allowSslDowngrade = false;
|
private $allowSslDowngrade = false;
|
||||||
protected $eventDispatcher;
|
private $eventDispatcher;
|
||||||
protected $sourceMirrors;
|
private $sourceMirrors;
|
||||||
protected $distMirrors;
|
private $distMirrors;
|
||||||
private $degradedMode = false;
|
private $degradedMode = false;
|
||||||
private $rootData;
|
private $rootData;
|
||||||
private $hasPartialPackages;
|
private $hasPartialPackages;
|
||||||
|
@ -134,28 +134,23 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
$constraint = $this->versionParser->parseConstraints($constraint);
|
$constraint = $this->versionParser->parseConstraints($constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO we need a new way for the repo to report this v2 protocol somehow
|
|
||||||
if ($this->lazyProvidersUrl) {
|
if ($this->lazyProvidersUrl) {
|
||||||
|
if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) {
|
||||||
|
return $this->filterPackages($this->whatProvides($name), $name, $constraint, true);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->loadAsyncPackages(array($name => $constraint), function ($name, $stability) {
|
return $this->loadAsyncPackages(array($name => $constraint), function ($name, $stability) {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$hasProviders) {
|
if (!$hasProviders) {
|
||||||
return parent::findPackage($name, $constraint);
|
return parent::findPackage($name, $constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->getProviderNames() as $providerName) {
|
foreach ($this->getProviderNames() as $providerName) {
|
||||||
if ($name === $providerName) {
|
if ($name === $providerName) {
|
||||||
$packages = $this->whatProvides($providerName);
|
return $this->filterPackages($this->whatProvides($providerName), $name, $constraint, true);
|
||||||
foreach ($packages as $package) {
|
|
||||||
if ($name === $package->getName()) {
|
|
||||||
$pkgConstraint = new Constraint('==', $package->getVersion());
|
|
||||||
if ($constraint->matches($pkgConstraint)) {
|
|
||||||
return $package;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,40 +163,58 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
// this call initializes loadRootServerFile which is needed for the rest below to work
|
// this call initializes loadRootServerFile which is needed for the rest below to work
|
||||||
$hasProviders = $this->hasProviders();
|
$hasProviders = $this->hasProviders();
|
||||||
|
|
||||||
// TODO we need a new way for the repo to report this v2 protocol somehow
|
|
||||||
if ($this->lazyProvidersUrl) {
|
|
||||||
return $this->loadAsyncPackages(array($name => $constraint ?: new EmptyConstraint()), function ($name, $stability) {
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!$hasProviders) {
|
|
||||||
return parent::findPackages($name, $constraint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalize name
|
|
||||||
$name = strtolower($name);
|
$name = strtolower($name);
|
||||||
|
|
||||||
if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
|
if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
|
||||||
$constraint = $this->versionParser->parseConstraints($constraint);
|
$constraint = $this->versionParser->parseConstraints($constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
$packages = array();
|
if ($this->lazyProvidersUrl) {
|
||||||
|
if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) {
|
||||||
|
return $this->filterPackages($this->whatProvides($name), $name, $constraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->loadAsyncPackages(array($name => $constraint ?: new EmptyConstraint()), function ($name, $stability) {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$hasProviders) {
|
||||||
|
return parent::findPackages($name, $constraint);
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->getProviderNames() as $providerName) {
|
foreach ($this->getProviderNames() as $providerName) {
|
||||||
if ($name === $providerName) {
|
if ($name === $providerName) {
|
||||||
$candidates = $this->whatProvides($providerName);
|
return $this->filterPackages($this->whatProvides($providerName), $name, $constraint);
|
||||||
foreach ($candidates as $package) {
|
|
||||||
if ($name === $package->getName()) {
|
|
||||||
$pkgConstraint = new Constraint('==', $package->getVersion());
|
|
||||||
if (null === $constraint || $constraint->matches($pkgConstraint)) {
|
|
||||||
$packages[] = $package;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function filterPackages(array $packages, $name, $constraint = null, $returnFirstMatch = false)
|
||||||
|
{
|
||||||
|
$packages = array();
|
||||||
|
|
||||||
|
foreach ($packages as $package) {
|
||||||
|
// TODO this check can be removed once providers are only what really has that name anyway
|
||||||
|
if ($name !== $package->getName()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pkgConstraint = new Constraint('==', $package->getVersion());
|
||||||
|
if (null === $constraint || $constraint->matches($pkgConstraint)) {
|
||||||
|
if ($returnFirstMatch) {
|
||||||
|
return $package;
|
||||||
|
}
|
||||||
|
|
||||||
|
$packages[] = $package;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($returnFirstMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return $packages;
|
return $packages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,39 +227,63 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
return parent::getPackages();
|
return parent::getPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPackageNames()
|
||||||
|
{
|
||||||
|
if ($this->hasProviders()) {
|
||||||
|
return $this->getProviderNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO implement new list API endpoint for repos somehow?
|
||||||
|
// TODO add getPackageNames to the RepositoryInterface perhaps? With filtering capability embedded?
|
||||||
|
|
||||||
|
return $this->getPackages();
|
||||||
|
}
|
||||||
|
|
||||||
public function loadPackages(array $packageNameMap, $isPackageAcceptableCallable)
|
public function loadPackages(array $packageNameMap, $isPackageAcceptableCallable)
|
||||||
{
|
{
|
||||||
// this call initializes loadRootServerFile which is needed for the rest below to work
|
// this call initializes loadRootServerFile which is needed for the rest below to work
|
||||||
$hasProviders = $this->hasProviders();
|
$hasProviders = $this->hasProviders();
|
||||||
|
|
||||||
// TODO we need a new way for the repo to report this v2 protocol somehow
|
if (!$hasProviders && !$this->hasPartialPackages() && !$this->lazyProvidersUrl) {
|
||||||
if ($this->lazyProvidersUrl) {
|
|
||||||
return $this->loadAsyncPackages($packageNameMap, $isPackageAcceptableCallable);
|
|
||||||
}
|
|
||||||
if (!$hasProviders) {
|
|
||||||
return parent::loadPackages($packageNameMap, $isPackageAcceptableCallable);
|
return parent::loadPackages($packageNameMap, $isPackageAcceptableCallable);
|
||||||
}
|
}
|
||||||
|
|
||||||
$packages = array();
|
$packages = array();
|
||||||
foreach ($packageNameMap as $name => $constraint) {
|
|
||||||
$matches = array();
|
if ($hasProviders || $this->hasPartialPackages()) {
|
||||||
$candidates = $this->whatProvides($name, false, $isPackageAcceptableCallable);
|
foreach ($packageNameMap as $name => $constraint) {
|
||||||
foreach ($candidates as $candidate) {
|
$matches = array();
|
||||||
if ($candidate->getName() === $name && (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion())))) {
|
|
||||||
$matches[spl_object_hash($candidate)] = $candidate;
|
if (!$hasProviders && !isset($this->partialPackagesByName[$name])) {
|
||||||
if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) {
|
continue;
|
||||||
$matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
foreach ($candidates as $candidate) {
|
$candidates = $this->whatProvides($name, $isPackageAcceptableCallable);
|
||||||
if ($candidate instanceof AliasPackage) {
|
foreach ($candidates as $candidate) {
|
||||||
if (isset($result[spl_object_hash($candidate->getAliasOf())])) {
|
if ($candidate->getName() === $name && (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion())))) {
|
||||||
$matches[spl_object_hash($candidate)] = $candidate;
|
$matches[spl_object_hash($candidate)] = $candidate;
|
||||||
|
if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) {
|
||||||
|
$matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
foreach ($candidates as $candidate) {
|
||||||
|
if ($candidate instanceof AliasPackage) {
|
||||||
|
if (isset($result[spl_object_hash($candidate->getAliasOf())])) {
|
||||||
|
$matches[spl_object_hash($candidate)] = $candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$packages = array_merge($packages, $matches);
|
||||||
|
|
||||||
|
unset($packageNameMap[$name]);
|
||||||
}
|
}
|
||||||
$packages = array_merge($packages, $matches);
|
|
||||||
|
return $packages;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lazyProvidersUrl) {
|
||||||
|
$packages = array_merge($packages, $this->loadAsyncPackages($packageNameMap, $isPackageAcceptableCallable));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $packages;
|
return $packages;
|
||||||
|
@ -295,7 +332,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
return parent::search($query, $mode);
|
return parent::search($query, $mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getProviderNames()
|
private function getProviderNames()
|
||||||
{
|
{
|
||||||
$this->loadRootServerFile();
|
$this->loadRootServerFile();
|
||||||
|
|
||||||
|
@ -315,7 +352,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function configurePackageTransportOptions(PackageInterface $package)
|
private function configurePackageTransportOptions(PackageInterface $package)
|
||||||
{
|
{
|
||||||
foreach ($package->getDistUrls() as $url) {
|
foreach ($package->getDistUrls() as $url) {
|
||||||
if (strpos($url, $this->baseUrl) === 0) {
|
if (strpos($url, $this->baseUrl) === 0) {
|
||||||
|
@ -326,7 +363,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function hasProviders()
|
private function hasProviders()
|
||||||
{
|
{
|
||||||
$this->loadRootServerFile();
|
$this->loadRootServerFile();
|
||||||
|
|
||||||
|
@ -335,21 +372,16 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $name package name
|
* @param string $name package name
|
||||||
* @param bool $bypassFilters If set to true, this bypasses the stability filtering, and forces a recompute without cache
|
|
||||||
* @param callable $isPackageAcceptableCallable
|
* @param callable $isPackageAcceptableCallable
|
||||||
* @return array|mixed
|
* @return array|mixed
|
||||||
*/
|
*/
|
||||||
public function whatProvides($name, $bypassFilters = false, $isPackageAcceptableCallable = null)
|
private function whatProvides($name, $isPackageAcceptableCallable = null)
|
||||||
{
|
{
|
||||||
if (isset($this->providers[$name]) && !$bypassFilters) {
|
if (isset($this->providers[$name])) {
|
||||||
return $this->providers[$name];
|
return $this->providers[$name];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->hasPartialPackages && null === $this->partialPackagesByName) {
|
if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) {
|
||||||
$this->initializePartialPackages();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->hasPartialPackages || !isset($this->partialPackagesByName[$name])) {
|
|
||||||
// skip platform packages, root package and composer-plugin-api
|
// skip platform packages, root package and composer-plugin-api
|
||||||
if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) {
|
if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) {
|
||||||
return array();
|
return array();
|
||||||
|
@ -420,7 +452,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
foreach ($packages['packages'] as $versions) {
|
foreach ($packages['packages'] as $versions) {
|
||||||
$versionsToLoad = array();
|
$versionsToLoad = array();
|
||||||
foreach ($versions as $version) {
|
foreach ($versions as $version) {
|
||||||
if (!$loadingPartialPackage && $this->hasPartialPackages && isset($this->partialPackagesByName[$version['name']])) {
|
if (!$loadingPartialPackage && $this->hasPartialPackages() && isset($this->partialPackagesByName[$version['name']])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,7 +473,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!$bypassFilters && $isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version']))) {
|
if ($isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version']))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,15 +521,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
|
|
||||||
$result = $this->providers[$name];
|
$result = $this->providers[$name];
|
||||||
|
|
||||||
// clean up the cache because otherwise using this puts the repo in an inconsistent state with a polluted unfiltered cache
|
|
||||||
// which is likely not an issue but might cause hard to track behaviors depending on how the repo is used
|
|
||||||
if ($bypassFilters) {
|
|
||||||
foreach ($this->providers[$name] as $uid => $provider) {
|
|
||||||
unset($this->providersByUid[$uid]);
|
|
||||||
}
|
|
||||||
unset($this->providers[$name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,76 +556,76 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
$packages = array();
|
$packages = array();
|
||||||
$repo = $this;
|
$repo = $this;
|
||||||
|
|
||||||
// TODO what if not, then throw?
|
if (!$this->lazyProvidersUrl) {
|
||||||
if ($this->lazyProvidersUrl) {
|
throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url');
|
||||||
foreach ($packageNames as $name => $constraint) {
|
}
|
||||||
$name = strtolower($name);
|
|
||||||
|
|
||||||
// skip platform packages, root package and composer-plugin-api
|
foreach ($packageNames as $name => $constraint) {
|
||||||
if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) {
|
$name = strtolower($name);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$url = str_replace('%package%', $name, $this->lazyProvidersUrl);
|
// skip platform packages, root package and composer-plugin-api
|
||||||
$cacheKey = 'provider-'.strtr($name, '/', '$').'.json';
|
if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$lastModified = null;
|
$url = str_replace('%package%', $name, $this->lazyProvidersUrl);
|
||||||
if ($contents = $this->cache->read($cacheKey)) {
|
$cacheKey = 'provider-'.strtr($name, '/', '$').'.json';
|
||||||
$contents = json_decode($contents, true);
|
|
||||||
$lastModified = isset($contents['last-modified']) ? $contents['last-modified'] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->asyncFetchFile($url, $cacheKey, $lastModified)
|
$lastModified = null;
|
||||||
->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) {
|
if ($contents = $this->cache->read($cacheKey)) {
|
||||||
static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time');
|
$contents = json_decode($contents, true);
|
||||||
|
$lastModified = isset($contents['last-modified']) ? $contents['last-modified'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
if (true === $response) {
|
$this->asyncFetchFile($url, $cacheKey, $lastModified)
|
||||||
$response = $contents;
|
->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) {
|
||||||
}
|
static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time');
|
||||||
|
|
||||||
if (!isset($response['packages'][$name])) {
|
if (true === $response) {
|
||||||
return;
|
$response = $contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
$versionsToLoad = array();
|
if (!isset($response['packages'][$name])) {
|
||||||
foreach ($response['packages'][$name] as $version) {
|
return;
|
||||||
if (isset($version['version_normalizeds'])) {
|
}
|
||||||
foreach ($version['version_normalizeds'] as $index => $normalizedVersion) {
|
|
||||||
if (!$repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $normalizedVersion)) {
|
$versionsToLoad = array();
|
||||||
foreach ($uniqKeys as $key) {
|
foreach ($response['packages'][$name] as $version) {
|
||||||
unset($version[$key.'s'][$index]);
|
if (isset($version['version_normalizeds'])) {
|
||||||
}
|
foreach ($version['version_normalizeds'] as $index => $normalizedVersion) {
|
||||||
|
if (!$repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $normalizedVersion)) {
|
||||||
|
foreach ($uniqKeys as $key) {
|
||||||
|
unset($version[$key.'s'][$index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (count($version['version_normalizeds'])) {
|
}
|
||||||
$versionsToLoad[] = $version;
|
if (count($version['version_normalizeds'])) {
|
||||||
}
|
$versionsToLoad[] = $version;
|
||||||
} else {
|
}
|
||||||
if (!isset($version['version_normalized'])) {
|
} else {
|
||||||
$version['version_normalized'] = $repo->versionParser->normalize($version['version']);
|
if (!isset($version['version_normalized'])) {
|
||||||
}
|
$version['version_normalized'] = $repo->versionParser->normalize($version['version']);
|
||||||
|
}
|
||||||
|
|
||||||
if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $version['version_normalized'])) {
|
if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $version['version_normalized'])) {
|
||||||
$versionsToLoad[] = $version;
|
$versionsToLoad[] = $version;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$loadedPackages = $repo->createPackages($versionsToLoad, 'Composer\Package\CompletePackage');
|
$loadedPackages = $repo->createPackages($versionsToLoad, 'Composer\Package\CompletePackage');
|
||||||
foreach ($loadedPackages as $package) {
|
foreach ($loadedPackages as $package) {
|
||||||
$package->setRepository($repo);
|
$package->setRepository($repo);
|
||||||
|
|
||||||
$packages[spl_object_hash($package)] = $package;
|
$packages[spl_object_hash($package)] = $package;
|
||||||
if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) {
|
if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) {
|
||||||
$packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
|
$packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, function ($e) {
|
}
|
||||||
// TODO use ->done() above instead with react/promise 2.0
|
}, function ($e) {
|
||||||
var_dump('Uncaught Ex', $e->getMessage());
|
// TODO use ->done() above instead with react/promise 2.0
|
||||||
throw $e;
|
throw $e;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->httpDownloader->wait();
|
$this->httpDownloader->wait();
|
||||||
|
@ -684,12 +707,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
}
|
}
|
||||||
|
|
||||||
// metadata-url indiates V2 repo protocol so it takes over from all the V1 types
|
// metadata-url indiates V2 repo protocol so it takes over from all the V1 types
|
||||||
// V2 only has lazyProviders and no ability to process anything else, plus support for async loading
|
// V2 only has lazyProviders and possibly partial packages, but no ability to process anything else,
|
||||||
|
// V2 also supports async loading
|
||||||
if (!empty($data['metadata-url'])) {
|
if (!empty($data['metadata-url'])) {
|
||||||
$this->lazyProvidersUrl = $this->canonicalizeUrl($data['metadata-url']);
|
$this->lazyProvidersUrl = $this->canonicalizeUrl($data['metadata-url']);
|
||||||
$this->providersUrl = null;
|
$this->providersUrl = null;
|
||||||
$this->hasProviders = false;
|
$this->hasProviders = false;
|
||||||
$this->hasPartialPackages = false;
|
$this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']);
|
||||||
$this->allowSslDowngrade = false;
|
$this->allowSslDowngrade = false;
|
||||||
unset($data['providers-url'], $data['providers'], $data['providers-includes']);
|
unset($data['providers-url'], $data['providers'], $data['providers-includes']);
|
||||||
}
|
}
|
||||||
|
@ -711,7 +735,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
return $this->rootData = $data;
|
return $this->rootData = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function canonicalizeUrl($url)
|
private function canonicalizeUrl($url)
|
||||||
{
|
{
|
||||||
if ('/' === $url[0]) {
|
if ('/' === $url[0]) {
|
||||||
return preg_replace('{(https?://[^/]+).*}i', '$1' . $url, $this->url);
|
return preg_replace('{(https?://[^/]+).*}i', '$1' . $url, $this->url);
|
||||||
|
@ -720,14 +744,23 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function loadDataFromServer()
|
private function loadDataFromServer()
|
||||||
{
|
{
|
||||||
$data = $this->loadRootServerFile();
|
$data = $this->loadRootServerFile();
|
||||||
|
|
||||||
return $this->loadIncludes($data);
|
return $this->loadIncludes($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function loadProviderListings($data)
|
private function hasPartialPackages()
|
||||||
|
{
|
||||||
|
if ($this->hasPartialPackages && null === $this->partialPackagesByName) {
|
||||||
|
$this->initializePartialPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->hasPartialPackages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadProviderListings($data)
|
||||||
{
|
{
|
||||||
if (isset($data['providers'])) {
|
if (isset($data['providers'])) {
|
||||||
if (!is_array($this->providerListing)) {
|
if (!is_array($this->providerListing)) {
|
||||||
|
@ -752,7 +785,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function loadIncludes($data)
|
private function loadIncludes($data)
|
||||||
{
|
{
|
||||||
$packages = array();
|
$packages = array();
|
||||||
|
|
||||||
|
@ -924,7 +957,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function fetchFileIfLastModified($filename, $cacheKey, $lastModifiedTime)
|
private function fetchFileIfLastModified($filename, $cacheKey, $lastModifiedTime)
|
||||||
{
|
{
|
||||||
$retries = 3;
|
$retries = 3;
|
||||||
while ($retries--) {
|
while ($retries--) {
|
||||||
|
@ -990,7 +1023,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function asyncFetchFile($filename, $cacheKey, $lastModifiedTime = null)
|
private function asyncFetchFile($filename, $cacheKey, $lastModifiedTime = null)
|
||||||
{
|
{
|
||||||
$retries = 3;
|
$retries = 3;
|
||||||
$httpDownloader = $this->httpDownloader;
|
$httpDownloader = $this->httpDownloader;
|
||||||
|
@ -1039,7 +1072,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
};
|
};
|
||||||
|
|
||||||
$reject = function ($e) use (&$retries, $httpDownloader, $filename, $options, &$reject, $accept, $io, $url, $cache, &$degradedMode) {
|
$reject = function ($e) use (&$retries, $httpDownloader, $filename, $options, &$reject, $accept, $io, $url, $cache, &$degradedMode) {
|
||||||
var_dump('Caught8', $e->getMessage());
|
|
||||||
if ($e instanceof TransportException && $e->getStatusCode() === 404) {
|
if ($e instanceof TransportException && $e->getStatusCode() === 404) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,132 +211,127 @@ class CurlDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
$active = true;
|
$active = true;
|
||||||
try {
|
$this->checkCurlResult(curl_multi_exec($this->multiHandle, $active));
|
||||||
$this->checkCurlResult(curl_multi_exec($this->multiHandle, $active));
|
if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) {
|
||||||
if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) {
|
// sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select
|
||||||
// sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select
|
usleep(150);
|
||||||
usleep(150);
|
}
|
||||||
|
|
||||||
|
while ($progress = curl_multi_info_read($this->multiHandle)) {
|
||||||
|
$curlHandle = $progress['handle'];
|
||||||
|
$i = (int) $curlHandle;
|
||||||
|
if (!isset($this->jobs[$i])) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
$progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);
|
||||||
|
$job = $this->jobs[$i];
|
||||||
|
unset($this->jobs[$i]);
|
||||||
|
curl_multi_remove_handle($this->multiHandle, $curlHandle);
|
||||||
|
$error = curl_error($curlHandle);
|
||||||
|
$errno = curl_errno($curlHandle);
|
||||||
|
curl_close($curlHandle);
|
||||||
|
|
||||||
while ($progress = curl_multi_info_read($this->multiHandle)) {
|
$headers = null;
|
||||||
$curlHandle = $progress['handle'];
|
$statusCode = null;
|
||||||
$i = (int) $curlHandle;
|
$response = null;
|
||||||
if (!isset($this->jobs[$i])) {
|
try {
|
||||||
continue;
|
// TODO progress
|
||||||
|
//$this->onProgress($curlHandle, $job['callback'], $progress, $job['progress']);
|
||||||
|
if (CURLE_OK !== $errno) {
|
||||||
|
throw new TransportException($error);
|
||||||
}
|
}
|
||||||
$progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);
|
|
||||||
$job = $this->jobs[$i];
|
|
||||||
unset($this->jobs[$i]);
|
|
||||||
curl_multi_remove_handle($this->multiHandle, $curlHandle);
|
|
||||||
$error = curl_error($curlHandle);
|
|
||||||
$errno = curl_errno($curlHandle);
|
|
||||||
curl_close($curlHandle);
|
|
||||||
|
|
||||||
$headers = null;
|
$statusCode = $progress['http_code'];
|
||||||
$statusCode = null;
|
rewind($job['headerHandle']);
|
||||||
$response = null;
|
$headers = explode("\r\n", rtrim(stream_get_contents($job['headerHandle'])));
|
||||||
try {
|
fclose($job['headerHandle']);
|
||||||
// TODO progress
|
|
||||||
//$this->onProgress($curlHandle, $job['callback'], $progress, $job['progress']);
|
|
||||||
if (CURLE_OK !== $errno) {
|
|
||||||
throw new TransportException($error);
|
|
||||||
}
|
|
||||||
|
|
||||||
$statusCode = $progress['http_code'];
|
// prepare response object
|
||||||
rewind($job['headerHandle']);
|
if ($job['filename']) {
|
||||||
$headers = explode("\r\n", rtrim(stream_get_contents($job['headerHandle'])));
|
fclose($job['bodyHandle']);
|
||||||
fclose($job['headerHandle']);
|
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $job['filename'].'~');
|
||||||
|
$this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG);
|
||||||
|
} else {
|
||||||
|
rewind($job['bodyHandle']);
|
||||||
|
$contents = stream_get_contents($job['bodyHandle']);
|
||||||
|
fclose($job['bodyHandle']);
|
||||||
|
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
|
||||||
|
$this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
// prepare response object
|
$result = $this->isAuthenticatedRetryNeeded($job, $response);
|
||||||
if ($job['filename']) {
|
if ($result['retry']) {
|
||||||
fclose($job['bodyHandle']);
|
|
||||||
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $job['filename'].'~');
|
|
||||||
$this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG);
|
|
||||||
} else {
|
|
||||||
rewind($job['bodyHandle']);
|
|
||||||
$contents = stream_get_contents($job['bodyHandle']);
|
|
||||||
fclose($job['bodyHandle']);
|
|
||||||
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
|
|
||||||
$this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG);
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $this->isAuthenticatedRetryNeeded($job, $response);
|
|
||||||
if ($result['retry']) {
|
|
||||||
if ($job['filename']) {
|
|
||||||
@unlink($job['filename'].'~');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->restartJob($job, $job['url'], array('storeAuth' => $result['storeAuth']));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle 3xx redirects, 304 Not Modified is excluded
|
|
||||||
if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) {
|
|
||||||
$location = $this->handleRedirect($job, $response);
|
|
||||||
if ($location) {
|
|
||||||
$this->restartJob($job, $location, array('redirects' => $job['attributes']['redirects'] + 1));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fail 4xx and 5xx responses and capture the response
|
|
||||||
if ($statusCode >= 400 && $statusCode <= 599) {
|
|
||||||
throw $this->failResponse($job, $response, $response->getStatusMessage());
|
|
||||||
// TODO progress
|
|
||||||
// $this->io->overwriteError("Downloading (<error>failed</error>)", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($job['attributes']['storeAuth']) {
|
|
||||||
$this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve promise
|
|
||||||
if ($job['filename']) {
|
|
||||||
rename($job['filename'].'~', $job['filename']);
|
|
||||||
call_user_func($job['resolve'], true);
|
|
||||||
} else {
|
|
||||||
call_user_func($job['resolve'], $response);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
if ($e instanceof TransportException && $headers) {
|
|
||||||
$e->setHeaders($headers);
|
|
||||||
$e->setStatusCode($statusCode);
|
|
||||||
}
|
|
||||||
if ($e instanceof TransportException && $response) {
|
|
||||||
$e->setResponse($response->getBody());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_resource($job['headerHandle'])) {
|
|
||||||
fclose($job['headerHandle']);
|
|
||||||
}
|
|
||||||
if (is_resource($job['bodyHandle'])) {
|
|
||||||
fclose($job['bodyHandle']);
|
|
||||||
}
|
|
||||||
if ($job['filename']) {
|
if ($job['filename']) {
|
||||||
@unlink($job['filename'].'~');
|
@unlink($job['filename'].'~');
|
||||||
}
|
}
|
||||||
call_user_func($job['reject'], $e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->jobs as $i => $curlHandle) {
|
$this->restartJob($job, $job['url'], array('storeAuth' => $result['storeAuth']));
|
||||||
if (!isset($this->jobs[$i])) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$curlHandle = $this->jobs[$i]['curlHandle'];
|
|
||||||
$progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);
|
|
||||||
|
|
||||||
if ($this->jobs[$i]['progress'] !== $progress) {
|
// handle 3xx redirects, 304 Not Modified is excluded
|
||||||
$previousProgress = $this->jobs[$i]['progress'];
|
if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) {
|
||||||
$this->jobs[$i]['progress'] = $progress;
|
$location = $this->handleRedirect($job, $response);
|
||||||
|
if ($location) {
|
||||||
// TODO
|
$this->restartJob($job, $location, array('redirects' => $job['attributes']['redirects'] + 1));
|
||||||
//$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress);
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fail 4xx and 5xx responses and capture the response
|
||||||
|
if ($statusCode >= 400 && $statusCode <= 599) {
|
||||||
|
throw $this->failResponse($job, $response, $response->getStatusMessage());
|
||||||
|
// TODO progress
|
||||||
|
// $this->io->overwriteError("Downloading (<error>failed</error>)", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($job['attributes']['storeAuth']) {
|
||||||
|
$this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve promise
|
||||||
|
if ($job['filename']) {
|
||||||
|
rename($job['filename'].'~', $job['filename']);
|
||||||
|
call_user_func($job['resolve'], true);
|
||||||
|
} else {
|
||||||
|
call_user_func($job['resolve'], $response);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if ($e instanceof TransportException && $headers) {
|
||||||
|
$e->setHeaders($headers);
|
||||||
|
$e->setStatusCode($statusCode);
|
||||||
|
}
|
||||||
|
if ($e instanceof TransportException && $response) {
|
||||||
|
$e->setResponse($response->getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_resource($job['headerHandle'])) {
|
||||||
|
fclose($job['headerHandle']);
|
||||||
|
}
|
||||||
|
if (is_resource($job['bodyHandle'])) {
|
||||||
|
fclose($job['bodyHandle']);
|
||||||
|
}
|
||||||
|
if ($job['filename']) {
|
||||||
|
@unlink($job['filename'].'~');
|
||||||
|
}
|
||||||
|
call_user_func($job['reject'], $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->jobs as $i => $curlHandle) {
|
||||||
|
if (!isset($this->jobs[$i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$curlHandle = $this->jobs[$i]['curlHandle'];
|
||||||
|
$progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);
|
||||||
|
|
||||||
|
if ($this->jobs[$i]['progress'] !== $progress) {
|
||||||
|
$previousProgress = $this->jobs[$i]['progress'];
|
||||||
|
$this->jobs[$i]['progress'] = $progress;
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
//$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress);
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
|
||||||
// TODO
|
|
||||||
var_dump('Caught2', get_class($e), $e->getMessage(), $e);die;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,9 @@ class ComposerRepositoryTest extends TestCase
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
$packages = $repo->whatProvides('a', false, array($this, 'isPackageAcceptableReturnTrue'));
|
$reflMethod = new \ReflectionMethod($repo, 'whatProvides');
|
||||||
|
$reflMethod->setAccessible(true);
|
||||||
|
$packages = $reflMethod->invoke($repo, 'a', array($this, 'isPackageAcceptableReturnTrue'));
|
||||||
|
|
||||||
$this->assertCount(7, $packages);
|
$this->assertCount(7, $packages);
|
||||||
$this->assertEquals(array('1', '1-alias', '2', '2-alias', '2-root', '3', '3-root'), array_keys($packages));
|
$this->assertEquals(array('1', '1-alias', '2', '2-alias', '2-root', '3', '3-root'), array_keys($packages));
|
||||||
|
|
Loading…
Reference in New Issue