diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index ab64f5200..d7b10696b 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -16,6 +16,7 @@ jobs: runs-on: "ubuntu-20.04" strategy: + fail-fast: false matrix: php-version: - "8.1" @@ -39,7 +40,7 @@ jobs: - dependencies: "lowest" php-version: "8.1" mongodb-version: "5.0" - driver-version: "1.17.0" + driver-version: "1.20.0" topology: "server" symfony-version: "stable" # Test with highest dependencies @@ -56,6 +57,13 @@ jobs: driver-version: "stable" dependencies: "highest" symfony-version: "stable" + # Test with ext-2.0 + - topology: "server" + php-version: "8.2" + mongodb-version: "7.0" + driver-version: "mongodb/mongo-php-driver@v2.x" + dependencies: "highest" + symfony-version: "7" # Test with a 5.0 sharded cluster # Currently disabled due to a bug where MongoDB reports "sharding status unknown" # - topology: "sharded_cluster" @@ -117,6 +125,11 @@ jobs: dependency-versions: "${{ matrix.dependencies }}" composer-options: "--prefer-dist" + - name: "Install latest Python version" + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - id: setup-mongodb uses: mongodb-labs/drivers-evergreen-tools@master with: diff --git a/composer.json b/composer.json index 1bdaf6c08..4dc0ba384 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ ], "require": { "php": "^8.1", - "ext-mongodb": "^1.17", + "ext-mongodb": "^1.20 || ^2.0", "doctrine/cache": "^1.11 || ^2.0", "doctrine/collections": "^1.5 || ^2.0", "doctrine/event-manager": "^1.0 || ^2.0", @@ -30,7 +30,7 @@ "doctrine/persistence": "^3.2", "friendsofphp/proxy-manager-lts": "^1.0", "jean85/pretty-package-versions": "^1.3.0 || ^2.0.1", - "mongodb/mongodb": "^1.17.0", + "mongodb/mongodb": "^1.20 || ^2.0@dev", "psr/cache": "^1.0 || ^2.0 || ^3.0", "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/deprecation-contracts": "^2.2 || ^3.0", diff --git a/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php b/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php index b8d3a6df4..09fc5f649 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php @@ -16,16 +16,22 @@ use Documents\Project; use Documents\SubProject; use Documents\User; +use Exception; use InvalidArgumentException; use LogicException; +use MongoDB\BSON\Int64; use MongoDB\BSON\ObjectId; use MongoDB\Collection; +use MongoDB\Driver\CursorId; +use MongoDB\Driver\CursorInterface; use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\Server; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use Traversable; use function array_keys; +use function class_exists; use function iterator_to_array; use const DOCTRINE_MONGODB_DATABASE; @@ -496,7 +502,7 @@ public function testCountOptionInheritance(): void public function testFindWithHint(): void { - $cursor = $this->createMock(Traversable::class); + $cursor = $this->createCursorMock(); $collection = $this->getMockCollection(); $collection->expects($this->once()) @@ -523,7 +529,7 @@ public function testFindOptionInheritance(): void $nearest = new ReadPreference('nearest'); $secondaryPreferred = new ReadPreference('secondaryPreferred'); - $cursor = $this->createMock(Traversable::class); + $cursor = $this->createCursorMock(); $collection = $this->getMockCollection(); $collection->expects($this->once()) @@ -552,7 +558,41 @@ public function testFindOptionInheritance(): void public function testNonRewindable(): void { - $cursor = new ArrayIterator(['foo']); + $cursor = new class ([['foo']]) extends ArrayIterator implements CursorInterface { + public function getId(): Int64 + { + return new Int64(0); + } + + public function getServer(): Server + { + throw new Exception('Not implemented'); + } + + public function isDead(): bool + { + return false; + } + + public function setTypeMap(array $typemap): void + { + } + + public function toArray(): array + { + return iterator_to_array($this); + } + + public function current(): array|null + { + return parent::current(); + } + + public function key(): int|null + { + return parent::key(); + } + }; $collection = $this->getMockCollection(); $collection->expects($this->once()) @@ -582,6 +622,16 @@ private function getMockCollection() { return $this->createMock(Collection::class); } + + private function createCursorMock(): CursorInterface|Traversable + { + return $this->createMock( + // Use the cursorID class to differentiate between 1.x and 2.x + class_exists(CursorId::class) + ? Traversable::class + : CursorInterface::class, + ); + } } #[ODM\Document(collection: 'people')] diff --git a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php index e3f117444..2e185dfc3 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php @@ -25,6 +25,7 @@ use Documents\Tournament\Tournament; use Documents\UserName; use InvalidArgumentException; +use Iterator; use MongoDB\BSON\Document; use MongoDB\Client; use MongoDB\Collection; @@ -33,7 +34,10 @@ use MongoDB\Driver\WriteConcern; use MongoDB\GridFS\Bucket; use MongoDB\Model\CollectionInfo; +use MongoDB\Model\CollectionInfoCommandIterator; +use MongoDB\Model\CollectionInfoIterator; use MongoDB\Model\IndexInfo; +use MongoDB\Model\IndexInfoIterator; use MongoDB\Model\IndexInfoIteratorIterator; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Constraint\ArrayHasKey; @@ -46,6 +50,7 @@ use function array_map; use function assert; use function in_array; +use function interface_exists; /** * @phpstan-import-type IndexMapping from ClassMetadata @@ -197,7 +202,7 @@ public function testEnsureIndexes(array $expectedWriteOptions, ?int $maxTimeMs, $filesCollection ->method('listIndexes') - ->willReturn([]); + ->willReturn($this->createIndexIterator()); $filesCollection ->expects($this->atLeastOnce()) ->method('createIndex') @@ -205,7 +210,7 @@ public function testEnsureIndexes(array $expectedWriteOptions, ?int $maxTimeMs, $chunksCollection ->method('listIndexes') - ->willReturn([]); + ->willReturn($this->createIndexIterator()); $chunksCollection ->expects($this->atLeastOnce()) ->method('createIndex') @@ -253,7 +258,7 @@ public function testEnsureDocumentIndexesForGridFSFile(array $expectedWriteOptio if ($class === $fileBucket) { $filesCollection ->method('listIndexes') - ->willReturn([]); + ->willReturn($this->createIndexIterator()); $filesCollection ->expects($this->once()) ->method('createIndex') @@ -261,7 +266,7 @@ public function testEnsureDocumentIndexesForGridFSFile(array $expectedWriteOptio $chunksCollection ->method('listIndexes') - ->willReturn([]); + ->willReturn($this->createIndexIterator()); $chunksCollection ->expects($this->once()) ->method('createIndex') @@ -298,7 +303,7 @@ public function testUpdateDocumentIndexesShouldCreateMappedIndexes(array $expect $collection ->expects($this->once()) ->method('listIndexes') - ->willReturn(new IndexInfoIteratorIterator(new ArrayIterator([]))); + ->willReturn($this->createIndexIterator()); $collection ->expects($this->once()) ->method('createIndex') @@ -328,7 +333,7 @@ public function testUpdateDocumentIndexesShouldDeleteUnmappedIndexesBeforeCreati $collection ->expects($this->once()) ->method('listIndexes') - ->willReturn(new IndexInfoIteratorIterator(new ArrayIterator($indexes))); + ->willReturn($this->createIndexIterator($indexes)); $collection ->expects($this->once()) ->method('createIndex') @@ -1336,10 +1341,10 @@ private function getMockDatabase() $db->method('listCollections')->willReturnCallback(function () { $collections = []; foreach ($this->documentCollections as $collectionName => $collection) { - $collections[] = new CollectionInfo(['name' => $collectionName]); + $collections[] = ['name' => $collectionName]; } - return $collections; + return $this->createCollectionIterator($collections); }); return $db; @@ -1372,4 +1377,28 @@ private function createSearchIndexCommandExceptionForOlderServers(): CommandExce { return new CommandException('Unrecognized pipeline stage name: \'$listSearchIndexes\'', 40234); } + + private function createIndexIterator(array $indexes = []): Iterator + { + if (interface_exists(IndexInfoIterator::class)) { + return new IndexInfoIteratorIterator(new ArrayIterator($indexes)); + } + + return new ArrayIterator(array_map( + static fn (array $indexInfo) => new IndexInfo($indexInfo), + $indexes, + )); + } + + private function createCollectionIterator(array $collections = []): Iterator + { + if (interface_exists(CollectionInfoIterator::class)) { + return new CollectionInfoCommandIterator(new ArrayIterator($collections)); + } + + return new ArrayIterator(array_map( + static fn (array $collectionInfo) => new CollectionInfo($collectionInfo), + $collections, + )); + } }