Skip to content

Commit ffa1add

Browse files
committed
Delete distribution files when removing related entities from the database
1 parent 3494d20 commit ffa1add

10 files changed

Lines changed: 371 additions & 2 deletions

File tree

‎ARCHITECTURE.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ src/
3434
├── DependencyInjection/ # Symfony dependency injection configuration
3535
├── Doctrine/
3636
│ ├── Entity/ # Doctrine ORM entities
37+
│ ├── EventListener/ # Doctrine entity lifecycle listeners
3738
│ ├── Repository/ # Doctrine repositories
3839
│ ├── Type/ # Custom Doctrine types
3940
│ └── DataFixtures/ # Database fixtures
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CodedMonkey\Dirigent\Doctrine\EventListener;
6+
7+
use CodedMonkey\Dirigent\Doctrine\Entity\Distribution;
8+
use CodedMonkey\Dirigent\Package\PackageDistributionResolver;
9+
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
10+
use Doctrine\ORM\Events;
11+
12+
#[AsEntityListener(Events::preRemove, entity: Distribution::class)]
13+
readonly class DistributionListener
14+
{
15+
public function __construct(
16+
private PackageDistributionResolver $distributionResolver,
17+
) {
18+
}
19+
20+
public function preRemove(Distribution $distribution): void
21+
{
22+
$this->distributionResolver->remove($distribution);
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CodedMonkey\Dirigent\Doctrine\EventListener;
6+
7+
use CodedMonkey\Dirigent\Doctrine\Entity\Metadata;
8+
use CodedMonkey\Dirigent\Package\PackageDistributionResolver;
9+
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
10+
use Doctrine\ORM\Events;
11+
12+
#[AsEntityListener(Events::preRemove, entity: Metadata::class)]
13+
readonly class MetadataListener
14+
{
15+
public function __construct(
16+
private PackageDistributionResolver $distributionResolver,
17+
) {
18+
}
19+
20+
public function preRemove(Metadata $metadata): void
21+
{
22+
$this->distributionResolver->removeMetadata($metadata);
23+
}
24+
}

‎src/Doctrine/EventListener/PackageListener.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use CodedMonkey\Dirigent\Doctrine\Entity\Package;
88
use CodedMonkey\Dirigent\Doctrine\Repository\PackageRepository;
99
use CodedMonkey\Dirigent\Message\RemovePackageProvider;
10+
use CodedMonkey\Dirigent\Package\PackageDistributionResolver;
1011
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
1112
use Doctrine\ORM\Event\PreRemoveEventArgs;
1213
use Doctrine\ORM\Events;
@@ -17,6 +18,7 @@
1718
{
1819
public function __construct(
1920
private MessageBusInterface $messenger,
21+
private PackageDistributionResolver $distributionResolver,
2022
) {
2123
}
2224

@@ -28,6 +30,9 @@ public function preRemove(Package $package, PreRemoveEventArgs $event): void
2830
// Delete existing package links
2931
$repository->deletePackageLinks($package);
3032

33+
// Delete mirrored package distributions
34+
$this->distributionResolver->removePackage($package);
35+
3136
// Remove package provider
3237
$this->messenger->dispatch(new RemovePackageProvider($package->getId()));
3338
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CodedMonkey\Dirigent\Doctrine\EventListener;
6+
7+
use CodedMonkey\Dirigent\Doctrine\Entity\Version;
8+
use CodedMonkey\Dirigent\Package\PackageDistributionResolver;
9+
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
10+
use Doctrine\ORM\Events;
11+
12+
#[AsEntityListener(Events::preRemove, entity: Version::class)]
13+
readonly class VersionListener
14+
{
15+
public function __construct(
16+
private PackageDistributionResolver $distributionResolver,
17+
) {
18+
}
19+
20+
public function preRemove(Version $version): void
21+
{
22+
$this->distributionResolver->removeVersion($version);
23+
}
24+
}

‎src/Doctrine/Repository/DistributionRepository.php‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
use CodedMonkey\Dirigent\Doctrine\Entity\Distribution;
88
use CodedMonkey\Dirigent\Doctrine\Entity\Metadata;
9+
use CodedMonkey\Dirigent\Doctrine\Entity\Package;
10+
use CodedMonkey\Dirigent\Doctrine\Entity\Version;
911
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
1012
use Doctrine\Persistence\ManagerRegistry;
1113

@@ -49,4 +51,42 @@ public function findOneByMetadataAndType(Metadata $metadata, string $type): ?Dis
4951
'type' => $type,
5052
]);
5153
}
54+
55+
/**
56+
* @return Distribution[]
57+
*/
58+
public function findByMetadata(Metadata $metadata): array
59+
{
60+
return $this->createQueryBuilder('distribution')
61+
->where('distribution.metadata = :metadata')
62+
->setParameter('metadata', $metadata)
63+
->getQuery()
64+
->getResult();
65+
}
66+
67+
/**
68+
* @return Distribution[]
69+
*/
70+
public function findByPackage(Package $package): array
71+
{
72+
return $this->createQueryBuilder('distribution')
73+
->join('distribution.metadata', 'metadata')
74+
->where('metadata.package = :package')
75+
->setParameter('package', $package)
76+
->getQuery()
77+
->getResult();
78+
}
79+
80+
/**
81+
* @return Distribution[]
82+
*/
83+
public function findByVersion(Version $version): array
84+
{
85+
return $this->createQueryBuilder('distribution')
86+
->join('distribution.metadata', 'metadata')
87+
->where('metadata.version = :version')
88+
->setParameter('version', $version)
89+
->getQuery()
90+
->getResult();
91+
}
5292
}

‎src/Package/PackageDistributionResolver.php‎

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
use CodedMonkey\Dirigent\Composer\ComposerClient;
88
use CodedMonkey\Dirigent\Doctrine\Entity\Distribution;
99
use CodedMonkey\Dirigent\Doctrine\Entity\Metadata;
10+
use CodedMonkey\Dirigent\Doctrine\Entity\Package;
11+
use CodedMonkey\Dirigent\Doctrine\Entity\Version;
1012
use CodedMonkey\Dirigent\Doctrine\Repository\DistributionRepository;
1113
use CodedMonkey\Dirigent\Message\ResolveDistribution;
1214
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1315
use Symfony\Component\Filesystem\Filesystem;
1416
use Symfony\Component\Lock\LockFactory;
17+
use Symfony\Component\Lock\SharedLockInterface;
1518
use Symfony\Component\Messenger\MessageBusInterface;
1619
use Symfony\Component\Messenger\Stamp\TransportNamesStamp;
1720

@@ -49,6 +52,51 @@ public function path(Metadata $metadata, string $type): string
4952
return "{$this->storagePath}/{$packageName}/{$versionName}-r{$revision}-{$reference}.{$type}";
5053
}
5154

55+
public function remove(Distribution $distribution): void
56+
{
57+
$path = $this->path($distribution->getMetadata(), $distribution->getReference());
58+
$lock = $this->createDistributionLock($path);
59+
60+
try {
61+
$this->filesystem->remove($path);
62+
63+
// Remove the package directory if it's empty
64+
$packageDirectory = dirname($path);
65+
if (is_dir($packageDirectory) && !new \FilesystemIterator($packageDirectory)->valid()) {
66+
$this->filesystem->remove($packageDirectory);
67+
}
68+
69+
// Remove the vendor directory if it's empty
70+
$vendorDirectory = dirname($packageDirectory);
71+
if (is_dir($vendorDirectory) && !new \FilesystemIterator($vendorDirectory)->valid()) {
72+
$this->filesystem->remove($vendorDirectory);
73+
}
74+
} finally {
75+
$lock->release();
76+
}
77+
}
78+
79+
public function removeMetadata(Metadata $metadata): void
80+
{
81+
foreach ($this->distributionRepository->findByMetadata($metadata) as $distribution) {
82+
$this->remove($distribution);
83+
}
84+
}
85+
86+
public function removePackage(Package $package): void
87+
{
88+
foreach ($this->distributionRepository->findByPackage($package) as $distribution) {
89+
$this->remove($distribution);
90+
}
91+
}
92+
93+
public function removeVersion(Version $version): void
94+
{
95+
foreach ($this->distributionRepository->findByVersion($version) as $distribution) {
96+
$this->remove($distribution);
97+
}
98+
}
99+
52100
public function resolve(Metadata $metadata, string $type, bool $async): bool
53101
{
54102
$path = $this->path($metadata, $type);
@@ -78,8 +126,7 @@ public function resolve(Metadata $metadata, string $type, bool $async): bool
78126
$distributionUrl = $metadata->getDistributionUrl();
79127
$path = $this->path($metadata, $type);
80128

81-
$lock = $this->lockFactory->createLock('distribution.' . hash('sha256', $path), ttl: null);
82-
$lock->acquire(blocking: true);
129+
$lock = $this->createDistributionLock($path);
83130

84131
try {
85132
if ($this->fileExists($path)) {
@@ -106,6 +153,14 @@ public function resolve(Metadata $metadata, string $type, bool $async): bool
106153
}
107154
}
108155

156+
private function createDistributionLock(string $path): SharedLockInterface
157+
{
158+
$lock = $this->lockFactory->createLock('distribution.' . hash('sha256', $path), ttl: null);
159+
$lock->acquire(blocking: true);
160+
161+
return $lock;
162+
}
163+
109164
private function fileExists(string $path): bool
110165
{
111166
return $this->filesystem->exists($path);
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CodedMonkey\Dirigent\Tests\FunctionalTests\Doctrine\Entity;
6+
7+
use CodedMonkey\Dirigent\Doctrine\Entity\Distribution;
8+
use CodedMonkey\Dirigent\Doctrine\Entity\Metadata;
9+
use CodedMonkey\Dirigent\Package\PackageDistributionResolver;
10+
use CodedMonkey\Dirigent\Tests\Helper\EntityManagerTestTrait;
11+
use CodedMonkey\Dirigent\Tests\Helper\KernelTestCaseTrait;
12+
use CodedMonkey\Dirigent\Tests\Helper\MockEntityFactoryTrait;
13+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
14+
use Symfony\Component\Filesystem\Filesystem;
15+
16+
class DistributionDeletionTest extends KernelTestCase
17+
{
18+
use EntityManagerTestTrait;
19+
use KernelTestCaseTrait;
20+
use MockEntityFactoryTrait;
21+
22+
public function testDeletingDistributionDeletesDistributionFile(): void
23+
{
24+
[$package, $version, $metadata] = $this->createMockPackageWithMetadata();
25+
[$distribution, $path] = $this->createDistributionFile($metadata);
26+
$this->persistEntities($package, $version, $metadata, $distribution);
27+
28+
$this->removeEntities($distribution);
29+
30+
self::assertFileDoesNotExist($path);
31+
}
32+
33+
public function testDeletingMetadataDeletesDistributionFile(): void
34+
{
35+
[$package, $version, $metadata] = $this->createMockPackageWithMetadata();
36+
[$distribution, $path] = $this->createDistributionFile($metadata);
37+
38+
// Metadata can't be deleted if it's the current metadata of a version, so we replace it
39+
$currentMetadata = $this->createMockMetadata($version);
40+
$version->setCurrentMetadata($currentMetadata);
41+
42+
$this->persistEntities($package, $version, $metadata, $currentMetadata, $distribution);
43+
44+
$this->removeEntities($metadata);
45+
46+
self::assertFileDoesNotExist($path);
47+
}
48+
49+
public function testDeletingPackageDeletesDistributionFile(): void
50+
{
51+
[$package, $version, $metadata] = $this->createMockPackageWithMetadata();
52+
[$distribution, $path] = $this->createDistributionFile($metadata);
53+
$this->persistEntities($package, $version, $metadata, $distribution);
54+
55+
$this->removeEntities($package);
56+
57+
self::assertFileDoesNotExist($path);
58+
}
59+
60+
public function testDeletingVersionDeletesDistributionFile(): void
61+
{
62+
[$package, $version, $metadata] = $this->createMockPackageWithMetadata();
63+
[$distribution, $path] = $this->createDistributionFile($metadata);
64+
$this->persistEntities($package, $version, $metadata, $distribution);
65+
66+
$this->removeEntities($version);
67+
68+
self::assertFileDoesNotExist($path);
69+
}
70+
71+
/**
72+
* @return array{Distribution, string}
73+
*/
74+
private function createDistributionFile(Metadata $metadata): array
75+
{
76+
$distribution = new Distribution($metadata, 'zip');
77+
$distribution->setResolvedAt();
78+
79+
$path = $this->getService(PackageDistributionResolver::class)->path($metadata, $distribution->getReference());
80+
new Filesystem()->dumpFile($path, 'distribution');
81+
82+
return [$distribution, $path];
83+
}
84+
}

‎tests/Helper/EntityManagerTestTrait.php‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ protected function persistEntities(...$entities): void
4242
$entityManager->flush();
4343
}
4444

45+
protected function removeEntities(...$entities): void
46+
{
47+
$entityManager = $this->getService(EntityManagerInterface::class);
48+
49+
foreach ($entities as $entity) {
50+
$entityManager->remove($entity);
51+
}
52+
53+
$entityManager->flush();
54+
}
55+
4556
protected function clearEntities(): void
4657
{
4758
$this->getService(EntityManagerInterface::class)->clear();

0 commit comments

Comments
 (0)