diff --git a/src/Application/Processor/Handler/PackageDiscoveryHandler.php b/src/Application/Processor/Handler/PackageDiscoveryHandler.php index d7f43480..ca46a068 100644 --- a/src/Application/Processor/Handler/PackageDiscoveryHandler.php +++ b/src/Application/Processor/Handler/PackageDiscoveryHandler.php @@ -10,13 +10,13 @@ use App\Application\Message\Event\Stats\StatsUpdatedEvent; use App\Application\Message\Event\Version\VersionCreatedEvent; use App\Application\Message\Event\Version\VersionUpdatedEvent; +use App\Application\Service\Packagist; use App\Domain\Dependency\DependencyRepositoryInterface; use App\Domain\Dependency\DependencyStatusEnum; use App\Domain\Package\PackageRepositoryInterface; use App\Domain\Stats\StatsRepositoryInterface; use App\Domain\Version\VersionRepositoryInterface; use App\Domain\Version\VersionStatusEnum; -use Buzz\Browser; use Courier\Client\Producer; use Courier\Message\CommandInterface; use Courier\Processor\Handler\HandlerResultEnum; @@ -24,17 +24,12 @@ use Psr\Log\LoggerInterface; class PackageDiscoveryHandler implements InvokeHandlerInterface { - /** - * File cache lifetime (12 hour TTL) - */ - private const FILE_TIMEOUT = 43200; - private DependencyRepositoryInterface $dependencyRepository; private PackageRepositoryInterface $packageRepository; private StatsRepositoryInterface $statsRepository; private VersionRepositoryInterface $versionRepository; private Producer $producer; - private Browser $browser; + private Packagist $packagist; private LoggerInterface $logger; public function __construct( @@ -43,7 +38,7 @@ public function __construct( StatsRepositoryInterface $statsRepository, VersionRepositoryInterface $versionRepository, Producer $producer, - Browser $browser, + Packagist $packagist, LoggerInterface $logger ) { $this->dependencyRepository = $dependencyRepository; @@ -51,7 +46,7 @@ public function __construct( $this->statsRepository = $statsRepository; $this->versionRepository = $versionRepository; $this->producer = $producer; - $this->browser = $browser; + $this->packagist = $packagist; $this->logger = $logger; } @@ -62,213 +57,187 @@ public function __construct( * - Package statistics */ public function __invoke(CommandInterface $command): HandlerResultEnum { - $package = $command->getPackage(); - - $dataPath = sprintf( - '%s/packages-%s.json', - sys_get_temp_dir(), - str_replace('/', '-', $package->getName()) - ); + try { + $package = $command->getPackage(); - $modTime = false; - if (file_exists($dataPath)) { - $modTime = filemtime($dataPath); - } - - if ($modTime === false || (time() - $modTime) > self::FILE_TIMEOUT) { - $url = sprintf( - 'https://packagist.org/packages/%s.json', - $package->getName() - ); + $metadata = $this->packagist->getPackageMetadataVersion1($package->getName()); - $response = $this->browser->get($url, ['User-Agent' => 'php.package.health (twitter.com/flavioheleno)']); - if ($response->getStatusCode() >= 400) { - $this->logger->critical('Failed to download metadata', [$package, $response]); + $package = $package + ->withDescription($metadata['description'] ?? '') + ->withUrl($metadata['repository'] ?? ''); + if ($package->isDirty()) { + $package = $this->packageRepository->update($package); - return HandlerResultEnum::Requeue; + $this->producer->sendEvent( + new PackageUpdatedEvent($package) + ); } - file_put_contents($dataPath, (string)$response->getBody()); - } - - $info = json_decode(file_get_contents($dataPath), true, 512, JSON_THROW_ON_ERROR); - if ($info === null) { - $this->logger->critical('Failed to download metadata', [$package]); - - return HandlerResultEnum::Requeue; - } - - $package = $package - ->withDescription($info['package']['description'] ?? '') - ->withUrl($info['package']['repository'] ?? ''); - if ($package->isDirty()) { - $package = $this->packageRepository->update($package); - - $this->producer->sendEvent( - new PackageUpdatedEvent($package) - ); - } - - $statsCol = $this->statsRepository->find( - [ - 'package_name' => $package->getName() - ] - ); - - $stats = $statsCol[0] ?? null; - if ($stats === null) { - $stats = $this->statsRepository->create( - $package->getName() - ); - - $this->producer->sendEvent( - new StatsCreatedEvent($stats) - ); - } - - $stats = $stats - ->withGithubStars($info['package']['github_stars'] ?? 0) - ->withGithubWatchers($info['package']['github_watchers'] ?? 0) - ->withGithubForks($info['package']['github_forks'] ?? 0) - ->withDependents($info['package']['dependents'] ?? 0) - ->withSuggesters($info['package']['suggesters'] ?? 0) - ->withFavers($info['package']['favers'] ?? 0) - ->withTotalDownloads($info['package']['downloads']['total'] ?? 0) - ->withMonthlyDownloads($info['package']['downloads']['monthly'] ?? 0) - ->withDailyDownloads($info['package']['downloads']['daily'] ?? 0); - - if ($stats->isDirty()) { - $stats = $this->statsRepository->update($stats); - - $this->producer->sendEvent( - new StatsUpdatedEvent($stats) - ); - } - - // version list is empty - if (count($info['package']['versions']) === 0) { - $this->logger->notice('Version list is empty', [$package]); - - return HandlerResultEnum::Accept; - } - - foreach (array_reverse($info['package']['versions']) as $release) { - // exclude branches from tagged releases (https://getcomposer.org/doc/articles/versions.md#branches) - $isBranch = preg_match('/^dev-|-dev$/', $release['version']) === 1; - - // find by the unique constraint (number, package_name) - $versionCol = $this->versionRepository->find( + $statsCol = $this->statsRepository->find( [ - 'number' => $release['version'], 'package_name' => $package->getName() ] ); - $version = $versionCol[0] ?? null; - if ($version === null) { - $version = $this->versionRepository->create( - $release['version'], - $release['version_normalized'], - $package->getName(), - $isBranch === false + $stats = $statsCol[0] ?? null; + if ($stats === null) { + $stats = $this->statsRepository->create( + $package->getName() ); $this->producer->sendEvent( - new VersionCreatedEvent($version) + new StatsCreatedEvent($stats) ); } - // track "require" dependencies - $filteredRequire = array_filter( - $release['require'] ?? [], - static function (string $key): bool { - return preg_match('/^(php|hhvm|ext-.*|lib-.*|pear-.*)$/', $key) !== 1 && - preg_match('/^[^\/]+\/[^\/]+$/', $key) === 1; - }, - ARRAY_FILTER_USE_KEY - ); + $stats = $stats + ->withGithubStars($metadata['github_stars'] ?? 0) + ->withGithubWatchers($metadata['github_watchers'] ?? 0) + ->withGithubForks($metadata['github_forks'] ?? 0) + ->withDependents($metadata['dependents'] ?? 0) + ->withSuggesters($metadata['suggesters'] ?? 0) + ->withFavers($metadata['favers'] ?? 0) + ->withTotalDownloads($metadata['downloads']['total'] ?? 0) + ->withMonthlyDownloads($metadata['downloads']['monthly'] ?? 0) + ->withDailyDownloads($metadata['downloads']['daily'] ?? 0); - // flag packages without require dependencies with VersionStatusEnum::NoDeps - if (empty($filteredRequire)) { - $version = $version->withStatus(VersionStatusEnum::NoDeps); - $version = $this->versionRepository->update($version); + if ($stats->isDirty()) { + $stats = $this->statsRepository->update($stats); $this->producer->sendEvent( - new VersionUpdatedEvent($version) + new StatsUpdatedEvent($stats) ); } - foreach ($filteredRequire as $dependencyName => $constraint) { - if ($constraint === 'self.version') { - // need to find out how to handle this - continue; - } + // version list is empty + if (count($metadata['versions']) === 0) { + $this->logger->notice('Version list is empty', [$package]); + + return HandlerResultEnum::Accept; + } + + foreach (array_reverse($metadata['versions']) as $release) { + // exclude branches from tagged releases (https://getcomposer.org/doc/articles/versions.md#branches) + $isBranch = preg_match('/^dev-|-dev$/', $release['version']) === 1; - // find by the unique constraint (version_id, name, development) - $dependencyCol = $this->dependencyRepository->find( + // find by the unique constraint (number, package_name) + $versionCol = $this->versionRepository->find( [ - 'version_id' => $version->getId(), - 'name' => $dependencyName, - 'development' => false + 'number' => $release['version'], + 'package_name' => $package->getName() ] ); - $dependency = $dependencyCol[0] ?? null; - if ($dependency === null) { - $dependency = $this->dependencyRepository->create( - $version->getId(), - $dependencyName, - $constraint, - false + $version = $versionCol[0] ?? null; + if ($version === null) { + $version = $this->versionRepository->create( + $release['version'], + $release['version_normalized'], + $package->getName(), + $isBranch === false ); $this->producer->sendEvent( - new DependencyCreatedEvent($dependency) + new VersionCreatedEvent($version) ); } - } - // track "require-dev" dependencies - $filteredRequireDev = array_filter( - $release['require-dev'] ?? [], - static function (string $key): bool { - return preg_match('/^(php|hhvm|ext-.*|lib-.*|pear-.*)$/', $key) !== 1 && - preg_match('/^[^\/]+\/[^\/]+$/', $key) === 1; - }, - ARRAY_FILTER_USE_KEY - ); + // track "require" dependencies + $filteredRequire = array_filter( + $release['require'] ?? [], + static function (string $key): bool { + return preg_match('/^(php|hhvm|ext-.*|lib-.*|pear-.*)$/', $key) !== 1 && + preg_match('/^[^\/]+\/[^\/]+$/', $key) === 1; + }, + ARRAY_FILTER_USE_KEY + ); + + // flag packages without require dependencies with VersionStatusEnum::NoDeps + if (empty($filteredRequire)) { + $version = $version->withStatus(VersionStatusEnum::NoDeps); + $version = $this->versionRepository->update($version); + + $this->producer->sendEvent( + new VersionUpdatedEvent($version) + ); + } - foreach ($filteredRequireDev as $dependencyName => $constraint) { - if ($constraint === 'self.version') { - // need to find out how to handle this - continue; + foreach ($filteredRequire as $dependencyName => $constraint) { + if ($constraint === 'self.version') { + // need to find out how to handle this + continue; + } + + // find by the unique constraint (version_id, name, development) + $dependencyCol = $this->dependencyRepository->find( + [ + 'version_id' => $version->getId(), + 'name' => $dependencyName, + 'development' => false + ] + ); + + $dependency = $dependencyCol[0] ?? null; + if ($dependency === null) { + $dependency = $this->dependencyRepository->create( + $version->getId(), + $dependencyName, + $constraint, + false + ); + + $this->producer->sendEvent( + new DependencyCreatedEvent($dependency) + ); + } } - // find by the unique constraint (version_id, name, development) - $dependencyCol = $this->dependencyRepository->find( - [ - 'version_id' => $version->getId(), - 'name' => $dependencyName, - 'development' => true - ] + // track "require-dev" dependencies + $filteredRequireDev = array_filter( + $release['require-dev'] ?? [], + static function (string $key): bool { + return preg_match('/^(php|hhvm|ext-.*|lib-.*|pear-.*)$/', $key) !== 1 && + preg_match('/^[^\/]+\/[^\/]+$/', $key) === 1; + }, + ARRAY_FILTER_USE_KEY ); - $dependency = $dependencyCol[0] ?? null; - if ($dependency === null) { - $dependency = $this->dependencyRepository->create( - $version->getId(), - $dependencyName, - $constraint, - true + foreach ($filteredRequireDev as $dependencyName => $constraint) { + if ($constraint === 'self.version') { + // need to find out how to handle this + continue; + } + + // find by the unique constraint (version_id, name, development) + $dependencyCol = $this->dependencyRepository->find( + [ + 'version_id' => $version->getId(), + 'name' => $dependencyName, + 'development' => true + ] ); - $this->producer->sendEvent( - new DependencyCreatedEvent($dependency) - ); + $dependency = $dependencyCol[0] ?? null; + if ($dependency === null) { + $dependency = $this->dependencyRepository->create( + $version->getId(), + $dependencyName, + $constraint, + true + ); + + $this->producer->sendEvent( + new DependencyCreatedEvent($dependency) + ); + } } } - } - return HandlerResultEnum::Accept; + return HandlerResultEnum::Accept; + } catch (Exception $exception) { + $this->logger->error($exception->getMessage(), [$exception]); + + return HandlerResultEnum::Requeue; + } } }