Skip to content

Commit eef2b4a

Browse files
committed
fix chunks collection purging for GridFS documents (#593)
1 parent 06e31d0 commit eef2b4a

File tree

6 files changed

+227
-89
lines changed

6 files changed

+227
-89
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -198,18 +198,6 @@ parameters:
198198
count: 2
199199
path: tests/Common/DataFixtures/ProxyReferenceRepositoryTest.php
200200

201-
-
202-
message: '#^Call to an undefined method Doctrine\\ODM\\MongoDB\\DocumentManager\:\:getConnection\(\)\.$#'
203-
identifier: method.notFound
204-
count: 1
205-
path: tests/Common/DataFixtures/Purger/MongoDBPurgerTest.php
206-
207-
-
208-
message: '#^Call to function method_exists\(\) with Doctrine\\ODM\\MongoDB\\DocumentManager and ''getClient'' will always evaluate to true\.$#'
209-
identifier: function.alreadyNarrowedType
210-
count: 1
211-
path: tests/Common/DataFixtures/Purger/MongoDBPurgerTest.php
212-
213201
-
214202
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\ORM\\\\ORMSetup'' and ''createAttributeMeta…'' will always evaluate to true\.$#'
215203
identifier: function.alreadyNarrowedType

src/Purger/MongoDBPurger.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ private function purgeWithDelete(): void
6969
continue;
7070
}
7171

72+
if ($metadata->isFile) {
73+
$this->dm->getDocumentBucket($metadata->name)->getFilesCollection()->deleteMany([]);
74+
$this->dm->getDocumentBucket($metadata->name)->getChunksCollection()->deleteMany([]);
75+
continue;
76+
}
77+
7278
$this->dm->getDocumentCollection($metadata->name)->deleteMany([]);
7379
}
7480
}
@@ -81,6 +87,12 @@ private function purgeWithDrop(): void
8187
continue;
8288
}
8389

90+
if ($metadata->isFile) {
91+
$this->dm->getDocumentBucket($metadata->name)->getFilesCollection()->drop([]);
92+
$this->dm->getDocumentBucket($metadata->name)->getChunksCollection()->drop([]);
93+
continue;
94+
}
95+
8496
$this->dm->getDocumentCollection($metadata->name)->drop();
8597
}
8698

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Common\DataFixtures\Purger;
6+
7+
use Doctrine\Common\DataFixtures\Purger\MongoDBPurger;
8+
use Doctrine\ODM\MongoDB\Configuration;
9+
use Doctrine\ODM\MongoDB\DocumentManager;
10+
use Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver;
11+
use Doctrine\Tests\Common\DataFixtures\BaseTestCase;
12+
use MongoCollection;
13+
use MongoDB\Collection;
14+
use MongoDB\Driver\Exception\ConnectionTimeoutException;
15+
16+
use function class_exists;
17+
use function dirname;
18+
use function method_exists;
19+
20+
use const PHP_VERSION_ID;
21+
22+
abstract class BaseMongoDBPurgerTestCase extends BaseTestCase
23+
{
24+
protected function getDocumentManager(): DocumentManager
25+
{
26+
if (! class_exists(DocumentManager::class)) {
27+
$this->markTestSkipped('Missing doctrine/mongodb-odm');
28+
}
29+
30+
$root = dirname(__DIR__, 5);
31+
32+
$config = new Configuration();
33+
$config->setProxyDir($root . '/generate/proxies');
34+
$config->setProxyNamespace('Proxies');
35+
$config->setHydratorDir($root . '/generate/hydrators');
36+
$config->setHydratorNamespace('Hydrators');
37+
$config->setMetadataDriverImpl(AttributeDriver::create(dirname(__DIR__) . '/TestDocument'));
38+
39+
/** @phpstan-ignore function.alreadyNarrowedType (that method exists only since ODM 2.14.0) */
40+
if (PHP_VERSION_ID >= 80400 && method_exists($config, 'setUseNativeLazyObject')) {
41+
$config->setUseNativeLazyObject(true);
42+
}
43+
44+
$dm = DocumentManager::create(null, $config);
45+
46+
$this->skipIfMongoDBUnavailable($dm);
47+
48+
return $dm;
49+
}
50+
51+
protected function getPurger(): MongoDBPurger
52+
{
53+
return new MongoDBPurger($this->getDocumentManager());
54+
}
55+
56+
protected function assertIndexCount(int $expectedCount, Collection|MongoCollection $collection): void
57+
{
58+
if ($collection instanceof Collection) {
59+
$indexes = $collection->listIndexes();
60+
} else {
61+
$indexes = $collection->getIndexInfo();
62+
}
63+
64+
$this->assertCount($expectedCount, $indexes);
65+
}
66+
67+
private function skipIfMongoDBUnavailable(DocumentManager $documentManager): void
68+
{
69+
/** @phpstan-ignore function.alreadyNarrowedType (that method exists only since ODM 2.14.0) */
70+
if (method_exists($documentManager, 'getClient')) {
71+
try {
72+
$documentManager->getClient()->selectDatabase('admin')->command(['ping' => 1]);
73+
} catch (ConnectionTimeoutException) {
74+
$this->markTestSkipped('Unable to connect to MongoDB');
75+
}
76+
77+
return;
78+
}
79+
80+
/** @phpstan-ignore method.notFound (connection methods exist in older ODM versions) */
81+
if ($documentManager->getConnection()->connect()) {
82+
return;
83+
}
84+
85+
$this->markTestSkipped('Unable to connect to MongoDB');
86+
}
87+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Common\DataFixtures\Purger;
6+
7+
use Doctrine\Common\DataFixtures\Purger\MongoDBPurgeMode;
8+
use Doctrine\ODM\MongoDB\Repository\GridFSRepository;
9+
use Doctrine\Tests\Common\DataFixtures\TestDocument\Image;
10+
11+
use function base64_encode;
12+
use function fclose;
13+
use function fopen;
14+
15+
class MongoDBGridFSPurgerTest extends BaseMongoDBPurgerTestCase
16+
{
17+
public const TEST_DOCUMENT_IMAGE = Image::class;
18+
19+
public function testPurgeWithDelete(): void
20+
{
21+
$purger = $this->getPurger();
22+
$dm = $purger->getObjectManager();
23+
$purger->setPurgeMode(MongoDBPurgeMode::Delete);
24+
25+
self::assertSame(MongoDBPurgeMode::Delete, $purger->getPurgeMode());
26+
27+
$filesCollection = $dm->getDocumentBucket(self::TEST_DOCUMENT_IMAGE)->getFilesCollection();
28+
$chunksCollection = $dm->getDocumentBucket(self::TEST_DOCUMENT_IMAGE)->getChunksCollection();
29+
30+
$filesCollection->drop();
31+
$chunksCollection->drop();
32+
33+
$this->assertIndexCount(0, $filesCollection);
34+
$this->assertIndexCount(0, $chunksCollection);
35+
36+
$purger->purge();
37+
38+
// Collection isn't created yet, so no indices should be present
39+
$this->assertIndexCount(0, $filesCollection);
40+
$this->assertIndexCount(0, $chunksCollection);
41+
42+
// Create a data stream for GridFS
43+
$stream = fopen('data://text/plain;base64,' . base64_encode('test content'), 'r');
44+
self::assertIsResource($stream);
45+
46+
/** @var GridFSRepository<Image> $repository */
47+
$repository = $dm->getRepository(self::TEST_DOCUMENT_IMAGE);
48+
$repository->uploadFromStream('test.jpg', $stream);
49+
50+
fclose($stream);
51+
52+
$this->assertSame(1, $filesCollection->countDocuments());
53+
$this->assertSame(1, $chunksCollection->countDocuments());
54+
55+
$this->assertIndexCount(2, $filesCollection);
56+
$this->assertIndexCount(2, $chunksCollection);
57+
58+
$purger->purge();
59+
60+
// After purging, the collection should still exist, but the file should be deleted
61+
$this->assertIndexCount(2, $filesCollection);
62+
$this->assertIndexCount(2, $chunksCollection);
63+
$this->assertSame(0, $filesCollection->countDocuments());
64+
$this->assertSame(0, $chunksCollection->countDocuments());
65+
}
66+
67+
public function testPurgeKeepsIndices(): void
68+
{
69+
$purger = $this->getPurger();
70+
$dm = $purger->getObjectManager();
71+
72+
$filesCollection = $dm->getDocumentBucket(self::TEST_DOCUMENT_IMAGE)->getFilesCollection();
73+
$chunksCollection = $dm->getDocumentBucket(self::TEST_DOCUMENT_IMAGE)->getChunksCollection();
74+
75+
$filesCollection->drop();
76+
$chunksCollection->drop();
77+
78+
$this->assertIndexCount(0, $filesCollection);
79+
$this->assertIndexCount(0, $chunksCollection);
80+
81+
// Create a data stream for GridFS
82+
$stream = fopen('data://text/plain;base64,' . base64_encode('test content'), 'r');
83+
self::assertIsResource($stream);
84+
85+
// Upload file using repository method
86+
/** @var GridFSRepository<Image> $repository */
87+
$repository = $dm->getRepository(self::TEST_DOCUMENT_IMAGE);
88+
$repository->uploadFromStream('test.jpg', $stream);
89+
90+
fclose($stream);
91+
92+
$dm->getSchemaManager()->ensureDocumentIndexes(self::TEST_DOCUMENT_IMAGE);
93+
94+
$this->assertIndexCount(2, $filesCollection);
95+
$this->assertIndexCount(2, $chunksCollection);
96+
97+
$this->assertSame(1, $filesCollection->countDocuments());
98+
$this->assertSame(1, $chunksCollection->countDocuments());
99+
100+
$purger->purge();
101+
102+
$this->assertIndexCount(2, $filesCollection);
103+
$this->assertIndexCount(2, $chunksCollection);
104+
105+
$this->assertSame(0, $filesCollection->countDocuments());
106+
$this->assertSame(0, $chunksCollection->countDocuments());
107+
}
108+
}

tests/Common/DataFixtures/Purger/MongoDBPurgerTest.php

Lines changed: 1 addition & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,12 @@
55
namespace Doctrine\Tests\Common\DataFixtures\Purger;
66

77
use Doctrine\Common\DataFixtures\Purger\MongoDBPurgeMode;
8-
use Doctrine\Common\DataFixtures\Purger\MongoDBPurger;
9-
use Doctrine\ODM\MongoDB\Configuration;
10-
use Doctrine\ODM\MongoDB\DocumentManager;
11-
use Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver;
12-
use Doctrine\Tests\Common\DataFixtures\BaseTestCase;
138
use Doctrine\Tests\Common\DataFixtures\TestDocument\Role;
14-
use MongoCollection;
15-
use MongoDB\Collection;
16-
use MongoDB\Driver\Exception\ConnectionTimeoutException;
179

18-
use function class_exists;
19-
use function dirname;
20-
use function method_exists;
21-
22-
use const PHP_VERSION_ID;
23-
24-
class MongoDBPurgerTest extends BaseTestCase
10+
class MongoDBPurgerTest extends BaseMongoDBPurgerTestCase
2511
{
2612
public const TEST_DOCUMENT_ROLE = Role::class;
2713

28-
private function getDocumentManager(): DocumentManager
29-
{
30-
if (! class_exists(DocumentManager::class)) {
31-
$this->markTestSkipped('Missing doctrine/mongodb-odm');
32-
}
33-
34-
$root = dirname(__DIR__, 5);
35-
36-
$config = new Configuration();
37-
$config->setProxyDir($root . '/generate/proxies');
38-
$config->setProxyNamespace('Proxies');
39-
$config->setHydratorDir($root . '/generate/hydrators');
40-
$config->setHydratorNamespace('Hydrators');
41-
$config->setMetadataDriverImpl(AttributeDriver::create(dirname(__DIR__) . '/TestDocument'));
42-
43-
/** @phpstan-ignore function.alreadyNarrowedType (that method exists only since ODM 2.14.0) */
44-
if (PHP_VERSION_ID >= 80400 && method_exists($config, 'setUseNativeLazyObject')) {
45-
$config->setUseNativeLazyObject(true);
46-
}
47-
48-
$dm = DocumentManager::create(null, $config);
49-
50-
$this->skipIfMongoDBUnavailable($dm);
51-
52-
return $dm;
53-
}
54-
55-
private function getPurger(): MongoDBPurger
56-
{
57-
return new MongoDBPurger($this->getDocumentManager());
58-
}
59-
6014
public function testPurgeWithDelete(): void
6115
{
6216
$purger = $this->getPurger();
@@ -111,34 +65,4 @@ public function testPurgeKeepsIndices(): void
11165
$purger->purge();
11266
$this->assertIndexCount(2, $collection);
11367
}
114-
115-
private function assertIndexCount(int $expectedCount, Collection|MongoCollection $collection): void
116-
{
117-
if ($collection instanceof Collection) {
118-
$indexes = $collection->listIndexes();
119-
} else {
120-
$indexes = $collection->getIndexInfo();
121-
}
122-
123-
$this->assertCount($expectedCount, $indexes);
124-
}
125-
126-
private function skipIfMongoDBUnavailable(DocumentManager $documentManager): void
127-
{
128-
if (method_exists($documentManager, 'getClient')) {
129-
try {
130-
$documentManager->getClient()->selectDatabase('admin')->command(['ping' => 1]);
131-
} catch (ConnectionTimeoutException) {
132-
$this->markTestSkipped('Unable to connect to MongoDB');
133-
}
134-
135-
return;
136-
}
137-
138-
if ($documentManager->getConnection()->connect()) {
139-
return;
140-
}
141-
142-
$this->markTestSkipped('Unable to connect to MongoDB');
143-
}
14468
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Common\DataFixtures\TestDocument;
6+
7+
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
8+
9+
#[ODM\File]
10+
class Image
11+
{
12+
#[ODM\Id]
13+
private string|null $id = null; // @phpstan-ignore property.unusedType (id is set by MongoDB ODM)
14+
15+
public function getId(): string|null
16+
{
17+
return $this->id;
18+
}
19+
}

0 commit comments

Comments
 (0)