Skip to content

Commit 308ca51

Browse files
committed
Improve package message handlers
Signed-off-by: Tim Goudriaan <[email protected]>
1 parent 709b007 commit 308ca51

File tree

10 files changed

+109
-57
lines changed

10 files changed

+109
-57
lines changed

src/Controller/ApiController.php

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use CodedMonkey\Dirigent\Package\PackageDistributionResolver;
1515
use CodedMonkey\Dirigent\Package\PackageMetadataResolver;
1616
use CodedMonkey\Dirigent\Package\PackageProviderManager;
17+
use Doctrine\ORM\EntityManagerInterface;
1718
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1819
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1920
use Symfony\Component\HttpFoundation\BinaryFileResponse;
@@ -31,12 +32,15 @@
3132
class ApiController extends AbstractController
3233
{
3334
public function __construct(
35+
private readonly EntityManagerInterface $entityManager,
3436
private readonly PackageRepository $packageRepository,
3537
private readonly VersionRepository $versionRepository,
3638
private readonly PackageMetadataResolver $metadataResolver,
3739
private readonly PackageDistributionResolver $distributionResolver,
3840
private readonly PackageProviderManager $providerManager,
3941
private readonly MessageBusInterface $messenger,
42+
#[Autowire(param: 'dirigent.packages.dynamic_updates')]
43+
private readonly bool $dynamicUpdatesEnabled,
4044
#[Autowire(param: 'dirigent.metadata.mirror_vcs_repositories')]
4145
private readonly bool $mirrorVcsRepositories = false,
4246
) {
@@ -81,15 +85,13 @@ public function root(RouterInterface $router): JsonResponse
8185
#[IsGrantedAccess]
8286
public function packageMetadata(Request $request): Response
8387
{
84-
$packageName = $request->attributes->get('package');
88+
$packageName = $request->attributes->getString('package');
8589
$basePackageName = u($packageName)->trimSuffix('~dev')->toString();
8690

87-
if (null === $package = $this->findPackage($basePackageName)) {
91+
if (null === $this->findPackage($basePackageName)) {
8892
throw $this->createNotFoundException();
8993
}
9094

91-
$this->messenger->dispatch(new UpdatePackage($package->getId(), PackageUpdateSource::Dynamic));
92-
9395
if (!$this->providerManager->exists($packageName)) {
9496
throw $this->createNotFoundException();
9597
}
@@ -126,8 +128,6 @@ public function packageDistribution(Request $request, string $reference, string
126128
throw $this->createNotFoundException();
127129
}
128130

129-
$this->messenger->dispatch(new UpdatePackage($package->getId(), PackageUpdateSource::Dynamic));
130-
131131
if (null === $version = $this->versionRepository->findOneByNormalizedVersion($package, $versionName)) {
132132
throw $this->createNotFoundException();
133133
}
@@ -167,24 +167,41 @@ public function trackInstallations(Request $request): Response
167167
return new JsonResponse(['status' => 'success'], Response::HTTP_CREATED);
168168
}
169169

170-
private function findPackage(string $packageName): ?Package
170+
private function findPackage(string $packageName, ?bool $create = false): ?Package
171171
{
172-
// Search for the package in the database
173-
if (null !== $package = $this->packageRepository->findOneByName($packageName)) {
174-
return $package;
175-
}
172+
$this->entityManager->beginTransaction();
173+
174+
try {
175+
// Search for the package in the database
176+
if (null === $package = $this->packageRepository->findOneByName($packageName)) {
177+
if (!$create || !$this->dynamicUpdatesEnabled) {
178+
// It doesn't exist in the database and we can't create it dynamically
179+
return null;
180+
}
181+
182+
// Search for the package in external registries
183+
if (null === $registry = $this->metadataResolver->findPackageProvider($packageName)) {
184+
return null;
185+
}
186+
187+
$package = new Package();
188+
$package->setName($packageName);
189+
$package->setMirrorRegistry($registry);
190+
$package->setFetchStrategy($this->mirrorVcsRepositories ? PackageFetchStrategy::Vcs : PackageFetchStrategy::Mirror);
191+
192+
$this->packageRepository->save($package, true);
193+
}
176194

177-
// Attempt to find a package from external registries
178-
if (null === $registry = $this->metadataResolver->findPackageProvider($packageName)) {
179-
return null;
180-
}
195+
if ($this->dynamicUpdatesEnabled) {
196+
$this->messenger->dispatch(new UpdatePackage($package->getId(), PackageUpdateSource::Dynamic));
197+
}
198+
} catch (\Throwable $exception) {
199+
$this->entityManager->rollback();
181200

182-
$package = new Package();
183-
$package->setName($packageName);
184-
$package->setMirrorRegistry($registry);
185-
$package->setFetchStrategy($this->mirrorVcsRepositories ? PackageFetchStrategy::Vcs : PackageFetchStrategy::Mirror);
201+
throw $exception;
202+
}
186203

187-
$this->packageRepository->save($package, true);
204+
$this->entityManager->commit();
188205

189206
return $package;
190207
}

src/Doctrine/Entity/Package.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ public function setUpdatedAt(\DateTimeImmutable $updatedAt): void
298298
$this->updatedAt = $updatedAt;
299299
}
300300

301+
public function isUpdateScheduled(): bool
302+
{
303+
return null !== $this->updateScheduledAt;
304+
}
305+
301306
public function getUpdateScheduledAt(): ?\DateTimeImmutable
302307
{
303308
return $this->updateScheduledAt;

src/Doctrine/Repository/PackageRepository.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*/
2020
class PackageRepository extends ServiceEntityRepository
2121
{
22-
private \DateInterval $periodicUpdateInterval;
22+
private ?\DateInterval $periodicUpdateInterval;
2323

2424
/**
2525
* @var array<string, Package>
@@ -29,11 +29,11 @@ class PackageRepository extends ServiceEntityRepository
2929
public function __construct(
3030
ManagerRegistry $registry,
3131
#[Autowire(param: 'dirigent.packages.periodic_update_interval')]
32-
string $periodicUpdateInterval,
32+
?string $periodicUpdateInterval,
3333
) {
3434
parent::__construct($registry, Package::class);
3535

36-
$this->periodicUpdateInterval = new \DateInterval($periodicUpdateInterval);
36+
$this->periodicUpdateInterval = $periodicUpdateInterval ? new \DateInterval($periodicUpdateInterval) : null;
3737
}
3838

3939
public function save(Package $entity, bool $flush = false): void
@@ -83,6 +83,10 @@ public function getStalePackageIds(): array
8383
{
8484
$connection = $this->getEntityManager()->getConnection();
8585

86+
if (!$this->periodicUpdateInterval) {
87+
return [];
88+
}
89+
8690
$staleFrom = (new \DateTimeImmutable())->setTimezone(new \DateTimeZone('UTC'));
8791
$staleFrom = $staleFrom->sub($this->periodicUpdateInterval);
8892

src/Doctrine/Repository/VersionRepository.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public function findOneByNormalizedVersion(Package $package, string $version): ?
4545
return $this->findOneBy(['package' => $package, 'normalizedVersion' => $version]);
4646
}
4747

48+
/**
49+
* @return array<string, array{id: int, version: string, normalized_version: string, source: ?array}>
50+
*/
4851
public function getVersionMetadataForUpdate(Package $package): array
4952
{
5053
$rows = $this->getEntityManager()->getConnection()->fetchAllAssociative(
@@ -57,7 +60,9 @@ public function getVersionMetadataForUpdate(Package $package): array
5760
if ($row['source']) {
5861
$row['source'] = json_decode($row['source'], true);
5962
}
60-
$versions[strtolower($row['normalized_version'])] = $row;
63+
64+
$key = strtolower($row['normalized_version']);
65+
$versions[$key] = $row;
6166
}
6267

6368
return $versions;

src/Message/DumpPackageProviderHandler.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#[AsMessageHandler]
1010
readonly class DumpPackageProviderHandler
1111
{
12+
use PackageHandlerTrait;
13+
1214
public function __construct(
1315
private PackageRepository $packageRepository,
1416
private PackageProviderManager $providerManager,
@@ -17,7 +19,7 @@ public function __construct(
1719

1820
public function __invoke(DumpPackageProvider $message): void
1921
{
20-
$package = $this->packageRepository->find($message->packageId);
22+
$package = $this->getPackage($this->packageRepository, $message->packageId);
2123

2224
$this->providerManager->dump($package);
2325
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace CodedMonkey\Dirigent\Message;
4+
5+
use CodedMonkey\Dirigent\Doctrine\Entity\Package;
6+
use CodedMonkey\Dirigent\Doctrine\Repository\PackageRepository;
7+
8+
trait PackageHandlerTrait
9+
{
10+
private function getPackage(PackageRepository $repository, int $id): Package
11+
{
12+
if (null === $package = $repository->find($id)) {
13+
throw new \InvalidArgumentException("Package (id: $id) not found.");
14+
}
15+
16+
return $package;
17+
}
18+
}

src/Message/SchedulePackageUpdateHandler.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#[AsMessageHandler]
1212
readonly class SchedulePackageUpdateHandler
1313
{
14+
use PackageHandlerTrait;
15+
1416
public function __construct(
1517
private PackageRepository $packageRepository,
1618
private MessageBusInterface $messenger,
@@ -19,7 +21,7 @@ public function __construct(
1921

2022
public function __invoke(SchedulePackageUpdate $message): void
2123
{
22-
$package = $this->packageRepository->find($message->packageId);
24+
$package = $this->getPackage($this->packageRepository, $message->packageId);
2325

2426
$stamps = [new TransportNamesStamp('async')];
2527

src/Message/UpdatePackageHandler.php

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,30 @@
1111
#[AsMessageHandler]
1212
readonly class UpdatePackageHandler
1313
{
14-
private \DateInterval $updateDelay;
14+
use PackageHandlerTrait;
15+
16+
private ?\DateInterval $dynamicUpdateDelay;
1517

1618
public function __construct(
1719
private PackageRepository $packageRepository,
1820
private PackageMetadataResolver $metadataResolver,
19-
#[Autowire(param: 'dirigent.packages.dynamic_updates')]
20-
private bool $dynamicUpdatesEnabled,
2121
#[Autowire(param: 'dirigent.packages.dynamic_update_delay')]
22-
string $updateDelay,
22+
?string $dynamicUpdateDelay,
2323
) {
24-
$this->updateDelay = new \DateInterval($updateDelay);
24+
$this->dynamicUpdateDelay = $dynamicUpdateDelay ? new \DateInterval($dynamicUpdateDelay) : null;
2525
}
2626

2727
public function __invoke(UpdatePackage $message): void
2828
{
29-
if ($message->source->isDynamic() && !$this->dynamicUpdatesEnabled) {
30-
// Dynamic updates are disabled
31-
return;
32-
}
33-
34-
$package = $this->packageRepository->find($message->packageId);
29+
$package = $this->getPackage($this->packageRepository, $message->packageId);
3530

36-
if ($message->scheduled && null === $package->getUpdateScheduledAt()) {
31+
if ($message->scheduled && !$package->isUpdateScheduled()) {
3732
// Package was already updated between being scheduled and now,
3833
// so stop the update to prevent excessive requests
3934
return;
4035
}
4136

42-
if (!$message->source->isManual() && $this->isFresh($package)) {
37+
if ($message->source->isDynamic() && $this->isFreshDynamicUpdate($package)) {
4338
// Package was recently updated
4439
return;
4540
}
@@ -51,17 +46,20 @@ public function __invoke(UpdatePackage $message): void
5146
$this->packageRepository->save($package, true);
5247
}
5348

54-
private function isFresh(Package $package): bool
49+
private function isFreshDynamicUpdate(Package $package): bool
5550
{
51+
// If the package was never updated, it's always stale
5652
if (null === $lastUpdatedAt = $package->getUpdatedAt()) {
5753
return false;
5854
}
5955

60-
$updateDelay = $package->getMirrorRegistry()?->getDynamicUpdateDelay() ?? $this->updateDelay;
56+
// Override update delay from registry
57+
$dynamicUpdateDelay = $package->getMirrorRegistry()?->getDynamicUpdateDelay() ?? $this->dynamicUpdateDelay;
6158

62-
$now = (new \DateTimeImmutable())->setTimezone(new \DateTimeZone('UTC'));
63-
$before = $now->sub($updateDelay);
59+
$freshFrom = (new \DateTimeImmutable())->setTimezone(new \DateTimeZone('UTC'));
60+
$freshFrom = $freshFrom->sub($dynamicUpdateDelay ?? new \DateInterval('PT0'));
6461

65-
return $before->getTimestamp() < $lastUpdatedAt->getTimestamp();
62+
// Check if the package was updated recently, and therefore fresh
63+
return $freshFrom < $lastUpdatedAt;
6664
}
6765
}

src/Message/UpdatePackageLinksHandler.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#[AsMessageHandler]
1010
readonly class UpdatePackageLinksHandler
1111
{
12+
use PackageHandlerTrait;
13+
1214
public function __construct(
1315
private PackageRepository $packageRepository,
1416
private VersionRepository $versionRepository,
@@ -17,7 +19,7 @@ public function __construct(
1719

1820
public function __invoke(UpdatePackageLinks $message): void
1921
{
20-
$package = $this->packageRepository->find($message->packageId);
22+
$package = $this->getPackage($this->packageRepository, $message->packageId);
2123
$version = $this->versionRepository->findOneByNormalizedVersion($package, $message->versionName);
2224

2325
$this->packageRepository->updatePackageLinks($package, $version);

src/Package/PackageMetadataResolver.php

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
use Composer\Pcre\Preg;
2727
use Composer\Repository\Vcs\VcsDriverInterface;
2828
use Doctrine\ORM\EntityManagerInterface;
29-
use Symfony\Component\Messenger\Envelope;
3029
use Symfony\Component\Messenger\MessageBusInterface;
3130
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
3231
use Symfony\Component\Messenger\Stamp\TransportNamesStamp;
@@ -178,10 +177,11 @@ private function resolveVcsRepository(Package $package): void
178177
*/
179178
private function updatePackage(Package $package, array $composerPackages, ?VcsDriverInterface $driver = null): void
180179
{
181-
$existingVersions = $this->versionRepository->getVersionMetadataForUpdate($package);
182-
/** @var ?CompletePackageInterface $primaryVersion Version to use as the package info source */
180+
$existingVersionMetadata = $this->versionRepository->getVersionMetadataForUpdate($package);
181+
/** @var ?Version $primaryVersion Version to use as the package info source */
183182
$primaryVersion = null;
184183

184+
// Every Composer package is a separate package version
185185
foreach ($composerPackages as $composerPackage) {
186186
if ($composerPackage instanceof AliasPackage) {
187187
continue;
@@ -204,7 +204,7 @@ private function updatePackage(Package $package, array $composerPackages, ?VcsDr
204204
$primaryVersion = $version;
205205
}
206206

207-
unset($existingVersions[$versionName]);
207+
unset($existingVersionMetadata[$versionName]);
208208
}
209209

210210
if ($primaryVersion) {
@@ -213,17 +213,16 @@ private function updatePackage(Package $package, array $composerPackages, ?VcsDr
213213
$package->setRepositoryUrl($primaryVersion->getSourceUrl());
214214
}
215215

216-
$message = Envelope::wrap(new UpdatePackageLinks($package->getId(), $primaryVersion->getNormalizedVersion()))
217-
->with(new DispatchAfterCurrentBusStamp())
218-
->with(new TransportNamesStamp('async'));
219-
$this->messenger->dispatch($message);
216+
$this->messenger->dispatch(new UpdatePackageLinks($package->getId(), $primaryVersion->getNormalizedVersion()), [
217+
new DispatchAfterCurrentBusStamp(),
218+
new TransportNamesStamp('async'),
219+
]);
220220
}
221221

222222
// Remove outdated versions
223-
foreach ($existingVersions as $version) {
224-
$versionEntity = $this->versionRepository->find($version['id']);
225-
226-
$this->entityManager->remove($versionEntity);
223+
foreach ($existingVersionMetadata as $versionMetadata) {
224+
$version = $this->entityManager->getReference(Version::class, $versionMetadata['id']);
225+
$this->entityManager->remove($version);
227226
}
228227

229228
$package->setUpdatedAt(new \DateTimeImmutable());

0 commit comments

Comments
 (0)