From 012503193e592287a469b2d7df49c435e607af27 Mon Sep 17 00:00:00 2001 From: Toni Vega <1808267+tonivega@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:46:04 +0200 Subject: [PATCH 01/28] Replaced a too strict assertion in DatabaseSessionHandler that prevented to use the sessions at all with a more simple sanity check (#3366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set zend.assertions=1 --------- Co-authored-by: Toni Vega Co-authored-by: Jérôme Tamarelle --- .github/workflows/build-ci-atlas.yml | 2 +- .github/workflows/build-ci.yml | 2 +- phpunit.xml.dist | 2 ++ src/Session/MongoDbSessionHandler.php | 8 +++++--- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-ci-atlas.yml b/.github/workflows/build-ci-atlas.yml index c699d2023..46f30150d 100644 --- a/.github/workflows/build-ci-atlas.yml +++ b/.github/workflows/build-ci-atlas.yml @@ -91,4 +91,4 @@ jobs: - name: "Run tests" run: | export MONGODB_URI="mongodb://127.0.0.1:27017/?directConnection=true" - ./vendor/bin/phpunit --coverage-clover coverage.xml --group atlas-search + php -d zend.assertions=1 ./vendor/bin/phpunit --coverage-clover coverage.xml --group atlas-search diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index 6fec7bb28..bbc8b53d1 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -118,4 +118,4 @@ jobs: - name: "Run tests" run: | export MONGODB_URI="mongodb://127.0.0.1:27017/?replicaSet=rs" - ./vendor/bin/phpunit --coverage-clover coverage.xml --exclude-group atlas-search + php -d zend.assertions=1 ./vendor/bin/phpunit --coverage-clover coverage.xml --exclude-group atlas-search diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7044f9069..d7f066483 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -23,6 +23,8 @@ + + ['root' => 'bson'], ], ); - assert($result instanceof Document); - return $result ? (string) $result->payload : false; + if ($result instanceof Document) { + return (string) $result->payload; + } + + return false; } public function write($sessionId, $data): bool From d8b509ebb3a8c31bfa204796698ccc28fbaa40fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 30 Apr 2025 10:27:47 +0200 Subject: [PATCH 02/28] Validate PSR namespaces in CI (#3363) --- .github/workflows/coding-standards.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 068415f37..a2211312e 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -53,6 +53,9 @@ jobs: with: composer-options: "--no-suggest" + - name: "Validate PSR class names" + run: "composer dump-autoload --optimize --strict-psr" + - name: "Format the code" continue-on-error: true run: | From 397a3e3826ea1e1268a1365f2bd53601e3598b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 30 Apr 2025 13:31:34 +0200 Subject: [PATCH 03/28] PHPORM-331 Fix test on query payload (#3375) New properties added by https://github.com/laravel/framework/pull/55529 --- tests/QueueTest.php | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/QueueTest.php b/tests/QueueTest.php index efc8f07ff..4de63391d 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -15,7 +15,7 @@ use MongoDB\Laravel\Queue\MongoQueue; use function app; -use function json_encode; +use function json_decode; class QueueTest extends TestCase { @@ -42,17 +42,16 @@ public function testQueueJobLifeCycle(): void $job = Queue::pop('test'); $this->assertInstanceOf(MongoJob::class, $job); $this->assertEquals(1, $job->isReserved()); - $this->assertEquals(json_encode([ - 'uuid' => $uuid, - 'displayName' => 'test', - 'job' => 'test', - 'maxTries' => null, - 'maxExceptions' => null, - 'failOnTimeout' => false, - 'backoff' => null, - 'timeout' => null, - 'data' => ['action' => 'QueueJobLifeCycle'], - ]), $job->getRawBody()); + $payload = json_decode($job->getRawBody(), true); + $this->assertEquals($uuid, $payload['uuid']); + $this->assertEquals('test', $payload['displayName']); + $this->assertEquals('test', $payload['job']); + $this->assertNull($payload['maxTries']); + $this->assertNull($payload['maxExceptions']); + $this->assertFalse($payload['failOnTimeout']); + $this->assertNull($payload['backoff']); + $this->assertNull($payload['timeout']); + $this->assertEquals(['action' => 'QueueJobLifeCycle'], $payload['data']); // Remove reserved job $job->delete(); From 9d7d4dcdff31c2df506fd32997a5baa2def1e6f7 Mon Sep 17 00:00:00 2001 From: Amir Reza Mehrbakhsh Date: Wed, 30 Apr 2025 13:39:14 +0200 Subject: [PATCH 04/28] PHPORM-330 Fix: Convert query duration time to milliseconds (#3374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Convert query duration time to milliseconds * Test that query time is expressed in milliseconds --------- Co-authored-by: Jérôme Tamarelle --- src/CommandSubscriber.php | 2 +- tests/ConnectionTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CommandSubscriber.php b/src/CommandSubscriber.php index 569c7c909..5daa6e97a 100644 --- a/src/CommandSubscriber.php +++ b/src/CommandSubscriber.php @@ -48,6 +48,6 @@ private function logQuery(CommandSucceededEvent|CommandFailedEvent $event): void } } - $this->connection->logQuery(Document::fromPHP($command)->toCanonicalExtendedJSON(), [], $event->getDurationMicros()); + $this->connection->logQuery(Document::fromPHP($command)->toCanonicalExtendedJSON(), [], $event->getDurationMicros() / 1000); } } diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 75761080e..1f970d819 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -289,6 +289,8 @@ public function testQueryLog() DB::table('items')->get(); $this->assertCount(1, $logs = DB::getQueryLog()); $this->assertJsonStringEqualsJsonString('{"find":"items","filter":{}}', $logs[0]['query']); + $this->assertLessThan(10, $logs[0]['time'], 'Query time is in milliseconds'); + $this->assertGreaterThan(0.01, $logs[0]['time'], 'Query time is in milliseconds'); DB::table('items')->insert(['id' => $id = new ObjectId(), 'name' => 'test']); $this->assertCount(2, $logs = DB::getQueryLog()); From 6260b4751d0c7046f54f7a0d4e42435ee766eee8 Mon Sep 17 00:00:00 2001 From: fergusean Date: Mon, 19 May 2025 08:31:18 -0400 Subject: [PATCH 05/28] Fix database name extraction from DSN containing a CA file path (#3381) --- src/Connection.php | 2 +- tests/ConnectionTest.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Connection.php b/src/Connection.php index 29b72ae44..3fa99e94b 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -214,7 +214,7 @@ protected function withFreshQueryLog($callback) protected function getDefaultDatabaseName(string $dsn, array $config): string { if (empty($config['database'])) { - if (! preg_match('/^mongodb(?:[+]srv)?:\\/\\/.+\\/([^?&]+)/s', $dsn, $matches)) { + if (! preg_match('/^mongodb(?:[+]srv)?:\\/\\/.+?\\/([^?&]+)/s', $dsn, $matches)) { throw new InvalidArgumentException('Database is not properly configured.'); } diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 1f970d819..de77da7f7 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -190,6 +190,12 @@ public static function dataConnectionConfig(): Generator 'expectedDatabaseName' => 'tests', 'config' => ['dsn' => 'mongodb://some-host:12345/tests'], ]; + + yield 'Database is extracted from DSN with CA path in options' => [ + 'expectedUri' => 'mongodb://some-host:12345/tests?tls=true&tlsCAFile=/path/to/ca.pem&retryWrites=false', + 'expectedDatabaseName' => 'tests', + 'config' => ['dsn' => 'mongodb://some-host:12345/tests?tls=true&tlsCAFile=/path/to/ca.pem&retryWrites=false'], + ]; } #[DataProvider('dataConnectionConfig')] From 72d6e5783d8ae2045b8c6b3f01dbbb07c5fa92b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 09:23:07 +0200 Subject: [PATCH 06/28] Bump ramsey/composer-install from 3.1.0 to 3.1.1 (#3393) Bumps [ramsey/composer-install](https://github.com/ramsey/composer-install) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/ramsey/composer-install/releases) - [Commits](https://github.com/ramsey/composer-install/compare/3.1.0...3.1.1) --- updated-dependencies: - dependency-name: ramsey/composer-install dependency-version: 3.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coding-standards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index a2211312e..dee6d9aab 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -49,7 +49,7 @@ jobs: run: "php --ri mongodb" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@3.1.0" + uses: "ramsey/composer-install@3.1.1" with: composer-options: "--no-suggest" From cf75f9664a92b45af0b8dd108176585ff1849e4d Mon Sep 17 00:00:00 2001 From: Pauline Vos Date: Tue, 27 May 2025 14:21:25 +0200 Subject: [PATCH 07/28] Extract duplicated collection methods in `Builder` The logic for fetching views and collections are very nearly the same bar aggregation support. --- src/Schema/Builder.php | 97 +++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 57 deletions(-) diff --git a/src/Schema/Builder.php b/src/Schema/Builder.php index 746fda99e..2a16d4466 100644 --- a/src/Schema/Builder.php +++ b/src/Schema/Builder.php @@ -6,6 +6,7 @@ use Closure; use MongoDB\Collection; +use MongoDB\Database; use MongoDB\Driver\Exception\ServerException; use MongoDB\Laravel\Connection; use MongoDB\Model\CollectionInfo; @@ -156,66 +157,13 @@ public function dropAllTables() /** @param string|null $schema Database name */ public function getTables($schema = null) { - $db = $this->connection->getDatabase($schema); - $collections = []; - - foreach ($db->listCollections() as $collectionInfo) { - $collectionName = $collectionInfo->getName(); - - // Skip views, which don't support aggregate - if ($collectionInfo->getType() === 'view') { - continue; - } - - $stats = $db->selectCollection($collectionName)->aggregate([ - ['$collStats' => ['storageStats' => ['scale' => 1]]], - ['$project' => ['storageStats.totalSize' => 1]], - ])->toArray(); - - $collections[] = [ - 'name' => $collectionName, - 'schema' => $db->getDatabaseName(), - 'schema_qualified_name' => $db->getDatabaseName() . '.' . $collectionName, - 'size' => $stats[0]?->storageStats?->totalSize ?? null, - 'comment' => null, - 'collation' => null, - 'engine' => null, - ]; - } - - usort($collections, fn ($a, $b) => $a['name'] <=> $b['name']); - - return $collections; + return $this->getCollectionRows('collection', $schema); } /** @param string|null $schema Database name */ public function getViews($schema = null) { - $db = $this->connection->getDatabase($schema); - $collections = []; - - foreach ($db->listCollections() as $collectionInfo) { - $collectionName = $collectionInfo->getName(); - - // Skip normal type collection - if ($collectionInfo->getType() !== 'view') { - continue; - } - - $collections[] = [ - 'name' => $collectionName, - 'schema' => $db->getDatabaseName(), - 'schema_qualified_name' => $db->getDatabaseName() . '.' . $collectionName, - 'size' => null, - 'comment' => null, - 'collation' => null, - 'engine' => null, - ]; - } - - usort($collections, fn ($a, $b) => $a['name'] <=> $b['name']); - - return $collections; + return $this->getCollectionRows('view', $schema); } /** @@ -254,7 +202,7 @@ public function getColumns($table) [$db, $table] = explode('.', $table, 2); } - $stats = $this->connection->getDatabase($db)->selectCollection($table)->aggregate([ + $stats = $this->connection->getDatabase($db)->getCollection($table)->aggregate([ // Sample 1,000 documents to get a representative sample of the collection ['$sample' => ['size' => 1_000]], // Convert each document to an array of fields @@ -389,7 +337,7 @@ public function getCollection($name) } /** - * Get all of the collections names for the database. + * Get all the collections names for the database. * * @deprecated * @@ -418,4 +366,39 @@ public static function isAtlasSearchNotSupportedException(ServerException $e): b 31082, // MongoDB 8: Using Atlas Search Database Commands and the $listSearchIndexes aggregation stage requires additional configuration. ], true); } + + /** @param string|null $schema Database name */ + private function getCollectionRows(string $collectionType, $schema = null) + { + $db = $this->connection->getDatabase($schema); + $collections = []; + + foreach ($db->listCollections() as $collectionInfo) { + $collectionName = $collectionInfo->getName(); + + if ($collectionInfo->getType() !== $collectionType) { + continue; + } + + // Aggregation is not supported on views + $stats = $collectionType !== 'view' ? $db->selectCollection($collectionName)->aggregate([ + ['$collStats' => ['storageStats' => ['scale' => 1]]], + ['$project' => ['storageStats.totalSize' => 1]], + ])->toArray() : null; + + $collections[] = [ + 'name' => $collectionName, + 'schema' => $db->getDatabaseName(), + 'schema_qualified_name' => $db->getDatabaseName() . '.' . $collectionName, + 'size' => $stats[0]?->storageStats?->totalSize ?? null, + 'comment' => null, + 'collation' => null, + 'engine' => null, + ]; + } + + usort($collections, fn ($a, $b) => $a['name'] <=> $b['name']); + + return $collections; + } } From c494c19119805a7ee281350a98c991508ceb3411 Mon Sep 17 00:00:00 2001 From: Pauline Vos Date: Wed, 28 May 2025 10:52:39 +0200 Subject: [PATCH 08/28] Add collation to `getCollections` and `getViews` Although it's not displayed anywhere other than `db:table` for now, the `collation` column is present in some of laravel's artisan dabase commands. --- src/Schema/Builder.php | 33 ++++++++++++++++++++++++++++++++- tests/SchemaTest.php | 18 +++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/Schema/Builder.php b/src/Schema/Builder.php index 2a16d4466..78cb3a4c1 100644 --- a/src/Schema/Builder.php +++ b/src/Schema/Builder.php @@ -15,6 +15,7 @@ use function array_column; use function array_fill_keys; use function array_filter; +use function array_key_exists; use function array_keys; use function array_map; use function array_merge; @@ -26,6 +27,7 @@ use function implode; use function in_array; use function is_array; +use function is_bool; use function is_string; use function iterator_to_array; use function sort; @@ -380,6 +382,9 @@ private function getCollectionRows(string $collectionType, $schema = null) continue; } + $options = $collectionInfo->getOptions(); + $collation = $options['collation'] ?? []; + // Aggregation is not supported on views $stats = $collectionType !== 'view' ? $db->selectCollection($collectionName)->aggregate([ ['$collStats' => ['storageStats' => ['scale' => 1]]], @@ -392,7 +397,7 @@ private function getCollectionRows(string $collectionType, $schema = null) 'schema_qualified_name' => $db->getDatabaseName() . '.' . $collectionName, 'size' => $stats[0]?->storageStats?->totalSize ?? null, 'comment' => null, - 'collation' => null, + 'collation' => $this->collationToString($collation), 'engine' => null, ]; } @@ -401,4 +406,30 @@ private function getCollectionRows(string $collectionType, $schema = null) return $collections; } + + private function collationToString(array $collation): string + { + $map = [ + 'locale' => 'l', + 'strength' => 's', + 'caseLevel' => 'cl', + 'caseFirst' => 'cf', + 'numericOrdering' => 'no', + 'alternate' => 'a', + 'maxVariable' => 'mv', + 'normalization' => 'n', + 'backwards' => 'b', + ]; + + $parts = []; + foreach ($collation as $key => $value) { + if (array_key_exists($key, $map)) { + $shortKey = $map[$key]; + $shortValue = is_bool($value) ? ($value ? '1' : '0') : $value; + $parts[] = $shortKey . '=' . $shortValue; + } + } + + return implode(';', $parts); + } } diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index 3257a671e..bc9666e5f 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -20,12 +20,15 @@ class SchemaTest extends TestCase { + private const COLL_WITH_COLLATION = 'collection_with_collation'; + public function tearDown(): void { $database = $this->getConnection('mongodb')->getDatabase(); assert($database instanceof Database); $database->dropCollection('newcollection'); $database->dropCollection('newcollection_two'); + $database->dropCollection(self::COLL_WITH_COLLATION); $database->dropCollection('test_view'); parent::tearDown(); @@ -394,9 +397,17 @@ public function testHasColumns(): void public function testGetTables() { + $db = DB::connection('mongodb')->getDatabase(); + $db->createCollection(self::COLL_WITH_COLLATION, [ + 'collation' => [ + 'locale' => 'fr', + 'strength' => 2, + ], + ]); + DB::connection('mongodb')->table('newcollection')->insert(['test' => 'value']); DB::connection('mongodb')->table('newcollection_two')->insert(['test' => 'value']); - DB::connection('mongodb')->getDatabase()->createCollection('test_view', ['viewOn' => 'newcollection']); + $db->createCollection('test_view', ['viewOn' => 'newcollection']); $dbName = DB::connection('mongodb')->getDatabaseName(); $tables = Schema::getTables(); @@ -407,6 +418,7 @@ public function testGetTables() $this->assertArrayHasKey('name', $table); $this->assertArrayHasKey('size', $table); $this->assertArrayHasKey('schema', $table); + $this->assertArrayHasKey('collation', $table); $this->assertArrayHasKey('schema_qualified_name', $table); $this->assertNotEquals('test_view', $table['name'], 'Standard views should not be included in the result of getTables.'); @@ -416,6 +428,10 @@ public function testGetTables() $this->assertEquals($dbName . '.newcollection', $table['schema_qualified_name']); $found = true; } + + if ($table['name'] === self::COLL_WITH_COLLATION) { + $this->assertEquals('l=fr;cl=0;cf=off;s=2;no=0;a=non-ignorable;mv=punct;n=0;b=0', $table['collation']); + } } if (! $found) { From f22577243009b72b40c040d27ff75bb47f2a4471 Mon Sep 17 00:00:00 2001 From: Pauline Vos Date: Wed, 28 May 2025 11:52:32 +0200 Subject: [PATCH 09/28] Add Pauline as a maintainer --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 2542b51bb..6edd8d484 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ }, "authors": [ { "name": "Andreas Braun", "email": "andreas.braun@mongodb.com", "role": "Leader" }, + { "name": "Pauline Vos", "email": "pauline.vos@mongodb.com", "role": "Maintainer" }, { "name": "Jérôme Tamarelle", "email": "jerome.tamarelle@mongodb.com", "role": "Maintainer" }, { "name": "Jeremy Mikola", "email": "jmikola@gmail.com", "role": "Maintainer" }, { "name": "Jens Segers", "homepage": "https://jenssegers.com", "role": "Creator" } From f6e3c1825c4fce7c7205cece473f766e9d2ccc6c Mon Sep 17 00:00:00 2001 From: Pauline Vos Date: Wed, 28 May 2025 15:57:27 +0200 Subject: [PATCH 10/28] Change string literals in `SchemaTest` to constants --- tests/SchemaTest.php | 252 ++++++++++++++++++++++--------------------- 1 file changed, 127 insertions(+), 125 deletions(-) diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index bc9666e5f..860daf19e 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -20,14 +20,16 @@ class SchemaTest extends TestCase { + private const COLL_1 = 'new_collection'; + private const COLL_2 = 'new_collection_two'; private const COLL_WITH_COLLATION = 'collection_with_collation'; public function tearDown(): void { $database = $this->getConnection('mongodb')->getDatabase(); assert($database instanceof Database); - $database->dropCollection('newcollection'); - $database->dropCollection('newcollection_two'); + $database->dropCollection(self::COLL_1); + $database->dropCollection(self::COLL_2); $database->dropCollection(self::COLL_WITH_COLLATION); $database->dropCollection('test_view'); @@ -36,204 +38,204 @@ public function tearDown(): void public function testCreate(): void { - Schema::create('newcollection'); - $this->assertTrue(Schema::hasCollection('newcollection')); - $this->assertTrue(Schema::hasTable('newcollection')); + Schema::create(self::COLL_1); + $this->assertTrue(Schema::hasCollection(self::COLL_1)); + $this->assertTrue(Schema::hasTable(self::COLL_1)); } public function testCreateWithCallback(): void { - Schema::create('newcollection', static function ($collection) { + Schema::create(self::COLL_1, static function ($collection) { self::assertInstanceOf(Blueprint::class, $collection); }); - $this->assertTrue(Schema::hasCollection('newcollection')); + $this->assertTrue(Schema::hasCollection(self::COLL_1)); } public function testCreateWithOptions(): void { - Schema::create('newcollection_two', null, ['capped' => true, 'size' => 1024]); - $this->assertTrue(Schema::hasCollection('newcollection_two')); - $this->assertTrue(Schema::hasTable('newcollection_two')); + Schema::create(self::COLL_2, null, ['capped' => true, 'size' => 1024]); + $this->assertTrue(Schema::hasCollection(self::COLL_2)); + $this->assertTrue(Schema::hasTable(self::COLL_2)); - $collection = Schema::getCollection('newcollection_two'); + $collection = Schema::getCollection(self::COLL_2); $this->assertTrue($collection['options']['capped']); $this->assertEquals(1024, $collection['options']['size']); } public function testDrop(): void { - Schema::create('newcollection'); - Schema::drop('newcollection'); - $this->assertFalse(Schema::hasCollection('newcollection')); + Schema::create(self::COLL_1); + Schema::drop(self::COLL_1); + $this->assertFalse(Schema::hasCollection(self::COLL_1)); } public function testBluePrint(): void { - Schema::table('newcollection', static function ($collection) { + Schema::table(self::COLL_1, static function ($collection) { self::assertInstanceOf(Blueprint::class, $collection); }); - Schema::table('newcollection', static function ($collection) { + Schema::table(self::COLL_1, static function ($collection) { self::assertInstanceOf(Blueprint::class, $collection); }); } public function testIndex(): void { - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->index('mykey1'); }); - $index = $this->assertIndexExists('newcollection', 'mykey1_1'); + $index = $this->assertIndexExists(self::COLL_1, 'mykey1_1'); $this->assertEquals(1, $index['key']['mykey1']); - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->index(['mykey2']); }); - $index = $this->assertIndexExists('newcollection', 'mykey2_1'); + $index = $this->assertIndexExists(self::COLL_1, 'mykey2_1'); $this->assertEquals(1, $index['key']['mykey2']); - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->string('mykey3')->index(); }); - $index = $this->assertIndexExists('newcollection', 'mykey3_1'); + $index = $this->assertIndexExists(self::COLL_1, 'mykey3_1'); $this->assertEquals(1, $index['key']['mykey3']); } public function testPrimary(): void { - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->string('mykey', 100)->primary(); }); - $index = $this->assertIndexExists('newcollection', 'mykey_1'); + $index = $this->assertIndexExists(self::COLL_1, 'mykey_1'); $this->assertEquals(1, $index['unique']); } public function testUnique(): void { - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->unique('uniquekey'); }); - $index = $this->assertIndexExists('newcollection', 'uniquekey_1'); + $index = $this->assertIndexExists(self::COLL_1, 'uniquekey_1'); $this->assertEquals(1, $index['unique']); } public function testDropIndex(): void { - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->unique('uniquekey'); $collection->dropIndex('uniquekey_1'); }); - $this->assertIndexNotExists('newcollection', 'uniquekey_1'); + $this->assertIndexNotExists(self::COLL_1, 'uniquekey_1'); - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->unique('uniquekey'); $collection->dropIndex(['uniquekey']); }); - $this->assertIndexNotExists('newcollection', 'uniquekey_1'); + $this->assertIndexNotExists(self::COLL_1, 'uniquekey_1'); - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->index(['field_a', 'field_b']); }); - $this->assertIndexExists('newcollection', 'field_a_1_field_b_1'); + $this->assertIndexExists(self::COLL_1, 'field_a_1_field_b_1'); - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->dropIndex(['field_a', 'field_b']); }); - $this->assertIndexNotExists('newcollection', 'field_a_1_field_b_1'); + $this->assertIndexNotExists(self::COLL_1, 'field_a_1_field_b_1'); $indexName = 'field_a_-1_field_b_1'; - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->index(['field_a' => -1, 'field_b' => 1]); }); - $this->assertIndexExists('newcollection', $indexName); + $this->assertIndexExists(self::COLL_1, $indexName); - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->dropIndex(['field_a' => -1, 'field_b' => 1]); }); - $this->assertIndexNotExists('newcollection', $indexName); + $this->assertIndexNotExists(self::COLL_1, $indexName); $indexName = 'custom_index_name'; - Schema::table('newcollection', function ($collection) use ($indexName) { + Schema::table(self::COLL_1, function ($collection) use ($indexName) { $collection->index(['field_a', 'field_b'], $indexName); }); - $this->assertIndexExists('newcollection', $indexName); + $this->assertIndexExists(self::COLL_1, $indexName); - Schema::table('newcollection', function ($collection) use ($indexName) { + Schema::table(self::COLL_1, function ($collection) use ($indexName) { $collection->dropIndex($indexName); }); - $this->assertIndexNotExists('newcollection', $indexName); + $this->assertIndexNotExists(self::COLL_1, $indexName); } public function testDropIndexIfExists(): void { - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->unique('uniquekey'); $collection->dropIndexIfExists('uniquekey_1'); }); - $this->assertIndexNotExists('newcollection', 'uniquekey'); + $this->assertIndexNotExists(self::COLL_1, 'uniquekey'); - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->unique('uniquekey'); $collection->dropIndexIfExists(['uniquekey']); }); - $this->assertIndexNotExists('newcollection', 'uniquekey'); + $this->assertIndexNotExists(self::COLL_1, 'uniquekey'); - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->index(['field_a', 'field_b']); }); - $this->assertIndexExists('newcollection', 'field_a_1_field_b_1'); + $this->assertIndexExists(self::COLL_1, 'field_a_1_field_b_1'); - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->dropIndexIfExists(['field_a', 'field_b']); }); - $this->assertIndexNotExists('newcollection', 'field_a_1_field_b_1'); + $this->assertIndexNotExists(self::COLL_1, 'field_a_1_field_b_1'); - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->index(['field_a', 'field_b'], 'custom_index_name'); }); - $this->assertIndexExists('newcollection', 'custom_index_name'); + $this->assertIndexExists(self::COLL_1, 'custom_index_name'); - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->dropIndexIfExists('custom_index_name'); }); - $this->assertIndexNotExists('newcollection', 'custom_index_name'); + $this->assertIndexNotExists(self::COLL_1, 'custom_index_name'); } public function testHasIndex(): void { - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->index('myhaskey1'); $this->assertTrue($collection->hasIndex('myhaskey1_1')); $this->assertFalse($collection->hasIndex('myhaskey1')); }); - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->index('myhaskey2'); $this->assertTrue($collection->hasIndex(['myhaskey2'])); $this->assertFalse($collection->hasIndex(['myhaskey2_1'])); }); - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->index(['field_a', 'field_b']); $this->assertTrue($collection->hasIndex(['field_a_1_field_b'])); $this->assertFalse($collection->hasIndex(['field_a_1_field_b_1'])); @@ -242,74 +244,74 @@ public function testHasIndex(): void public function testSparse(): void { - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->sparse('sparsekey'); }); - $index = $this->assertIndexExists('newcollection', 'sparsekey_1'); + $index = $this->assertIndexExists(self::COLL_1, 'sparsekey_1'); $this->assertEquals(1, $index['sparse']); } public function testExpire(): void { - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->expire('expirekey', 60); }); - $index = $this->assertIndexExists('newcollection', 'expirekey_1'); + $index = $this->assertIndexExists(self::COLL_1, 'expirekey_1'); $this->assertEquals(60, $index['expireAfterSeconds']); } public function testSoftDeletes(): void { - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->softDeletes(); }); - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->string('email')->nullable()->index(); }); - $index = $this->assertIndexExists('newcollection', 'email_1'); + $index = $this->assertIndexExists(self::COLL_1, 'email_1'); $this->assertEquals(1, $index['key']['email']); } public function testFluent(): void { - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->string('email')->index(); $collection->string('token')->index(); $collection->timestamp('created_at'); }); - $index = $this->assertIndexExists('newcollection', 'email_1'); + $index = $this->assertIndexExists(self::COLL_1, 'email_1'); $this->assertEquals(1, $index['key']['email']); - $index = $this->assertIndexExists('newcollection', 'token_1'); + $index = $this->assertIndexExists(self::COLL_1, 'token_1'); $this->assertEquals(1, $index['key']['token']); } public function testGeospatial(): void { - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->geospatial('point'); $collection->geospatial('area', '2d'); $collection->geospatial('continent', '2dsphere'); }); - $index = $this->assertIndexExists('newcollection', 'point_2d'); + $index = $this->assertIndexExists(self::COLL_1, 'point_2d'); $this->assertEquals('2d', $index['key']['point']); - $index = $this->assertIndexExists('newcollection', 'area_2d'); + $index = $this->assertIndexExists(self::COLL_1, 'area_2d'); $this->assertEquals('2d', $index['key']['area']); - $index = $this->assertIndexExists('newcollection', 'continent_2dsphere'); + $index = $this->assertIndexExists(self::COLL_1, 'continent_2dsphere'); $this->assertEquals('2dsphere', $index['key']['continent']); } public function testDummies(): void { - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->boolean('activated')->default(0); $collection->integer('user_id')->unsigned(); }); @@ -318,22 +320,22 @@ public function testDummies(): void public function testSparseUnique(): void { - Schema::table('newcollection', function ($collection) { + Schema::table(self::COLL_1, function ($collection) { $collection->sparse_and_unique('sparseuniquekey'); }); - $index = $this->assertIndexExists('newcollection', 'sparseuniquekey_1'); + $index = $this->assertIndexExists(self::COLL_1, 'sparseuniquekey_1'); $this->assertEquals(1, $index['sparse']); $this->assertEquals(1, $index['unique']); } public function testRenameColumn(): void { - DB::connection()->table('newcollection')->insert(['test' => 'value']); - DB::connection()->table('newcollection')->insert(['test' => 'value 2']); - DB::connection()->table('newcollection')->insert(['column' => 'column value']); + DB::connection()->table(self::COLL_1)->insert(['test' => 'value']); + DB::connection()->table(self::COLL_1)->insert(['test' => 'value 2']); + DB::connection()->table(self::COLL_1)->insert(['column' => 'column value']); - $check = DB::connection()->table('newcollection')->get(); + $check = DB::connection()->table(self::COLL_1)->get(); $this->assertCount(3, $check); $this->assertObjectHasProperty('test', $check[0]); @@ -346,11 +348,11 @@ public function testRenameColumn(): void $this->assertObjectNotHasProperty('test', $check[2]); $this->assertObjectNotHasProperty('newtest', $check[2]); - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->renameColumn('test', 'newtest'); }); - $check2 = DB::connection()->table('newcollection')->get(); + $check2 = DB::connection()->table(self::COLL_1)->get(); $this->assertCount(3, $check2); $this->assertObjectHasProperty('newtest', $check2[0]); @@ -369,30 +371,30 @@ public function testRenameColumn(): void public function testHasColumn(): void { - $this->assertTrue(Schema::hasColumn('newcollection', '_id')); - $this->assertTrue(Schema::hasColumn('newcollection', 'id')); + $this->assertTrue(Schema::hasColumn(self::COLL_1, '_id')); + $this->assertTrue(Schema::hasColumn(self::COLL_1, 'id')); - DB::connection()->table('newcollection')->insert(['column1' => 'value', 'embed' => ['_id' => 1]]); + DB::connection()->table(self::COLL_1)->insert(['column1' => 'value', 'embed' => ['_id' => 1]]); - $this->assertTrue(Schema::hasColumn('newcollection', 'column1')); - $this->assertFalse(Schema::hasColumn('newcollection', 'column2')); - $this->assertTrue(Schema::hasColumn('newcollection', 'embed._id')); - $this->assertTrue(Schema::hasColumn('newcollection', 'embed.id')); + $this->assertTrue(Schema::hasColumn(self::COLL_1, 'column1')); + $this->assertFalse(Schema::hasColumn(self::COLL_1, 'column2')); + $this->assertTrue(Schema::hasColumn(self::COLL_1, 'embed._id')); + $this->assertTrue(Schema::hasColumn(self::COLL_1, 'embed.id')); } public function testHasColumns(): void { - $this->assertTrue(Schema::hasColumns('newcollection', ['_id'])); - $this->assertTrue(Schema::hasColumns('newcollection', ['id'])); + $this->assertTrue(Schema::hasColumns(self::COLL_1, ['_id'])); + $this->assertTrue(Schema::hasColumns(self::COLL_1, ['id'])); // Insert documents with both column1 and column2 - DB::connection()->table('newcollection')->insert([ + DB::connection()->table(self::COLL_1)->insert([ ['column1' => 'value1', 'column2' => 'value2'], ['column1' => 'value3'], ]); - $this->assertTrue(Schema::hasColumns('newcollection', ['column1', 'column2'])); - $this->assertFalse(Schema::hasColumns('newcollection', ['column1', 'column3'])); + $this->assertTrue(Schema::hasColumns(self::COLL_1, ['column1', 'column2'])); + $this->assertFalse(Schema::hasColumns(self::COLL_1, ['column1', 'column3'])); } public function testGetTables() @@ -405,9 +407,9 @@ public function testGetTables() ], ]); - DB::connection('mongodb')->table('newcollection')->insert(['test' => 'value']); - DB::connection('mongodb')->table('newcollection_two')->insert(['test' => 'value']); - $db->createCollection('test_view', ['viewOn' => 'newcollection']); + DB::connection('mongodb')->table(self::COLL_1)->insert(['test' => 'value']); + DB::connection('mongodb')->table(self::COLL_2)->insert(['test' => 'value']); + $db->createCollection('test_view', ['viewOn' => self::COLL_1]); $dbName = DB::connection('mongodb')->getDatabaseName(); $tables = Schema::getTables(); @@ -422,10 +424,10 @@ public function testGetTables() $this->assertArrayHasKey('schema_qualified_name', $table); $this->assertNotEquals('test_view', $table['name'], 'Standard views should not be included in the result of getTables.'); - if ($table['name'] === 'newcollection') { + if ($table['name'] === self::COLL_1) { $this->assertEquals(8192, $table['size']); $this->assertEquals($dbName, $table['schema']); - $this->assertEquals($dbName . '.newcollection', $table['schema_qualified_name']); + $this->assertEquals($dbName . '.' . self::COLL_1, $table['schema_qualified_name']); $found = true; } @@ -435,17 +437,17 @@ public function testGetTables() } if (! $found) { - $this->fail('Collection "newcollection" not found'); + $this->fail('Collection "' . self::COLL_1 . '" not found'); } } public function testGetViews() { - DB::connection('mongodb')->table('newcollection')->insert(['test' => 'value']); - DB::connection('mongodb')->table('newcollection_two')->insert(['test' => 'value']); + DB::connection('mongodb')->table(self::COLL_1)->insert(['test' => 'value']); + DB::connection('mongodb')->table(self::COLL_2)->insert(['test' => 'value']); $dbName = DB::connection('mongodb')->getDatabaseName(); - DB::connection('mongodb')->getDatabase()->createCollection('test_view', ['viewOn' => 'newcollection']); + DB::connection('mongodb')->getDatabase()->createCollection('test_view', ['viewOn' => self::COLL_1]); $tables = Schema::getViews(); @@ -459,7 +461,7 @@ public function testGetViews() $this->assertArrayHasKey('schema_qualified_name', $table); // Ensure "normal collections" are not in the views list - $this->assertNotEquals('newcollection', $table['name'], 'Normal collections should not be included in the result of getViews.'); + $this->assertNotEquals(self::COLL_1, $table['name'], 'Normal collections should not be included in the result of getViews.'); if ($table['name'] === 'test_view') { $this->assertEquals($dbName, $table['schema']); @@ -475,45 +477,45 @@ public function testGetViews() public function testGetTableListing() { - DB::connection('mongodb')->table('newcollection')->insert(['test' => 'value']); - DB::connection('mongodb')->table('newcollection_two')->insert(['test' => 'value']); + DB::connection('mongodb')->table(self::COLL_1)->insert(['test' => 'value']); + DB::connection('mongodb')->table(self::COLL_2)->insert(['test' => 'value']); $tables = Schema::getTableListing(); $this->assertIsArray($tables); $this->assertGreaterThanOrEqual(2, count($tables)); - $this->assertContains('newcollection', $tables); - $this->assertContains('newcollection_two', $tables); + $this->assertContains(self::COLL_1, $tables); + $this->assertContains(self::COLL_2, $tables); } public function testGetTableListingBySchema() { - DB::connection('mongodb')->table('newcollection')->insert(['test' => 'value']); - DB::connection('mongodb')->table('newcollection_two')->insert(['test' => 'value']); + DB::connection('mongodb')->table(self::COLL_1)->insert(['test' => 'value']); + DB::connection('mongodb')->table(self::COLL_2)->insert(['test' => 'value']); $dbName = DB::connection('mongodb')->getDatabaseName(); $tables = Schema::getTableListing([$dbName, 'database__that_does_not_exists'], schemaQualified: true); $this->assertIsArray($tables); $this->assertGreaterThanOrEqual(2, count($tables)); - $this->assertContains($dbName . '.newcollection', $tables); - $this->assertContains($dbName . '.newcollection_two', $tables); + $this->assertContains($dbName . '.' . self::COLL_1, $tables); + $this->assertContains($dbName . '.' . self::COLL_2, $tables); $tables = Schema::getTableListing([$dbName, 'database__that_does_not_exists'], schemaQualified: false); $this->assertIsArray($tables); $this->assertGreaterThanOrEqual(2, count($tables)); - $this->assertContains('newcollection', $tables); - $this->assertContains('newcollection_two', $tables); + $this->assertContains(self::COLL_1, $tables); + $this->assertContains(self::COLL_2, $tables); } public function testGetColumns() { - $collection = DB::connection('mongodb')->table('newcollection'); + $collection = DB::connection('mongodb')->table(self::COLL_1); $collection->insert(['text' => 'value', 'mixed' => ['key' => 'value']]); $collection->insert(['date' => new UTCDateTime(), 'binary' => new Binary('binary'), 'mixed' => true]); - $columns = Schema::getColumns('newcollection'); + $columns = Schema::getColumns(self::COLL_1); $this->assertIsArray($columns); $this->assertCount(5, $columns); @@ -544,7 +546,7 @@ public function testGetColumns() $this->assertSame([], $columns); // Qualified table name - $columns = Schema::getColumns(DB::getDatabaseName() . '.newcollection'); + $columns = Schema::getColumns(DB::getDatabaseName() . '.' . self::COLL_1); $this->assertIsArray($columns); $this->assertCount(5, $columns); } @@ -552,12 +554,12 @@ public function testGetColumns() /** @see AtlasSearchTest::testGetIndexes() */ public function testGetIndexes() { - Schema::create('newcollection', function (Blueprint $collection) { + Schema::create(self::COLL_1, function (Blueprint $collection) { $collection->index('mykey1'); $collection->string('mykey2')->unique('unique_index'); $collection->string('mykey3')->index(); }); - $indexes = Schema::getIndexes('newcollection'); + $indexes = Schema::getIndexes(self::COLL_1); self::assertIsArray($indexes); self::assertCount(4, $indexes); @@ -603,7 +605,7 @@ public function testSearchIndex(): void { $this->skipIfSearchIndexManagementIsNotSupported(); - Schema::create('newcollection', function (Blueprint $collection) { + Schema::create(self::COLL_1, function (Blueprint $collection) { $collection->searchIndex([ 'mappings' => [ 'dynamic' => false, @@ -614,7 +616,7 @@ public function testSearchIndex(): void ]); }); - $index = $this->getSearchIndex('newcollection', 'default'); + $index = $this->getSearchIndex(self::COLL_1, 'default'); self::assertNotNull($index); self::assertSame('default', $index['name']); @@ -622,11 +624,11 @@ public function testSearchIndex(): void self::assertFalse($index['latestDefinition']['mappings']['dynamic']); self::assertSame('lucene.whitespace', $index['latestDefinition']['mappings']['fields']['foo']['analyzer']); - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->dropSearchIndex('default'); }); - $index = $this->getSearchIndex('newcollection', 'default'); + $index = $this->getSearchIndex(self::COLL_1, 'default'); self::assertNull($index); } @@ -634,7 +636,7 @@ public function testVectorSearchIndex() { $this->skipIfSearchIndexManagementIsNotSupported(); - Schema::create('newcollection', function (Blueprint $collection) { + Schema::create(self::COLL_1, function (Blueprint $collection) { $collection->vectorSearchIndex([ 'fields' => [ ['type' => 'vector', 'path' => 'foo', 'numDimensions' => 128, 'similarity' => 'euclidean', 'quantization' => 'none'], @@ -642,7 +644,7 @@ public function testVectorSearchIndex() ], 'vector'); }); - $index = $this->getSearchIndex('newcollection', 'vector'); + $index = $this->getSearchIndex(self::COLL_1, 'vector'); self::assertNotNull($index); self::assertSame('vector', $index['name']); @@ -650,11 +652,11 @@ public function testVectorSearchIndex() self::assertSame('vector', $index['latestDefinition']['fields'][0]['type']); // Drop the index - Schema::table('newcollection', function (Blueprint $collection) { + Schema::table(self::COLL_1, function (Blueprint $collection) { $collection->dropSearchIndex('vector'); }); - $index = $this->getSearchIndex('newcollection', 'vector'); + $index = $this->getSearchIndex(self::COLL_1, 'vector'); self::assertNull($index); } From 7ca536bb466c07909b4feda3d1216f7bd1f906f3 Mon Sep 17 00:00:00 2001 From: Pauline Vos Date: Mon, 2 Jun 2025 16:21:50 +0200 Subject: [PATCH 11/28] Add conditional return type to `Builder::raw()` (#3395) for both the Eloquent and the Query builder, so that developers can understand that the return type will either be a `Collection` or an `Expression` based on the argument that's passed to `raw()` --- src/Eloquent/Builder.php | 10 +++++++++- src/Query/Builder.php | 8 +++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Eloquent/Builder.php b/src/Eloquent/Builder.php index f3ffd7012..7dba7b7ab 100644 --- a/src/Eloquent/Builder.php +++ b/src/Eloquent/Builder.php @@ -4,10 +4,12 @@ namespace MongoDB\Laravel\Eloquent; +use Closure; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use MongoDB\BSON\Document; +use MongoDB\Builder\Expression; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Builder\Type\SearchOperatorInterface; use MongoDB\Driver\CursorInterface; @@ -229,7 +231,13 @@ public function decrement($column, $amount = 1, array $extra = []) return parent::decrement($column, $amount, $extra); } - /** @inheritdoc */ + /** + * @param (Closure():T)|Expression|null $value + * + * @return ($value is Closure ? T : ($value is null ? Collection : Expression)) + * + * @template T + */ public function raw($value = null) { // Get raw results from the query builder. diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 6823998fd..9a6701b87 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -964,7 +964,13 @@ public function lists($column, $key = null) return $this->pluck($column, $key); } - /** @inheritdoc */ + /** + * @param (Closure():T)|Expression|null $value + * + * @return ($value is Closure ? T : ($value is null ? Collection : Expression)) + * + * @template T + */ public function raw($value = null) { // Execute the closure on the mongodb collection From 7a0f0bc2599dac6a0b2f865aeb8366e057b58f2b Mon Sep 17 00:00:00 2001 From: Pauline Vos Date: Wed, 4 Jun 2025 10:16:41 +0200 Subject: [PATCH 12/28] Support adding schema validation (#3397) To support the '$jsonSchema' operation on collections --- src/Schema/Blueprint.php | 19 +++++++++++++++++++ tests/SchemaTest.php | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/Schema/Blueprint.php b/src/Schema/Blueprint.php index 1197bfde1..1ae46cf6c 100644 --- a/src/Schema/Blueprint.php +++ b/src/Schema/Blueprint.php @@ -9,6 +9,7 @@ use MongoDB\Laravel\Connection; use function array_flip; +use function array_merge; use function implode; use function in_array; use function is_array; @@ -117,6 +118,24 @@ public function hasIndex($indexOrColumns = null) return false; } + public function jsonSchema( + array $schema = [], + ?string $validationLevel = null, + ?string $validationAction = null, + ): void { + $options = array_merge( + [ + 'validator' => [ + '$jsonSchema' => $schema, + ], + ], + $validationLevel ? ['validationLevel' => $validationLevel] : [], + $validationAction ? ['validationAction' => $validationAction] : [], + ); + + $this->connection->getDatabase()->modifyCollection($this->collection->getCollectionName(), $options); + } + /** * @param string|array $indexOrColumns * diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index 860daf19e..9726eb705 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -63,6 +63,39 @@ public function testCreateWithOptions(): void $this->assertEquals(1024, $collection['options']['size']); } + public function testCreateWithSchemaValidator(): void + { + $schema = [ + 'bsonType' => 'object', + 'required' => [ 'username' ], + 'properties' => [ + 'username' => [ + 'bsonType' => 'string', + 'description' => 'must be a string and is required', + ], + ], + ]; + + Schema::create(self::COLL_2, function (Blueprint $collection) use ($schema) { + $collection->string('username'); + $collection->jsonSchema(schema: $schema, validationAction: 'warn'); + }); + + $this->assertTrue(Schema::hasCollection(self::COLL_2)); + $this->assertTrue(Schema::hasTable(self::COLL_2)); + + $collection = Schema::getCollection(self::COLL_2); + $this->assertEquals( + ['$jsonSchema' => $schema], + $collection['options']['validator'], + ); + + $this->assertEquals( + 'warn', + $collection['options']['validationAction'], + ); + } + public function testDrop(): void { Schema::create(self::COLL_1); From 5fe1f5dcffe8b7e22ce0dd81bb3d9beaa122fd49 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Fri, 6 Jun 2025 10:08:57 -0400 Subject: [PATCH 13/28] DOCSP-50472: schema validation (#3400) * DOCSP-50472: schema validation * apply phpcbf formatting * small wording fix * fixes * log error * fix int type * wip * PV tech review 1 --- docs/eloquent-models/schema-builder.txt | 73 ++++++++++++++++--- .../schema-builder/flights_migration.php | 19 +++++ docs/quick-start/backend-service-tutorial.txt | 4 +- 3 files changed, 82 insertions(+), 14 deletions(-) diff --git a/docs/eloquent-models/schema-builder.txt b/docs/eloquent-models/schema-builder.txt index 3cdec0f03..e9c1dff17 100644 --- a/docs/eloquent-models/schema-builder.txt +++ b/docs/eloquent-models/schema-builder.txt @@ -21,8 +21,9 @@ Overview -------- Laravel provides a **facade** to access the schema builder class ``Schema``, -which lets you create and modify tables. Facades are static interfaces to -classes that make the syntax more concise and improve testability. +which lets you create and modify tables, or collections in MongoDB. +Facades are static interfaces to classes that make the syntax more +concise and improve testability. The {+odm-short+} supports a subset of the index and collection management methods in the Laravel ``Schema`` facade. @@ -33,16 +34,10 @@ in the Laravel documentation. The following sections describe the Laravel schema builder features available in the {+odm-short+} and show examples of how to use them: -- :ref:`` -- :ref:`` -- :ref:`` - -.. note:: - - The {+odm-short+} supports managing indexes and collections, but - excludes support for MongoDB JSON schemas for data validation. To learn - more about JSON schema validation, see :manual:`Schema Validation ` - in the {+server-docs-name+}. +- :ref:`laravel-eloquent-migrations` +- :ref:`laravel-eloquent-schema-validation` +- :ref:`laravel-eloquent-collection-exists` +- :ref:`laravel-eloquent-indexes` .. _laravel-eloquent-migrations: @@ -117,6 +112,60 @@ To learn more about Laravel migrations, see `Database: Migrations `__ in the Laravel documentation. +.. _laravel-eloquent-schema-validation: + +Implement Schema Validation +--------------------------- + +You can use the ``jsonSchema()`` method to implement :manual:`schema +validation ` when using the following schema +builder methods: + +- ``Schema::create()``: When creating a new collection +- ``Schema::table()``: When updating collection properties + +You can use schema validation to restrict data types and value ranges of +document fields in a specified collection. After you implement schema +validation, the server restricts write operations that don't follow the +validation rules. + +You can pass the following parameters to ``jsonSchema()``: + +- ``schema``: Array that specifies the validation rules for the + collection. To learn more about constructing a schema, see + the :manual:`$jsonSchema ` + reference in the {+server-docs-name+}. + +- ``validationLevel``: Sets the level of validation enforcement. + Accepted values are ``"strict"`` (default) and ``"moderate"``. + +- ``validationAction``: Specifies the action to take when invalid + operations are attempted. Accepted values are ``"error"`` (default) and + ``"warn"``. + +This example demonstrates how to specify a schema in the +``jsonSchema()`` method when creating a collection. The schema +validation has the following specifications: + +- Documents in the ``pilots`` collection must + contain the ``license_number`` field. + +- The ``license_number`` field must have an integer value between + ``1000`` and ``9999``. + +- If you attempt to perform invalid write operations, the server raises + an error. + +.. literalinclude:: /includes/schema-builder/flights_migration.php + :language: php + :dedent: + :start-after: begin-json-schema + :end-before: end-json-schema + +If you attempt to insert a document into the ``pilots`` collection that +violates the schema validation rule, {+odm-long+} returns a +:php:`BulkWriteException `. + .. _laravel-eloquent-collection-exists: Check Whether a Collection Exists diff --git a/docs/includes/schema-builder/flights_migration.php b/docs/includes/schema-builder/flights_migration.php index 861c339ef..4f776f260 100644 --- a/docs/includes/schema-builder/flights_migration.php +++ b/docs/includes/schema-builder/flights_migration.php @@ -19,6 +19,25 @@ public function up(): void $collection->unique('mission_id', options: ['name' => 'unique_mission_id_idx']); }); // end create index + + // begin-json-schema + Schema::create('pilots', function (Blueprint $collection) { + $collection->jsonSchema( + schema: [ + 'bsonType' => 'object', + 'required' => ['license_number'], + 'properties' => [ + 'license_number' => [ + 'bsonType' => 'int', + 'minimum' => 1000, + 'maximum' => 9999, + ], + ], + ], + validationAction: 'error', + ); + }); + // end-json-schema } public function down(): void diff --git a/docs/quick-start/backend-service-tutorial.txt b/docs/quick-start/backend-service-tutorial.txt index 9236c698a..7ecdf8cf8 100644 --- a/docs/quick-start/backend-service-tutorial.txt +++ b/docs/quick-start/backend-service-tutorial.txt @@ -1,8 +1,8 @@ .. _laravel-tutorial-backend-service: -========================================================== +=========================================================== Tutorial: Build a Back End Service by Using {+odm-long+} -========================================================== +=========================================================== .. facet:: :name: genre From e2b119a18a0d8d745da9f72855207266071c81b2 Mon Sep 17 00:00:00 2001 From: Faissal Wahabali Date: Tue, 10 Jun 2025 10:06:45 +0100 Subject: [PATCH 14/28] PHPORM-351 `QueryBuilder` multiply and divide support (#3373) --- src/Query/Builder.php | 41 +++++++++++++++++++++++++++++++ tests/QueryBuilderTest.php | 49 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 9a6701b87..8d57ba4c8 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -890,6 +890,47 @@ public function decrementEach(array $columns, array $extra = [], array $options return $this->incrementEach($decrement, $extra, $options); } + /** + * Multiply a column's value by a given amount. + * + * @param string $column + * @param float|int $amount + * + * @return int + */ + public function multiply($column, $amount, array $extra = [], array $options = []) + { + $query = ['$mul' => [(string) $column => $amount]]; + + if (! empty($extra)) { + $query['$set'] = $extra; + } + + // Protect + $this->where(function ($query) use ($column) { + $query->where($column, 'exists', true); + + $query->whereNotNull($column); + }); + + $options = $this->inheritConnectionOptions($options); + + return $this->performUpdate($query, $options); + } + + /** + * Divide a column's value by a given amount. + * + * @param string $column + * @param float|int $amount + * + * @return int + */ + public function divide($column, $amount, array $extra = [], array $options = []) + { + return $this->multiply($column, 1 / $amount, $extra, $options); + } + /** @inheritdoc */ public function pluck($column, $key = null) { diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 1233cda75..5c52b9003 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -1052,6 +1052,55 @@ public function testIncrement() $this->assertEquals(1, $user->age); } + public function testMultiplyAndDivide() + { + DB::table('users')->insert([ + ['name' => 'John Doe', 'salary' => 88000, 'note' => 'senior'], + ['name' => 'Jane Doe', 'salary' => 64000, 'note' => 'junior'], + ['name' => 'Robert Roe', 'salary' => null], + ['name' => 'Mark Moe'], + ]); + + $user = DB::table('users')->where('name', 'John Doe')->first(); + $this->assertEquals(88000, $user->salary); + + DB::table('users')->where('name', 'John Doe')->multiply('salary', 1); + $user = DB::table('users')->where('name', 'John Doe')->first(); + $this->assertEquals(88000, $user->salary); + + DB::table('users')->where('name', 'John Doe')->divide('salary', 1); + $user = DB::table('users')->where('name', 'John Doe')->first(); + $this->assertEquals(88000, $user->salary); + + DB::table('users')->where('name', 'John Doe')->multiply('salary', 2); + $user = DB::table('users')->where('name', 'John Doe')->first(); + $this->assertEquals(176000, $user->salary); + + DB::table('users')->where('name', 'John Doe')->divide('salary', 2); + $user = DB::table('users')->where('name', 'John Doe')->first(); + $this->assertEquals(88000, $user->salary); + + DB::table('users')->where('name', 'Jane Doe')->multiply('salary', 10, ['note' => 'senior']); + $user = DB::table('users')->where('name', 'Jane Doe')->first(); + $this->assertEquals(640000, $user->salary); + $this->assertEquals('senior', $user->note); + + DB::table('users')->where('name', 'John Doe')->divide('salary', 2, ['note' => 'junior']); + $user = DB::table('users')->where('name', 'John Doe')->first(); + $this->assertEquals(44000, $user->salary); + $this->assertEquals('junior', $user->note); + + DB::table('users')->multiply('salary', 1); + $user = DB::table('users')->where('name', 'John Doe')->first(); + $this->assertEquals(44000, $user->salary); + $user = DB::table('users')->where('name', 'Jane Doe')->first(); + $this->assertEquals(640000, $user->salary); + $user = DB::table('users')->where('name', 'Robert Roe')->first(); + $this->assertNull($user->salary); + $user = DB::table('users')->where('name', 'Mark Moe')->first(); + $this->assertFalse(isset($user->salary)); + } + public function testProjections() { DB::table('items')->insert([ From b952e768cf861de83cb33b68511caff899758f0f Mon Sep 17 00:00:00 2001 From: Pauline Vos Date: Wed, 11 Jun 2025 17:36:44 +0200 Subject: [PATCH 15/28] Bump PHP to 8.2 in Docker test container (#3399) --- Dockerfile | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 43529d9e4..39e37531d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG PHP_VERSION=8.1 +ARG PHP_VERSION=8.2 FROM php:${PHP_VERSION}-cli diff --git a/docker-compose.yml b/docker-compose.yml index fc0f0e49a..463da5f79 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: mongodb: container_name: mongodb - image: mongodb/mongodb-atlas-local:latest + image: mongodb/mongodb-atlas-local:8 ports: - "27017:27017" healthcheck: From 456326b75b5d74143878c4fcd2ea8f014adc189c Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Thu, 12 Jun 2025 11:32:22 -0400 Subject: [PATCH 16/28] DOCSP-50607: multiply/divide QB methods (#3403) * DOCSP-50607: multiply/divide QB methods * separate mul & div examples * add versioning * wip --- docs/eloquent-models/schema-builder.txt | 6 +-- .../query-builder/QueryBuilderTest.php | 39 +++++++++++++++---- docs/query-builder.txt | 36 +++++++++++++++++ 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/docs/eloquent-models/schema-builder.txt b/docs/eloquent-models/schema-builder.txt index e9c1dff17..a3e1df913 100644 --- a/docs/eloquent-models/schema-builder.txt +++ b/docs/eloquent-models/schema-builder.txt @@ -117,9 +117,9 @@ in the Laravel documentation. Implement Schema Validation --------------------------- -You can use the ``jsonSchema()`` method to implement :manual:`schema -validation ` when using the following schema -builder methods: +Starting in {+odm-short+} v5.5, you can use the ``jsonSchema()`` method +to implement :manual:`schema validation ` when +using the following schema builder methods: - ``Schema::create()``: When creating a new collection - ``Schema::table()``: When updating collection properties diff --git a/docs/includes/query-builder/QueryBuilderTest.php b/docs/includes/query-builder/QueryBuilderTest.php index 3f7ea2274..a90f1685f 100644 --- a/docs/includes/query-builder/QueryBuilderTest.php +++ b/docs/includes/query-builder/QueryBuilderTest.php @@ -213,10 +213,10 @@ public function testGroupBy(): void { // begin query groupBy $result = DB::table('movies') - ->where('rated', 'G') - ->groupBy('runtime') - ->orderBy('runtime', 'asc') - ->get(['title']); + ->where('rated', 'G') + ->groupBy('runtime') + ->orderBy('runtime', 'asc') + ->get(['title']); // end query groupBy $this->assertInstanceOf(\Illuminate\Support\Collection::class, $result); @@ -420,10 +420,10 @@ public function testWhereRaw(): void // begin query raw $result = DB::table('movies') ->whereRaw([ - 'imdb.votes' => ['$gte' => 1000 ], + 'imdb.votes' => ['$gte' => 1000], '$or' => [ ['imdb.rating' => ['$gt' => 7]], - ['directors' => ['$in' => [ 'Yasujiro Ozu', 'Sofia Coppola', 'Federico Fellini' ]]], + ['directors' => ['$in' => ['Yasujiro Ozu', 'Sofia Coppola', 'Federico Fellini']]], ], ])->get(); // end query raw @@ -470,7 +470,7 @@ public function testNear(): void { $this->importTheaters(); - // begin query near + // begin query near $results = DB::table('theaters') ->where('location.geo', 'near', [ '$geometry' => [ @@ -588,7 +588,7 @@ public function testUpdateUpsert(): void [ 'plot' => 'An autobiographical movie', 'year' => 1998, - 'writers' => [ 'Will Hunting' ], + 'writers' => ['Will Hunting'], ], ['upsert' => true], ); @@ -597,6 +597,29 @@ public function testUpdateUpsert(): void $this->assertIsInt($result); } + public function testMultiplyDivide(): void + { + // begin multiply divide + $result = DB::table('movies') + ->where('year', 2001) + ->multiply('imdb.votes', 5); + + $result = DB::table('movies') + ->where('year', 2001) + ->divide('runtime', 2); + // end multiply divide + + $this->assertIsInt($result); + + // begin multiply with set + $result = DB::table('movies') + ->where('year', 1958) + ->multiply('runtime', 1.5, ['note' => 'Adds recovered footage.']); + // end multiply with set + + $this->assertIsInt($result); + } + public function testIncrement(): void { // begin increment diff --git a/docs/query-builder.txt b/docs/query-builder.txt index a73d5e791..2358ed7d5 100644 --- a/docs/query-builder.txt +++ b/docs/query-builder.txt @@ -1169,6 +1169,7 @@ This section includes query builder examples that show how to use the following MongoDB-specific write operations: - :ref:`Upsert a document ` +- :ref:`Multiply and divide values ` - :ref:`Increment a numerical value ` - :ref:`Decrement a numerical value ` - :ref:`Add an array element ` @@ -1252,6 +1253,41 @@ and the ``title`` field and value specified in the ``where()`` query operation: The ``update()`` query builder method returns the number of documents that the operation updated or inserted. +.. _laravel-mongodb-query-builder-mul-div: + +Multiply and Divide Numerical Values Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Starting in {+odm-short+} v5.5, you can perform multiplication and +division operations on numerical values by using the ``multiply()`` and +``divide()`` query builder methods. + +The following example shows how to use the ``multiply()`` and +``divide()`` methods to manipulate the values of the +``imdb.votes`` and ``runtime`` fields: + +.. literalinclude:: /includes/query-builder/QueryBuilderTest.php + :language: php + :dedent: + :start-after: begin multiply divide + :end-before: end multiply divide + +.. tip:: update() Method + + You can perform the same operations by using the ``update()`` + method and passing an update document that includes the :manual:`$mul + ` operator. To learn more about + ``update()``, see the :ref:`laravel-fundamentals-write-modify` guide. + +You can optionally pass an array parameter to perform a ``$set`` update +in the same operation, as shown in the following example: + +.. literalinclude:: /includes/query-builder/QueryBuilderTest.php + :language: php + :dedent: + :start-after: begin multiply with set + :end-before: end multiply with set + .. _laravel-mongodb-query-builder-increment: Increment a Numerical Value Example From 3a7443f054374061cd745ee18cce8c937f3cf2ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 15:55:09 +0200 Subject: [PATCH 17/28] Bump stefanzweifel/git-auto-commit-action from 5 to 6 (#3409) Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 5 to 6. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v5...v6) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coding-standards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index dee6d9aab..c79d91bb8 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -67,6 +67,6 @@ jobs: run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr" - name: "Commit the changes" - uses: stefanzweifel/git-auto-commit-action@v5 + uses: stefanzweifel/git-auto-commit-action@v6 with: commit_message: "apply phpcbf formatting" From aad17bb7ac964a41022db95815ed043f4497462b Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 17 Jun 2025 18:55:39 +0200 Subject: [PATCH 18/28] Limit CI workflows to supported branches (#3411) * Fix YAML indentation * Limit CI workflows to supported branches * Run CI for feature branches --- .github/workflows/build-ci-atlas.yml | 176 ++++++++++--------- .github/workflows/build-ci.yml | 230 +++++++++++++------------ .github/workflows/coding-standards.yml | 6 + .github/workflows/merge-up.yml | 1 + .github/workflows/static-analysis.yml | 6 + 5 files changed, 222 insertions(+), 197 deletions(-) diff --git a/.github/workflows/build-ci-atlas.yml b/.github/workflows/build-ci-atlas.yml index 46f30150d..41f14e376 100644 --- a/.github/workflows/build-ci-atlas.yml +++ b/.github/workflows/build-ci-atlas.yml @@ -1,94 +1,100 @@ name: "Atlas CI" on: - push: - pull_request: + push: + branches: + - "[0-9]+.[0-9x]+" + - "feature/*" + pull_request: + branches: + - "[0-9]+.[0-9x]+" + - "feature/*" env: MONGODB_EXT_V1: mongodb-1.21.0 MONGODB_EXT_V2: stable jobs: - build: - runs-on: "${{ matrix.os }}" - - name: "PHP/${{ matrix.php }} Laravel/${{ matrix.laravel }} Driver/${{ matrix.driver }}" - - strategy: - matrix: - os: - - "ubuntu-latest" - php: - - "8.2" - - "8.3" - - "8.4" - laravel: - - "11.*" - - "12.*" - driver: - - 1 - include: - - php: "8.4" - laravel: "12.*" - os: "ubuntu-latest" - driver: 2 - - steps: - - uses: "actions/checkout@v4" - - - name: "Create MongoDB Atlas Local" - run: | - docker run --name mongodb -p 27017:27017 --detach mongodb/mongodb-atlas-local:latest - until docker exec --tty mongodb mongosh --eval "db.runCommand({ ping: 1 })"; do - sleep 1 - done - until docker exec --tty mongodb mongosh --eval "db.createCollection('connection_test') && db.getCollection('connection_test').createSearchIndex({mappings:{dynamic: true}})"; do - sleep 1 - done - - - name: "Show MongoDB server status" - run: | - docker exec --tty mongodb mongosh --eval "db.runCommand({ serverStatus: 1 })" - - - name: Setup cache environment - id: extcache - uses: shivammathur/cache-extensions@v1 - with: - php-version: ${{ matrix.php }} - extensions: ${{ matrix.driver == 1 && env.MONGODB_EXT_V1 || env.MONGODB_EXT_V2 }} - key: "extcache-v1" - - - name: "Installing php" - uses: "shivammathur/setup-php@v2" - with: - php-version: ${{ matrix.php }} - extensions: "curl,mbstring,xdebug,${{ matrix.driver == 1 && env.MONGODB_EXT_V1 || env.MONGODB_EXT_V2 }}" - coverage: "xdebug" - tools: "composer" - - - name: "Show Docker version" - if: ${{ runner.debug }} - run: "docker version && env" - - - name: "Restrict Laravel version" - run: "composer require --dev --no-update 'laravel/framework:${{ matrix.laravel }}'" - - - name: "Download Composer cache dependencies from cache" - id: "composer-cache" - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: "Cache Composer dependencies" - uses: "actions/cache@v4" - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: "${{ matrix.os }}-composer-${{ hashFiles('**/composer.json') }}" - restore-keys: "${{ matrix.os }}-composer-" - - - name: "Install dependencies" - run: | - composer update --no-interaction - - - name: "Run tests" - run: | - export MONGODB_URI="mongodb://127.0.0.1:27017/?directConnection=true" - php -d zend.assertions=1 ./vendor/bin/phpunit --coverage-clover coverage.xml --group atlas-search + build: + runs-on: "${{ matrix.os }}" + + name: "PHP/${{ matrix.php }} Laravel/${{ matrix.laravel }} Driver/${{ matrix.driver }}" + + strategy: + matrix: + os: + - "ubuntu-latest" + php: + - "8.2" + - "8.3" + - "8.4" + laravel: + - "11.*" + - "12.*" + driver: + - 1 + include: + - php: "8.4" + laravel: "12.*" + os: "ubuntu-latest" + driver: 2 + + steps: + - uses: "actions/checkout@v4" + + - name: "Create MongoDB Atlas Local" + run: | + docker run --name mongodb -p 27017:27017 --detach mongodb/mongodb-atlas-local:latest + until docker exec --tty mongodb mongosh --eval "db.runCommand({ ping: 1 })"; do + sleep 1 + done + until docker exec --tty mongodb mongosh --eval "db.createCollection('connection_test') && db.getCollection('connection_test').createSearchIndex({mappings:{dynamic: true}})"; do + sleep 1 + done + + - name: "Show MongoDB server status" + run: | + docker exec --tty mongodb mongosh --eval "db.runCommand({ serverStatus: 1 })" + + - name: Setup cache environment + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ matrix.php }} + extensions: ${{ matrix.driver == 1 && env.MONGODB_EXT_V1 || env.MONGODB_EXT_V2 }} + key: "extcache-v1" + + - name: "Installing php" + uses: "shivammathur/setup-php@v2" + with: + php-version: ${{ matrix.php }} + extensions: "curl,mbstring,xdebug,${{ matrix.driver == 1 && env.MONGODB_EXT_V1 || env.MONGODB_EXT_V2 }}" + coverage: "xdebug" + tools: "composer" + + - name: "Show Docker version" + if: ${{ runner.debug }} + run: "docker version && env" + + - name: "Restrict Laravel version" + run: "composer require --dev --no-update 'laravel/framework:${{ matrix.laravel }}'" + + - name: "Download Composer cache dependencies from cache" + id: "composer-cache" + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: "Cache Composer dependencies" + uses: "actions/cache@v4" + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: "${{ matrix.os }}-composer-${{ hashFiles('**/composer.json') }}" + restore-keys: "${{ matrix.os }}-composer-" + + - name: "Install dependencies" + run: | + composer update --no-interaction + + - name: "Run tests" + run: | + export MONGODB_URI="mongodb://127.0.0.1:27017/?directConnection=true" + php -d zend.assertions=1 ./vendor/bin/phpunit --coverage-clover coverage.xml --group atlas-search diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index bbc8b53d1..f94a32e79 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -1,121 +1,127 @@ name: "CI" on: - push: - pull_request: + push: + branches: + - "[0-9]+.[0-9x]+" + - "feature/*" + pull_request: + branches: + - "[0-9]+.[0-9x]+" + - "feature/*" env: MONGODB_EXT_V1: mongodb-1.21.0 MONGODB_EXT_V2: stable jobs: - build: - runs-on: "${{ matrix.os }}" - - name: "PHP/${{ matrix.php }} Laravel/${{ matrix.laravel }} Driver/${{ matrix.driver }} Server/${{ matrix.mongodb }} ${{ matrix.mode }}" - - strategy: - matrix: - os: - - "ubuntu-latest" - mongodb: - - "4.4" - - "5.0" - - "6.0" - - "7.0" - - "8.0" - php: - - "8.1" - - "8.2" - - "8.3" - - "8.4" - laravel: - - "10.*" - - "11.*" - - "12.*" - driver: - - 2 - include: - - php: "8.1" - laravel: "10.*" - mongodb: "5.0" - mode: "low-deps" - os: "ubuntu-latest" - driver: 1 - - php: "8.3" - laravel: "11.*" - mongodb: "8.0" - os: "ubuntu-latest" - driver: 1 - - php: "8.4" - laravel: "12.*" - mongodb: "8.0" - os: "ubuntu-latest" - driver: 1 - exclude: - - php: "8.1" - laravel: "11.*" - - php: "8.1" - laravel: "12.*" - - steps: - - uses: "actions/checkout@v4" - - - name: "Create MongoDB Replica Set" - run: | - docker run --name mongodb -p 27017:27017 -e MONGO_INITDB_DATABASE=unittest --detach mongo:${{ matrix.mongodb }} mongod --replSet rs --setParameter transactionLifetimeLimitSeconds=5 - - if [ "${{ matrix.mongodb }}" = "4.4" ]; then MONGOSH_BIN="mongo"; else MONGOSH_BIN="mongosh"; fi - until docker exec --tty mongodb $MONGOSH_BIN --eval "db.runCommand({ ping: 1 })"; do - sleep 1 - done - sudo docker exec --tty mongodb $MONGOSH_BIN --eval "rs.initiate({\"_id\":\"rs\",\"members\":[{\"_id\":0,\"host\":\"127.0.0.1:27017\" }]})" - - - name: "Show MongoDB server status" - run: | - if [ "${{ matrix.mongodb }}" = "4.4" ]; then MONGOSH_BIN="mongo"; else MONGOSH_BIN="mongosh"; fi - docker exec --tty mongodb $MONGOSH_BIN --eval "db.runCommand({ serverStatus: 1 })" - - - name: Setup cache environment - id: extcache - uses: shivammathur/cache-extensions@v1 - with: - php-version: ${{ matrix.php }} - extensions: ${{ matrix.driver == 1 && env.MONGODB_EXT_V1 || env.MONGODB_EXT_V2 }} - key: "extcache-v1" - - - name: "Installing php" - uses: "shivammathur/setup-php@v2" - with: - php-version: ${{ matrix.php }} - extensions: "curl,mbstring,xdebug,${{ matrix.driver == 1 && env.MONGODB_EXT_V1 || env.MONGODB_EXT_V2 }}" - coverage: "xdebug" - tools: "composer" - - - name: "Show Docker version" - if: ${{ runner.debug }} - run: "docker version && env" - - - name: "Restrict Laravel version" - run: "composer require --dev --no-update 'laravel/framework:${{ matrix.laravel }}'" - - - name: "Download Composer cache dependencies from cache" - id: "composer-cache" - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: "Cache Composer dependencies" - uses: "actions/cache@v4" - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: "${{ matrix.os }}-composer-${{ hashFiles('**/composer.json') }}" - restore-keys: "${{ matrix.os }}-composer-" - - - name: "Install dependencies" - run: | - composer update --no-interaction \ - $([[ "${{ matrix.mode }}" == low-deps ]] && echo ' --prefer-lowest') \ - $([[ "${{ matrix.mode }}" == ignore-php-req ]] && echo ' --ignore-platform-req=php+') - - name: "Run tests" - run: | - export MONGODB_URI="mongodb://127.0.0.1:27017/?replicaSet=rs" - php -d zend.assertions=1 ./vendor/bin/phpunit --coverage-clover coverage.xml --exclude-group atlas-search + build: + runs-on: "${{ matrix.os }}" + + name: "PHP/${{ matrix.php }} Laravel/${{ matrix.laravel }} Driver/${{ matrix.driver }} Server/${{ matrix.mongodb }} ${{ matrix.mode }}" + + strategy: + matrix: + os: + - "ubuntu-latest" + mongodb: + - "4.4" + - "5.0" + - "6.0" + - "7.0" + - "8.0" + php: + - "8.1" + - "8.2" + - "8.3" + - "8.4" + laravel: + - "10.*" + - "11.*" + - "12.*" + driver: + - 2 + include: + - php: "8.1" + laravel: "10.*" + mongodb: "5.0" + mode: "low-deps" + os: "ubuntu-latest" + driver: 1 + - php: "8.3" + laravel: "11.*" + mongodb: "8.0" + os: "ubuntu-latest" + driver: 1 + - php: "8.4" + laravel: "12.*" + mongodb: "8.0" + os: "ubuntu-latest" + driver: 1 + exclude: + - php: "8.1" + laravel: "11.*" + - php: "8.1" + laravel: "12.*" + + steps: + - uses: "actions/checkout@v4" + + - name: "Create MongoDB Replica Set" + run: | + docker run --name mongodb -p 27017:27017 -e MONGO_INITDB_DATABASE=unittest --detach mongo:${{ matrix.mongodb }} mongod --replSet rs --setParameter transactionLifetimeLimitSeconds=5 + + if [ "${{ matrix.mongodb }}" = "4.4" ]; then MONGOSH_BIN="mongo"; else MONGOSH_BIN="mongosh"; fi + until docker exec --tty mongodb $MONGOSH_BIN --eval "db.runCommand({ ping: 1 })"; do + sleep 1 + done + sudo docker exec --tty mongodb $MONGOSH_BIN --eval "rs.initiate({\"_id\":\"rs\",\"members\":[{\"_id\":0,\"host\":\"127.0.0.1:27017\" }]})" + + - name: "Show MongoDB server status" + run: | + if [ "${{ matrix.mongodb }}" = "4.4" ]; then MONGOSH_BIN="mongo"; else MONGOSH_BIN="mongosh"; fi + docker exec --tty mongodb $MONGOSH_BIN --eval "db.runCommand({ serverStatus: 1 })" + + - name: Setup cache environment + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ matrix.php }} + extensions: ${{ matrix.driver == 1 && env.MONGODB_EXT_V1 || env.MONGODB_EXT_V2 }} + key: "extcache-v1" + + - name: "Installing php" + uses: "shivammathur/setup-php@v2" + with: + php-version: ${{ matrix.php }} + extensions: "curl,mbstring,xdebug,${{ matrix.driver == 1 && env.MONGODB_EXT_V1 || env.MONGODB_EXT_V2 }}" + coverage: "xdebug" + tools: "composer" + + - name: "Show Docker version" + if: ${{ runner.debug }} + run: "docker version && env" + + - name: "Restrict Laravel version" + run: "composer require --dev --no-update 'laravel/framework:${{ matrix.laravel }}'" + + - name: "Download Composer cache dependencies from cache" + id: "composer-cache" + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: "Cache Composer dependencies" + uses: "actions/cache@v4" + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: "${{ matrix.os }}-composer-${{ hashFiles('**/composer.json') }}" + restore-keys: "${{ matrix.os }}-composer-" + + - name: "Install dependencies" + run: | + composer update --no-interaction \ + $([[ "${{ matrix.mode }}" == low-deps ]] && echo ' --prefer-lowest') \ + $([[ "${{ matrix.mode }}" == ignore-php-req ]] && echo ' --ignore-platform-req=php+') + - name: "Run tests" + run: | + export MONGODB_URI="mongodb://127.0.0.1:27017/?replicaSet=rs" + php -d zend.assertions=1 ./vendor/bin/phpunit --coverage-clover coverage.xml --exclude-group atlas-search diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index c79d91bb8..4d0eda3f9 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -2,7 +2,13 @@ name: "Coding Standards" on: push: + branches: + - "[0-9]+.[0-9x]+" + - "feature/*" pull_request: + branches: + - "[0-9]+.[0-9x]+" + - "feature/*" env: PHP_VERSION: "8.4" diff --git a/.github/workflows/merge-up.yml b/.github/workflows/merge-up.yml index 1ddbb7228..2ed3feaea 100644 --- a/.github/workflows/merge-up.yml +++ b/.github/workflows/merge-up.yml @@ -4,6 +4,7 @@ on: push: branches: - "[0-9]+.[0-9x]+" + - "feature/*" env: GH_TOKEN: ${{ secrets.MERGE_UP_TOKEN }} diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index e0c907953..8bc18e0f9 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -2,7 +2,13 @@ name: "Static Analysis" on: push: + branches: + - "[0-9]+.[0-9x]+" + - "feature/*" pull_request: + branches: + - "[0-9]+.[0-9x]+" + - "feature/*" workflow_call: inputs: ref: From b02b40c120be6dee9ebf63ff0b7bc42464c8fce4 Mon Sep 17 00:00:00 2001 From: Pauline Vos Date: Thu, 19 Jun 2025 15:31:48 +0200 Subject: [PATCH 19/28] Improve error handling on unsupported hybrid queries (#3404) Hybrid belongs-to-many relationships are not supported for query constraints. However, the support check was done downstream of a bunch of Eloquent stuff, resulting in the user getting an exception that didn't tell them anything about the usage being unsupported. This moves that check further up the chain so that the user is alerted to the lack of support before we do anything else. --- src/Helpers/QueriesRelationships.php | 27 +++++++++++++++++++++++- tests/HybridRelationsTest.php | 31 +++++++++++++++++++++------- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/Helpers/QueriesRelationships.php b/src/Helpers/QueriesRelationships.php index 1f1ffa34b..29d708e3c 100644 --- a/src/Helpers/QueriesRelationships.php +++ b/src/Helpers/QueriesRelationships.php @@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\HasOneOrMany; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Collection; +use LogicException; use MongoDB\Laravel\Eloquent\Model; use MongoDB\Laravel\Relations\MorphToMany; @@ -104,6 +105,8 @@ protected function isAcrossConnections(Relation $relation) */ public function addHybridHas(Relation $relation, $operator = '>=', $count = 1, $boolean = 'and', ?Closure $callback = null) { + $this->assertHybridRelationSupported($relation); + $hasQuery = $relation->getQuery(); if ($callback) { $hasQuery->callScope($callback); @@ -128,6 +131,26 @@ public function addHybridHas(Relation $relation, $operator = '>=', $count = 1, $ return $this->whereIn($this->getRelatedConstraintKey($relation), $relatedIds, $boolean, $not); } + /** + * @param Relation $relation + * + * @return void + * + * @throws Exception + */ + private function assertHybridRelationSupported(Relation $relation): void + { + if ( + $relation instanceof HasOneOrMany + || $relation instanceof BelongsTo + || ($relation instanceof BelongsToMany && ! $this->isAcrossConnections($relation)) + ) { + return; + } + + throw new LogicException(class_basename($relation) . ' is not supported for hybrid query constraints.'); + } + /** * @param Builder $hasQuery * @param Relation $relation @@ -213,6 +236,8 @@ protected function getConstrainedRelatedIds($relations, $operator, $count) */ protected function getRelatedConstraintKey(Relation $relation) { + $this->assertHybridRelationSupported($relation); + if ($relation instanceof HasOneOrMany) { return $relation->getLocalKeyName(); } @@ -221,7 +246,7 @@ protected function getRelatedConstraintKey(Relation $relation) return $relation->getForeignKeyName(); } - if ($relation instanceof BelongsToMany && ! $this->isAcrossConnections($relation)) { + if ($relation instanceof BelongsToMany) { return $this->model->getKeyName(); } diff --git a/tests/HybridRelationsTest.php b/tests/HybridRelationsTest.php index 08423007c..71fb0830b 100644 --- a/tests/HybridRelationsTest.php +++ b/tests/HybridRelationsTest.php @@ -78,7 +78,7 @@ public function testSqlRelations() $this->assertEquals('John Doe', $role->sqlUser->name); // MongoDB User - $user = new User(); + $user = new User(); $user->name = 'John Doe'; $user->save(); @@ -105,7 +105,7 @@ public function testSqlRelations() public function testHybridWhereHas() { - $user = new SqlUser(); + $user = new SqlUser(); $otherUser = new SqlUser(); $this->assertInstanceOf(SqlUser::class, $user); $this->assertInstanceOf(SQLiteConnection::class, $user->getConnection()); @@ -114,11 +114,11 @@ public function testHybridWhereHas() // SQL User $user->name = 'John Doe'; - $user->id = 2; + $user->id = 2; $user->save(); // Other user $otherUser->name = 'Other User'; - $otherUser->id = 3; + $otherUser->id = 3; $otherUser->save(); // Make sure they are created $this->assertIsInt($user->id); @@ -159,7 +159,7 @@ public function testHybridWhereHas() public function testHybridWith() { - $user = new SqlUser(); + $user = new SqlUser(); $otherUser = new SqlUser(); $this->assertInstanceOf(SqlUser::class, $user); $this->assertInstanceOf(SQLiteConnection::class, $user->getConnection()); @@ -168,11 +168,11 @@ public function testHybridWith() // SQL User $user->name = 'John Doe'; - $user->id = 2; + $user->id = 2; $user->save(); // Other user $otherUser->name = 'Other User'; - $otherUser->id = 3; + $otherUser->id = 3; $otherUser->save(); // Make sure they are created $this->assertIsInt($user->id); @@ -268,6 +268,23 @@ public function testHybridBelongsToMany() $this->assertEquals(1, $check->skills->count()); } + public function testQueryingHybridBelongsToManyRelationFails() + { + $user = new SqlUser(); + $this->assertInstanceOf(SQLiteConnection::class, $user->getConnection()); + + // Create Mysql Users + $user->fill(['name' => 'John Doe'])->save(); + $skill = Skill::query()->create(['name' => 'MongoDB']); + $user->skills()->save($skill); + + $this->expectExceptionMessage('BelongsToMany is not supported for hybrid query constraints.'); + + SqlUser::whereHas('skills', function ($query) { + return $query->where('name', 'LIKE', 'MongoDB'); + }); + } + public function testHybridMorphToManySqlModelToMongoModel() { // SqlModel -> MorphToMany -> MongoModel From 21d4d280702d119f2b33132e33d87f29c9936d5b Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Mon, 23 Jun 2025 09:44:43 -0400 Subject: [PATCH 20/28] DOCSP-50781: Update SoftDeletes references (#3413) --- docs/eloquent-models/model-class.txt | 2 +- docs/includes/eloquent-models/PlanetSoftDelete.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/eloquent-models/model-class.txt b/docs/eloquent-models/model-class.txt index 6f686e88a..da820b18c 100644 --- a/docs/eloquent-models/model-class.txt +++ b/docs/eloquent-models/model-class.txt @@ -168,7 +168,7 @@ Eloquent includes a soft delete feature that changes the behavior of the database. It sets a timestamp on the ``deleted_at`` field to exclude it from retrieve operations automatically. -To enable soft deletes on a class, add the ``MongoDB\Laravel\Eloquent\SoftDeletes`` +To enable soft deletes on a class, add the ``Illuminate\Database\Eloquent\SoftDeletes`` trait as shown in the following code example: .. literalinclude:: /includes/eloquent-models/PlanetSoftDelete.php diff --git a/docs/includes/eloquent-models/PlanetSoftDelete.php b/docs/includes/eloquent-models/PlanetSoftDelete.php index 05d106206..70ccba24b 100644 --- a/docs/includes/eloquent-models/PlanetSoftDelete.php +++ b/docs/includes/eloquent-models/PlanetSoftDelete.php @@ -2,8 +2,8 @@ namespace App\Models; +use Illuminate\Database\Eloquent\SoftDeletes; use MongoDB\Laravel\Eloquent\Model; -use MongoDB\Laravel\Eloquent\SoftDeletes; class Planet extends Model { From 01d7af93969dca0d6d6eeb8ea4abeaa22b1731a6 Mon Sep 17 00:00:00 2001 From: Pauline Vos Date: Tue, 1 Jul 2025 12:35:18 +0200 Subject: [PATCH 21/28] Deprecate Mongo soft deletes trait (#3408) In favor of the Laravel one. Ours is now obsolete; the SoftDeletes trait is only necessary to remove the call to qualifyColumn in the parent trait. But the DocumentModel::qualifyColumn is already disabled --- src/Eloquent/MassPrunable.php | 1 + src/Eloquent/SoftDeletes.php | 8 ++++++++ tests/Models/Soft.php | 2 +- tests/Scout/Models/ScoutUser.php | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Eloquent/MassPrunable.php b/src/Eloquent/MassPrunable.php index 98e947842..ecf033a3b 100644 --- a/src/Eloquent/MassPrunable.php +++ b/src/Eloquent/MassPrunable.php @@ -5,6 +5,7 @@ namespace MongoDB\Laravel\Eloquent; use Illuminate\Database\Eloquent\MassPrunable as EloquentMassPrunable; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Events\ModelsPruned; use function class_uses_recursive; diff --git a/src/Eloquent/SoftDeletes.php b/src/Eloquent/SoftDeletes.php index 135c55dcf..438219f3c 100644 --- a/src/Eloquent/SoftDeletes.php +++ b/src/Eloquent/SoftDeletes.php @@ -4,6 +4,14 @@ namespace MongoDB\Laravel\Eloquent; +use function sprintf; +use function trigger_error; + +use const E_USER_DEPRECATED; + +trigger_error(sprintf('Since mongodb/laravel-mongodb:5.5, trait "%s" is deprecated, use "%s" instead.', SoftDeletes::class, \Illuminate\Database\Eloquent\SoftDeletes::class), E_USER_DEPRECATED); + +/** @deprecated since mongodb/laravel-mongodb:5.5, use \Illuminate\Database\Eloquent\SoftDeletes instead */ trait SoftDeletes { use \Illuminate\Database\Eloquent\SoftDeletes; diff --git a/tests/Models/Soft.php b/tests/Models/Soft.php index f887d05a9..999d13fd4 100644 --- a/tests/Models/Soft.php +++ b/tests/Models/Soft.php @@ -6,10 +6,10 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; use MongoDB\Laravel\Eloquent\Builder; use MongoDB\Laravel\Eloquent\DocumentModel; use MongoDB\Laravel\Eloquent\MassPrunable; -use MongoDB\Laravel\Eloquent\SoftDeletes; /** @property Carbon $deleted_at */ class Soft extends Model diff --git a/tests/Scout/Models/ScoutUser.php b/tests/Scout/Models/ScoutUser.php index 50fa39a94..581606f75 100644 --- a/tests/Scout/Models/ScoutUser.php +++ b/tests/Scout/Models/ScoutUser.php @@ -5,11 +5,11 @@ namespace MongoDB\Laravel\Tests\Scout\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\SQLiteBuilder; use Illuminate\Support\Facades\Schema; use Laravel\Scout\Searchable; -use MongoDB\Laravel\Eloquent\SoftDeletes; use function assert; From 08d21aa8e5ca0f169547563ff6a61e86d9f3fd2d Mon Sep 17 00:00:00 2001 From: Pauline Vos Date: Wed, 2 Jul 2025 08:49:00 +0200 Subject: [PATCH 22/28] Add Mongo builder mixin to `DocumentModel` (#3417) It helps with autocompletion as IDEs will recognize Mongo builder methods that are not present on the base builder. --- src/Eloquent/Builder.php | 2 +- src/Eloquent/DocumentModel.php | 1 + tests/Models/Anniversary.php | 6 ------ tests/Models/HiddenAnimal.php | 6 ------ 4 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Eloquent/Builder.php b/src/Eloquent/Builder.php index 7dba7b7ab..998baef05 100644 --- a/src/Eloquent/Builder.php +++ b/src/Eloquent/Builder.php @@ -69,7 +69,7 @@ class Builder extends EloquentBuilder ]; /** - * @return ($function is null ? AggregationBuilder : self) + * @return ($function is null ? AggregationBuilder : $this) * * @inheritdoc */ diff --git a/src/Eloquent/DocumentModel.php b/src/Eloquent/DocumentModel.php index 965b1a444..f8d399e62 100644 --- a/src/Eloquent/DocumentModel.php +++ b/src/Eloquent/DocumentModel.php @@ -50,6 +50,7 @@ use function strlen; use function var_export; +/** @mixin Builder */ trait DocumentModel { use HybridRelations; diff --git a/tests/Models/Anniversary.php b/tests/Models/Anniversary.php index fb78c9a55..c37196c16 100644 --- a/tests/Models/Anniversary.php +++ b/tests/Models/Anniversary.php @@ -6,16 +6,10 @@ use Illuminate\Database\Eloquent\Model; use MongoDB\Laravel\Eloquent\DocumentModel; -use MongoDB\Laravel\Eloquent\Model as Eloquent; -use MongoDB\Laravel\Query\Builder; /** * @property string $name * @property string $anniversary - * @mixin Eloquent - * @method static Builder create(...$values) - * @method static Builder truncate() - * @method static Eloquent sole(...$parameters) */ class Anniversary extends Model { diff --git a/tests/Models/HiddenAnimal.php b/tests/Models/HiddenAnimal.php index 240238da0..f6217177c 100644 --- a/tests/Models/HiddenAnimal.php +++ b/tests/Models/HiddenAnimal.php @@ -6,17 +6,11 @@ use Illuminate\Database\Eloquent\Model; use MongoDB\Laravel\Eloquent\DocumentModel; -use MongoDB\Laravel\Eloquent\Model as Eloquent; -use MongoDB\Laravel\Query\Builder; /** * @property string $name * @property string $country * @property bool $can_be_eaten - * @mixin Eloquent - * @method static Builder create(...$values) - * @method static Builder truncate() - * @method static Eloquent sole(...$parameters) */ final class HiddenAnimal extends Model { From 63da42ce1d66f24cb914300a6f526308055fd351 Mon Sep 17 00:00:00 2001 From: Pauline Vos Date: Thu, 3 Jul 2025 15:43:46 +0200 Subject: [PATCH 23/28] PHPORM-146: Add override attribute everywhere (#3412) * Add `#[Override]` attributes to `Query\Builder` to keep track of the methods we're overriding on the base builder * Add `#[Override]` attribute to `MongoBatchRepository` * Add `#[Override]` attribute to `MongoLock` * Add attribute to Eloquent builder * Add `#[Override]` attribute to `MongoQueue` * Add `#[Override]` attribute to relationship classes * Add `#[Override]` attribute to `Schema` namespace classes * Add attribute to session handler * Add `#[Override]` attribute to Validation namespace * Add `#[Override]` attribute to `CommandSubscriber` * Add `#[Override] attribute to `Connection` * Add `#[Override]` attribute to `ServiceProvider` implementations --- src/Bus/MongoBatchRepository.php | 2 + src/Cache/MongoLock.php | 1 + src/CommandSubscriber.php | 4 ++ src/Connection.php | 11 +++++ src/Eloquent/Builder.php | 14 ++++-- src/MongoDBBusServiceProvider.php | 4 ++ src/MongoDBServiceProvider.php | 2 + src/Query/Builder.php | 49 +++++++++++++++++++- src/Queue/MongoQueue.php | 10 +++- src/Relations/BelongsTo.php | 6 +++ src/Relations/BelongsToMany.php | 20 ++++++-- src/Relations/EmbedsOneOrMany.php | 19 ++++---- src/Relations/HasMany.php | 8 ++-- src/Relations/HasOne.php | 12 ++--- src/Relations/MorphMany.php | 9 +--- src/Relations/MorphTo.php | 12 ++--- src/Relations/MorphToMany.php | 29 ++++++------ src/Schema/Blueprint.php | 9 ++++ src/Schema/Builder.php | 33 +++++++++++-- src/Session/MongoDbSessionHandler.php | 6 +++ src/Validation/DatabasePresenceVerifier.php | 26 ++--------- src/Validation/ValidationServiceProvider.php | 2 + 22 files changed, 208 insertions(+), 80 deletions(-) diff --git a/src/Bus/MongoBatchRepository.php b/src/Bus/MongoBatchRepository.php index 2656bbc30..afd95e0b2 100644 --- a/src/Bus/MongoBatchRepository.php +++ b/src/Bus/MongoBatchRepository.php @@ -216,6 +216,7 @@ public function prune(DateTimeInterface $before): int } /** Prune all the unfinished entries older than the given date. */ + #[Override] public function pruneUnfinished(DateTimeInterface $before): int { $result = $this->collection->deleteMany( @@ -229,6 +230,7 @@ public function pruneUnfinished(DateTimeInterface $before): int } /** Prune all the cancelled entries older than the given date. */ + #[Override] public function pruneCancelled(DateTimeInterface $before): int { $result = $this->collection->deleteMany( diff --git a/src/Cache/MongoLock.php b/src/Cache/MongoLock.php index d273b4d99..50d04c7ce 100644 --- a/src/Cache/MongoLock.php +++ b/src/Cache/MongoLock.php @@ -41,6 +41,7 @@ public function __construct( /** * Attempt to acquire the lock. */ + #[Override] public function acquire(): bool { // The lock can be acquired if: it doesn't exist, it has expired, diff --git a/src/CommandSubscriber.php b/src/CommandSubscriber.php index 5daa6e97a..1cad23280 100644 --- a/src/CommandSubscriber.php +++ b/src/CommandSubscriber.php @@ -7,6 +7,7 @@ use MongoDB\Driver\Monitoring\CommandStartedEvent; use MongoDB\Driver\Monitoring\CommandSubscriber as CommandSubscriberInterface; use MongoDB\Driver\Monitoring\CommandSucceededEvent; +use Override; use function get_object_vars; use function in_array; @@ -21,16 +22,19 @@ public function __construct(private Connection $connection) { } + #[Override] public function commandStarted(CommandStartedEvent $event): void { $this->commands[$event->getOperationId()] = $event; } + #[Override] public function commandFailed(CommandFailedEvent $event): void { $this->logQuery($event); } + #[Override] public function commandSucceeded(CommandSucceededEvent $event): void { $this->logQuery($event); diff --git a/src/Connection.php b/src/Connection.php index 3fa99e94b..780cad321 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -16,6 +16,7 @@ use MongoDB\Driver\ReadPreference; use MongoDB\Laravel\Concerns\ManagesTransactions; use OutOfBoundsException; +use Override; use Throwable; use function filter_var; @@ -95,6 +96,7 @@ public function __construct(array $config) * * @return Query\Builder */ + #[Override] public function table($table, $as = null) { $query = new Query\Builder($this, $this->getQueryGrammar(), $this->getPostProcessor()); @@ -115,6 +117,7 @@ public function getCollection($name): Collection } /** @inheritdoc */ + #[Override] public function getSchemaBuilder() { return new Schema\Builder($this); @@ -172,6 +175,8 @@ public function getClient(): ?Client return $this->connection; } + /** @inheritdoc */ + #[Override] public function enableQueryLog() { parent::enableQueryLog(); @@ -182,6 +187,7 @@ public function enableQueryLog() } } + #[Override] public function disableQueryLog() { parent::disableQueryLog(); @@ -192,6 +198,7 @@ public function disableQueryLog() } } + #[Override] protected function withFreshQueryLog($callback) { try { @@ -340,6 +347,7 @@ protected function getDsn(array $config): string } /** @inheritdoc */ + #[Override] public function getDriverName() { return 'mongodb'; @@ -352,12 +360,14 @@ public function getDriverTitle() } /** @inheritdoc */ + #[Override] protected function getDefaultPostProcessor() { return new Query\Processor(); } /** @inheritdoc */ + #[Override] protected function getDefaultQueryGrammar() { // Argument added in Laravel 12 @@ -365,6 +375,7 @@ protected function getDefaultQueryGrammar() } /** @inheritdoc */ + #[Override] protected function getDefaultSchemaGrammar() { // Argument added in Laravel 12 diff --git a/src/Eloquent/Builder.php b/src/Eloquent/Builder.php index 998baef05..5d4018f9d 100644 --- a/src/Eloquent/Builder.php +++ b/src/Eloquent/Builder.php @@ -18,6 +18,7 @@ use MongoDB\Laravel\Helpers\QueriesRelationships; use MongoDB\Laravel\Query\AggregationBuilder; use MongoDB\Model\BSONDocument; +use Override; use function array_key_exists; use function array_map; @@ -127,7 +128,12 @@ public function vectorSearch( return $this->model->hydrate($results->all()); } - /** @inheritdoc */ + /** + * @param array $options + * + * @inheritdoc + */ + #[Override] public function update(array $values, array $options = []) { // Intercept operations on embedded models and delegate logic @@ -270,6 +276,7 @@ public function raw($value = null) return $results; } + #[Override] public function firstOrCreate(array $attributes = [], array $values = []) { $instance = (clone $this)->where($attributes)->first(); @@ -285,6 +292,7 @@ public function firstOrCreate(array $attributes = [], array $values = []) return $this->createOrFirst($attributes, $values); } + #[Override] public function createOrFirst(array $attributes = [], array $values = []) { // The duplicate key error would abort the transaction. Using the regular firstOrCreate in that case. @@ -308,9 +316,8 @@ public function createOrFirst(array $attributes = [], array $values = []) * TODO Remove if https://github.com/laravel/framework/commit/6484744326531829341e1ff886cc9b628b20d73e * will be reverted * Issue in laravel/frawework https://github.com/laravel/framework/issues/27791. - * - * @return array */ + #[Override] protected function addUpdatedAtColumn(array $values) { if (! $this->model->usesTimestamps() || $this->model->getUpdatedAtColumn() === null) { @@ -332,6 +339,7 @@ public function getConnection(): Connection } /** @inheritdoc */ + #[Override] protected function ensureOrderForCursorPagination($shouldReverse = false) { if (empty($this->query->orders)) { diff --git a/src/MongoDBBusServiceProvider.php b/src/MongoDBBusServiceProvider.php index d3d6f25fc..ab0afb588 100644 --- a/src/MongoDBBusServiceProvider.php +++ b/src/MongoDBBusServiceProvider.php @@ -10,6 +10,7 @@ use Illuminate\Support\ServiceProvider; use InvalidArgumentException; use MongoDB\Laravel\Bus\MongoBatchRepository; +use Override; use function sprintf; @@ -18,6 +19,7 @@ class MongoDBBusServiceProvider extends ServiceProvider implements DeferrablePro /** * Register the service provider. */ + #[Override] public function register() { $this->app->singleton(MongoBatchRepository::class, function (Container $app) { @@ -46,6 +48,8 @@ public function register() }); } + /** @inheritdoc */ + #[Override] public function provides() { return [ diff --git a/src/MongoDBServiceProvider.php b/src/MongoDBServiceProvider.php index a51a63919..644eb7a56 100644 --- a/src/MongoDBServiceProvider.php +++ b/src/MongoDBServiceProvider.php @@ -24,6 +24,7 @@ use MongoDB\Laravel\Queue\MongoConnector; use MongoDB\Laravel\Scout\ScoutEngine; use MongoDB\Laravel\Session\MongoDbSessionHandler; +use Override; use RuntimeException; use function assert; @@ -47,6 +48,7 @@ public function boot() /** * Register the service provider. */ + #[Override] public function register() { // Add database driver. diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 8d57ba4c8..6fb38fba1 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -243,12 +243,14 @@ public function hint($index) } /** @inheritdoc */ + #[Override] public function find($id, $columns = []) { return $this->where('_id', '=', $this->convertKey($id))->first($columns); } /** @inheritdoc */ + #[Override] public function value($column) { $result = (array) $this->first([$column]); @@ -257,12 +259,14 @@ public function value($column) } /** @inheritdoc */ + #[Override] public function get($columns = []) { return $this->getFresh($columns); } /** @inheritdoc */ + #[Override] public function cursor($columns = []) { $result = $this->getFresh($columns, true); @@ -579,6 +583,7 @@ public function generateCacheKey() } /** @return ($function is null ? AggregationBuilder : mixed) */ + #[Override] public function aggregate($function = null, $columns = ['*']) { assert(is_array($columns), new TypeError(sprintf('Argument #2 ($columns) must be of type array, %s given', get_debug_type($columns)))); @@ -640,9 +645,10 @@ public function aggregate($function = null, $columns = ['*']) } /** - * {@inheritDoc} + * @param string $function + * @param array $columns * - * @see \Illuminate\Database\Query\Builder::aggregateByGroup() + * @return mixed */ public function aggregateByGroup(string $function, array $columns = ['*']) { @@ -654,6 +660,7 @@ public function aggregateByGroup(string $function, array $columns = ['*']) } /** @inheritdoc */ + #[Override] public function exists() { return $this->first(['id']) !== null; @@ -676,6 +683,7 @@ public function distinct($column = false) * * @inheritdoc */ + #[Override] public function orderBy($column, $direction = 'asc') { if (is_string($direction)) { @@ -697,6 +705,7 @@ public function orderBy($column, $direction = 'asc') } /** @inheritdoc */ + #[Override] public function whereBetween($column, iterable $values, $boolean = 'and', $not = false) { $type = 'between'; @@ -721,6 +730,7 @@ public function whereBetween($column, iterable $values, $boolean = 'and', $not = } /** @inheritdoc */ + #[Override] public function insert(array $values) { // Allow empty insert batch for consistency with Eloquent SQL @@ -755,6 +765,7 @@ public function insert(array $values) } /** @inheritdoc */ + #[Override] public function insertGetId(array $values, $sequence = null) { $options = $this->inheritConnectionOptions(); @@ -774,6 +785,7 @@ public function insertGetId(array $values, $sequence = null) } /** @inheritdoc */ + #[Override] public function update(array $values, array $options = []) { // Use $set as default operator for field names that are not in an operator @@ -790,6 +802,7 @@ public function update(array $values, array $options = []) } /** @inheritdoc */ + #[Override] public function upsert(array $values, $uniqueBy, $update = null): int { if ($values === []) { @@ -836,6 +849,7 @@ public function upsert(array $values, $uniqueBy, $update = null): int } /** @inheritdoc */ + #[Override] public function increment($column, $amount = 1, array $extra = [], array $options = []) { $query = ['$inc' => [(string) $column => $amount]]; @@ -856,6 +870,12 @@ public function increment($column, $amount = 1, array $extra = [], array $option return $this->performUpdate($query, $options); } + /** + * @param array $options + * + * @inheritdoc + */ + #[Override] public function incrementEach(array $columns, array $extra = [], array $options = []) { $stage['$addFields'] = $extra; @@ -873,12 +893,14 @@ public function incrementEach(array $columns, array $extra = [], array $options } /** @inheritdoc */ + #[Override] public function decrement($column, $amount = 1, array $extra = [], array $options = []) { return $this->increment($column, -1 * $amount, $extra, $options); } /** @inheritdoc */ + #[Override] public function decrementEach(array $columns, array $extra = [], array $options = []) { $decrement = []; @@ -932,6 +954,7 @@ public function divide($column, $amount, array $extra = [], array $options = []) } /** @inheritdoc */ + #[Override] public function pluck($column, $key = null) { $results = $this->get($key === null ? [$column] : [$column, $key]); @@ -942,6 +965,7 @@ public function pluck($column, $key = null) } /** @inheritdoc */ + #[Override] public function delete($id = null) { // If an ID is passed to the method, we will set the where clause to check @@ -973,6 +997,7 @@ public function delete($id = null) } /** @inheritdoc */ + #[Override] public function from($collection, $as = null) { if ($collection) { @@ -1012,6 +1037,7 @@ public function lists($column, $key = null) * * @template T */ + #[Override] public function raw($value = null) { // Execute the closure on the mongodb collection @@ -1114,11 +1140,13 @@ public function drop($columns) * * @inheritdoc */ + #[Override] public function newQuery() { return new static($this->connection, $this->grammar, $this->processor); } + #[Override] public function runPaginationCountQuery($columns = ['*']) { if ($this->distinct) { @@ -1201,6 +1229,7 @@ public function convertKey($id) * * @return $this */ + #[Override] public function where($column, $operator = null, $value = null, $boolean = 'and') { $params = func_get_args(); @@ -1714,6 +1743,7 @@ private function inheritConnectionOptions(array $options = []): array } /** @inheritdoc */ + #[Override] public function __call($method, $parameters) { if ($method === 'unset') { @@ -1724,90 +1754,105 @@ public function __call($method, $parameters) } /** @internal This method is not supported by MongoDB. */ + #[Override] public function toSql() { throw new BadMethodCallException('This method is not supported by MongoDB. Try "toMql()" instead.'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function toRawSql() { throw new BadMethodCallException('This method is not supported by MongoDB. Try "toMql()" instead.'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function whereColumn($first, $operator = null, $second = null, $boolean = 'and') { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function whereFullText($columns, $value, array $options = [], $boolean = 'and') { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function groupByRaw($sql, array $bindings = []) { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function orderByRaw($sql, $bindings = []) { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function unionAll($query) { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function union($query, $all = false) { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function having($column, $operator = null, $value = null, $boolean = 'and') { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function havingRaw($sql, array $bindings = [], $boolean = 'and') { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function havingBetween($column, iterable $values, $boolean = 'and', $not = false) { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function whereIntegerInRaw($column, $values, $boolean = 'and', $not = false) { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function orWhereIntegerInRaw($column, $values) { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function whereIntegerNotInRaw($column, $values, $boolean = 'and') { throw new BadMethodCallException('This method is not supported by MongoDB'); } /** @internal This method is not supported by MongoDB. */ + #[Override] public function orWhereIntegerNotInRaw($column, $values, $boolean = 'and') { throw new BadMethodCallException('This method is not supported by MongoDB'); diff --git a/src/Queue/MongoQueue.php b/src/Queue/MongoQueue.php index 7810aab92..1e353bd65 100644 --- a/src/Queue/MongoQueue.php +++ b/src/Queue/MongoQueue.php @@ -8,6 +8,7 @@ use Illuminate\Queue\DatabaseQueue; use MongoDB\Laravel\Connection; use MongoDB\Operation\FindOneAndUpdate; +use Override; use stdClass; class MongoQueue extends DatabaseQueue @@ -34,7 +35,12 @@ public function __construct(Connection $database, $table, $default = 'default', $this->retryAfter = $retryAfter; } - /** @inheritdoc */ + /** + * @return MongoJob|null + * + * @inheritdoc + */ + #[Override] public function pop($queue = null) { $queue = $this->getQueue($queue); @@ -138,12 +144,14 @@ protected function releaseJob($id, $attempts) } /** @inheritdoc */ + #[Override] public function deleteReserved($queue, $id) { $this->database->table($this->table)->where('_id', $id)->delete(); } /** @inheritdoc */ + #[Override] public function deleteAndRelease($queue, $job, $delay) { $this->deleteReserved($queue, $job->getJobId()); diff --git a/src/Relations/BelongsTo.php b/src/Relations/BelongsTo.php index 93eb11f8e..15447c219 100644 --- a/src/Relations/BelongsTo.php +++ b/src/Relations/BelongsTo.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo as EloquentBelongsTo; +use Override; /** * @template TRelatedModel of Model @@ -26,6 +27,7 @@ public function getHasCompareKey() } /** @inheritdoc */ + #[Override] public function addConstraints() { if (static::$constraints) { @@ -37,6 +39,7 @@ public function addConstraints() } /** @inheritdoc */ + #[Override] public function addEagerConstraints(array $models) { // We'll grab the primary key name of the related models since it could be set to @@ -46,6 +49,7 @@ public function addEagerConstraints(array $models) } /** @inheritdoc */ + #[Override] public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { return $query; @@ -58,11 +62,13 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, * * @return string */ + #[Override] protected function whereInMethod(Model $model, $key) { return 'whereIn'; } + #[Override] public function getQualifiedForeignKeyName(): string { return $this->foreignKey; diff --git a/src/Relations/BelongsToMany.php b/src/Relations/BelongsToMany.php index 042ec22ce..8978483ec 100644 --- a/src/Relations/BelongsToMany.php +++ b/src/Relations/BelongsToMany.php @@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany as EloquentBelongsToMany; use Illuminate\Support\Arr; use MongoDB\Laravel\Eloquent\Model as DocumentModel; +use Override; use function array_diff; use function array_keys; @@ -39,12 +40,14 @@ public function getHasCompareKey() } /** @inheritdoc */ + #[Override] public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { return $query; } /** @inheritdoc */ + #[Override] protected function hydratePivotRelation(array $models) { // Do nothing. @@ -61,12 +64,14 @@ protected function getSelectColumns(array $columns = ['*']) } /** @inheritdoc */ + #[Override] protected function shouldSelect(array $columns = ['*']) { return $columns; } /** @inheritdoc */ + #[Override] public function addConstraints() { if (static::$constraints) { @@ -89,6 +94,7 @@ protected function setWhere() } /** @inheritdoc */ + #[Override] public function save(Model $model, array $pivotAttributes = [], $touch = true) { $model->save(['touch' => false]); @@ -99,6 +105,7 @@ public function save(Model $model, array $pivotAttributes = [], $touch = true) } /** @inheritdoc */ + #[Override] public function create(array $attributes = [], array $joining = [], $touch = true) { $instance = $this->related->newInstance($attributes); @@ -114,6 +121,7 @@ public function create(array $attributes = [], array $joining = [], $touch = tru } /** @inheritdoc */ + #[Override] public function sync($ids, $detaching = true) { $changes = [ @@ -177,6 +185,7 @@ public function sync($ids, $detaching = true) } /** @inheritdoc */ + #[Override] public function updateExistingPivot($id, array $attributes, $touch = true) { // Do nothing, we have no pivot table. @@ -184,6 +193,7 @@ public function updateExistingPivot($id, array $attributes, $touch = true) } /** @inheritdoc */ + #[Override] public function attach($id, array $attributes = [], $touch = true) { if ($id instanceof Model) { @@ -224,6 +234,7 @@ public function attach($id, array $attributes = [], $touch = true) } /** @inheritdoc */ + #[Override] public function detach($ids = [], $touch = true) { if ($ids instanceof Model) { @@ -264,6 +275,7 @@ public function detach($ids = [], $touch = true) } /** @inheritdoc */ + #[Override] protected function buildDictionary(Collection $results) { $foreign = $this->foreignPivotKey; @@ -283,6 +295,7 @@ protected function buildDictionary(Collection $results) } /** @inheritdoc */ + #[Override] public function newPivotQuery() { return $this->newRelatedQuery(); @@ -309,12 +322,14 @@ public function getForeignKey() } /** @inheritdoc */ + #[Override] public function getQualifiedForeignPivotKeyName() { return $this->foreignPivotKey; } /** @inheritdoc */ + #[Override] public function getQualifiedRelatedPivotKeyName() { return $this->relatedPivotKey; @@ -323,10 +338,9 @@ public function getQualifiedRelatedPivotKeyName() /** * Get the name of the "where in" method for eager loading. * - * @param string $key - * - * @return string + * @inheritdoc */ + #[Override] protected function whereInMethod(Model $model, $key) { return 'whereIn'; diff --git a/src/Relations/EmbedsOneOrMany.php b/src/Relations/EmbedsOneOrMany.php index a46593cf4..cc9376dcc 100644 --- a/src/Relations/EmbedsOneOrMany.php +++ b/src/Relations/EmbedsOneOrMany.php @@ -12,6 +12,7 @@ use Illuminate\Database\Query\Expression; use MongoDB\Driver\Exception\LogicException; use MongoDB\Laravel\Eloquent\Model as DocumentModel; +use Override; use Throwable; use function array_merge; @@ -78,6 +79,7 @@ public function __construct(Builder $query, Model $parent, Model $related, strin } /** @inheritdoc */ + #[Override] public function addConstraints() { if (static::$constraints) { @@ -86,12 +88,14 @@ public function addConstraints() } /** @inheritdoc */ + #[Override] public function addEagerConstraints(array $models) { // There are no eager loading constraints. } /** @inheritdoc */ + #[Override] public function match(array $models, Collection $results, $relation) { foreach ($models as $model) { @@ -105,13 +109,7 @@ public function match(array $models, Collection $results, $relation) return $models; } - /** - * Shorthand to get the results of the relationship. - * - * @param array $columns - * - * @return Collection - */ + #[Override] public function get($columns = ['*']) { return $this->getResults(); @@ -324,6 +322,7 @@ protected function getParentRelation() } /** @inheritdoc */ + #[Override] public function getQuery() { // Because we are sharing this relation instance to models, we need @@ -332,6 +331,7 @@ public function getQuery() } /** @inheritdoc */ + #[Override] public function toBase() { // Because we are sharing this relation instance to models, we need @@ -367,6 +367,7 @@ protected function getPathHierarchy($glue = '.') } /** @inheritdoc */ + #[Override] public function getQualifiedParentKeyName() { $parentRelation = $this->getParentRelation(); @@ -425,10 +426,10 @@ public function getQualifiedForeignKeyName() * Get the name of the "where in" method for eager loading. * * @param EloquentModel $model - * @param string $key * - * @return string + * @inheritdoc */ + #[Override] protected function whereInMethod(EloquentModel $model, $key) { return 'whereIn'; diff --git a/src/Relations/HasMany.php b/src/Relations/HasMany.php index c8e7e0590..052230495 100644 --- a/src/Relations/HasMany.php +++ b/src/Relations/HasMany.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany as EloquentHasMany; +use Override; /** * @template TRelatedModel of Model @@ -20,6 +21,7 @@ class HasMany extends EloquentHasMany * * @return string */ + #[Override] public function getForeignKeyName() { return $this->foreignKey; @@ -36,6 +38,7 @@ public function getHasCompareKey() } /** @inheritdoc */ + #[Override] public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { $foreignKey = $this->getHasCompareKey(); @@ -46,10 +49,9 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, /** * Get the name of the "where in" method for eager loading. * - * @param string $key - * - * @return string + * @inheritdoc */ + #[Override] protected function whereInMethod(Model $model, $key) { return 'whereIn'; diff --git a/src/Relations/HasOne.php b/src/Relations/HasOne.php index ea26761d3..bfa297c4e 100644 --- a/src/Relations/HasOne.php +++ b/src/Relations/HasOne.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasOne as EloquentHasOne; +use Override; /** * @template TRelatedModel of Model @@ -20,6 +21,7 @@ class HasOne extends EloquentHasOne * * @return string */ + #[Override] public function getForeignKeyName() { return $this->foreignKey; @@ -36,6 +38,7 @@ public function getHasCompareKey() } /** @inheritdoc */ + #[Override] public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { $foreignKey = $this->getForeignKeyName(); @@ -43,13 +46,8 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, return $query->select($foreignKey)->where($foreignKey, 'exists', true); } - /** - * Get the name of the "where in" method for eager loading. - * - * @param string $key - * - * @return string - */ + /** Get the name of the "where in" method for eager loading. */ + #[Override] protected function whereInMethod(Model $model, $key) { return 'whereIn'; diff --git a/src/Relations/MorphMany.php b/src/Relations/MorphMany.php index 5f395950f..925ebcfa9 100644 --- a/src/Relations/MorphMany.php +++ b/src/Relations/MorphMany.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphMany as EloquentMorphMany; +use Override; /** * @template TRelatedModel of Model @@ -14,13 +15,7 @@ */ class MorphMany extends EloquentMorphMany { - /** - * Get the name of the "where in" method for eager loading. - * - * @param string $key - * - * @return string - */ + #[Override] protected function whereInMethod(Model $model, $key) { return 'whereIn'; diff --git a/src/Relations/MorphTo.php b/src/Relations/MorphTo.php index 4888b2d97..9f1bf1441 100644 --- a/src/Relations/MorphTo.php +++ b/src/Relations/MorphTo.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphTo as EloquentMorphTo; +use Override; /** * @template TRelatedModel of Model @@ -15,6 +16,7 @@ class MorphTo extends EloquentMorphTo { /** @inheritdoc */ + #[Override] public function addConstraints() { if (static::$constraints) { @@ -30,6 +32,7 @@ public function addConstraints() } /** @inheritdoc */ + #[Override] protected function getResultsByType($type) { $instance = $this->createModelByType($type); @@ -41,13 +44,8 @@ protected function getResultsByType($type) return $query->whereIn($key, $this->gatherKeysByType($type, $instance->getKeyType()))->get(); } - /** - * Get the name of the "where in" method for eager loading. - * - * @param string $key - * - * @return string - */ + /** Get the name of the "where in" method for eager loading. */ + #[Override] protected function whereInMethod(Model $model, $key) { return 'whereIn'; diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index a1514d235..724dad912 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\MorphToMany as EloquentMorphToMany; use Illuminate\Support\Arr; use MongoDB\BSON\ObjectId; +use Override; use function array_diff; use function array_key_exists; @@ -31,25 +32,25 @@ */ class MorphToMany extends EloquentMorphToMany { - /** @inheritdoc */ + #[Override] public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { return $query; } - /** @inheritdoc */ + #[Override] protected function hydratePivotRelation(array $models) { // Do nothing. } - /** @inheritdoc */ + #[Override] protected function shouldSelect(array $columns = ['*']) { return $columns; } - /** @inheritdoc */ + #[Override] public function addConstraints() { if (static::$constraints) { @@ -57,7 +58,7 @@ public function addConstraints() } } - /** @inheritdoc */ + #[Override] public function addEagerConstraints(array $models) { // To load relation's data, we act normally on MorphToMany relation, @@ -102,6 +103,7 @@ protected function setWhere() } /** @inheritdoc */ + #[Override] public function save(Model $model, array $pivotAttributes = [], $touch = true) { $model->save(['touch' => false]); @@ -112,6 +114,7 @@ public function save(Model $model, array $pivotAttributes = [], $touch = true) } /** @inheritdoc */ + #[Override] public function create(array $attributes = [], array $joining = [], $touch = true) { $instance = $this->related->newInstance($attributes); @@ -127,6 +130,7 @@ public function create(array $attributes = [], array $joining = [], $touch = tru } /** @inheritdoc */ + #[Override] public function sync($ids, $detaching = true) { $changes = [ @@ -203,12 +207,14 @@ public function sync($ids, $detaching = true) } /** @inheritdoc */ + #[Override] public function updateExistingPivot($id, array $attributes, $touch = true): void { // Do nothing, we have no pivot table. } /** @inheritdoc */ + #[Override] public function attach($id, array $attributes = [], $touch = true) { if ($id instanceof Model) { @@ -302,6 +308,7 @@ public function attach($id, array $attributes = [], $touch = true) } /** @inheritdoc */ + #[Override] public function detach($ids = [], $touch = true) { if ($ids instanceof Model) { @@ -376,6 +383,7 @@ public function detach($ids = [], $touch = true) } /** @inheritdoc */ + #[Override] protected function buildDictionary(Collection $results) { $foreign = $this->foreignPivotKey; @@ -403,6 +411,7 @@ protected function buildDictionary(Collection $results) } /** @inheritdoc */ + #[Override] public function newPivotQuery() { return $this->newRelatedQuery(); @@ -418,19 +427,13 @@ public function newRelatedQuery() return $this->related->newQuery(); } - /** @inheritdoc */ + #[Override] public function getQualifiedRelatedPivotKeyName() { return $this->relatedPivotKey; } - /** - * Get the name of the "where in" method for eager loading. - * - * @param string $key - * - * @return string - */ + #[Override] protected function whereInMethod(Model $model, $key) { return 'whereIn'; diff --git a/src/Schema/Blueprint.php b/src/Schema/Blueprint.php index 1ae46cf6c..24e23d50e 100644 --- a/src/Schema/Blueprint.php +++ b/src/Schema/Blueprint.php @@ -7,6 +7,7 @@ use Illuminate\Database\Schema\Blueprint as BaseBlueprint; use MongoDB\Collection; use MongoDB\Laravel\Connection; +use Override; use function array_flip; use function array_merge; @@ -38,6 +39,7 @@ class Blueprint extends BaseBlueprint protected $columns = []; /** @inheritdoc */ + #[Override] public function index($columns = null, $name = null, $algorithm = null, $options = []) { $columns = $this->fluent($columns); @@ -64,12 +66,14 @@ public function index($columns = null, $name = null, $algorithm = null, $options } /** @inheritdoc */ + #[Override] public function primary($columns = null, $name = null, $algorithm = null, $options = []) { return $this->unique($columns, $name, $algorithm, $options); } /** @inheritdoc */ + #[Override] public function dropIndex($index = null) { $index = $this->transformColumns($index); @@ -170,6 +174,7 @@ protected function transformColumns($indexOrColumns) } /** @inheritdoc */ + #[Override] public function unique($columns = null, $name = null, $algorithm = null, $options = []) { $columns = $this->fluent($columns); @@ -251,6 +256,7 @@ public function expire($columns, $seconds) * * @return void */ + #[Override] public function create($options = []) { $collection = $this->collection->getCollectionName(); @@ -262,6 +268,7 @@ public function create($options = []) } /** @inheritdoc */ + #[Override] public function drop() { $this->collection->drop(); @@ -270,6 +277,7 @@ public function drop() } /** @inheritdoc */ + #[Override] public function renameColumn($from, $to) { $this->collection->updateMany([$from => ['$exists' => true]], ['$rename' => [$from => $to]]); @@ -278,6 +286,7 @@ public function renameColumn($from, $to) } /** @inheritdoc */ + #[Override] public function addColumn($type, $name, array $parameters = []) { $this->fluent($name); diff --git a/src/Schema/Builder.php b/src/Schema/Builder.php index 78cb3a4c1..207f4f1b3 100644 --- a/src/Schema/Builder.php +++ b/src/Schema/Builder.php @@ -6,11 +6,11 @@ use Closure; use MongoDB\Collection; -use MongoDB\Database; use MongoDB\Driver\Exception\ServerException; use MongoDB\Laravel\Connection; use MongoDB\Model\CollectionInfo; use MongoDB\Model\IndexInfo; +use Override; use function array_column; use function array_fill_keys; @@ -99,12 +99,14 @@ public function hasCollection($name) } /** @inheritdoc */ + #[Override] public function hasTable($table) { return $this->hasCollection($table); } /** @inheritdoc */ + #[Override] public function table($table, Closure $callback) { $blueprint = $this->createBlueprint($table); @@ -115,6 +117,7 @@ public function table($table, Closure $callback) } /** @inheritdoc */ + #[Override] public function create($table, ?Closure $callback = null, array $options = []) { $blueprint = $this->createBlueprint($table); @@ -127,6 +130,7 @@ public function create($table, ?Closure $callback = null, array $options = []) } /** @inheritdoc */ + #[Override] public function dropIfExists($table) { if ($this->hasCollection($table)) { @@ -135,6 +139,7 @@ public function dropIfExists($table) } /** @inheritdoc */ + #[Override] public function drop($table) { $blueprint = $this->createBlueprint($table); @@ -151,18 +156,29 @@ public function drop($table) * one by one. The database will be automatically recreated when a new connection * writes to it. */ + #[Override] public function dropAllTables() { $this->connection->getDatabase()->drop(); } - /** @param string|null $schema Database name */ + /** + * @param string|null $schema Database name + * + * @inheritdoc + */ + #[Override] public function getTables($schema = null) { return $this->getCollectionRows('collection', $schema); } - /** @param string|null $schema Database name */ + /** + * @param string|null $schema Database name + * + * @inheritdoc + */ + #[Override] public function getViews($schema = null) { return $this->getCollectionRows('view', $schema); @@ -174,6 +190,7 @@ public function getViews($schema = null) * * @return array */ + #[Override] public function getTableListing($schema = null, $schemaQualified = false) { $collections = []; @@ -197,6 +214,7 @@ public function getTableListing($schema = null, $schemaQualified = false) return $collections; } + #[Override] public function getColumns($table) { $db = null; @@ -255,6 +273,7 @@ public function getColumns($table) return $columns; } + #[Override] public function getIndexes($table) { $collection = $this->connection->getDatabase()->selectCollection($table); @@ -309,12 +328,18 @@ public function getIndexes($table) return $indexList; } + #[Override] public function getForeignKeys($table) { return []; } - /** @inheritdoc */ + /** + * @return Blueprint + * + * @inheritdoc + */ + #[Override] protected function createBlueprint($table, ?Closure $callback = null) { return new Blueprint($this->connection, $table); diff --git a/src/Session/MongoDbSessionHandler.php b/src/Session/MongoDbSessionHandler.php index dd57f2a3c..3677ea758 100644 --- a/src/Session/MongoDbSessionHandler.php +++ b/src/Session/MongoDbSessionHandler.php @@ -16,6 +16,7 @@ use MongoDB\BSON\Document; use MongoDB\BSON\UTCDateTime; use MongoDB\Collection; +use Override; use function tap; use function time; @@ -32,6 +33,7 @@ public function close(): bool return true; } + #[Override] public function gc($lifetime): int { $result = $this->getCollection()->deleteMany(['last_activity' => ['$lt' => $this->getUTCDateTime(-$lifetime)]]); @@ -39,6 +41,7 @@ public function gc($lifetime): int return $result->getDeletedCount() ?? 0; } + #[Override] public function destroy($sessionId): bool { $this->getCollection()->deleteOne(['_id' => (string) $sessionId]); @@ -46,6 +49,7 @@ public function destroy($sessionId): bool return true; } + #[Override] public function read($sessionId): string|false { $result = $this->getCollection()->findOne( @@ -63,6 +67,7 @@ public function read($sessionId): string|false return false; } + #[Override] public function write($sessionId, $data): bool { $payload = $this->getDefaultPayload($data); @@ -87,6 +92,7 @@ public function createTTLIndex(): void ); } + #[Override] protected function getDefaultPayload($data): array { $payload = [ diff --git a/src/Validation/DatabasePresenceVerifier.php b/src/Validation/DatabasePresenceVerifier.php index c5c378539..fdd783ab5 100644 --- a/src/Validation/DatabasePresenceVerifier.php +++ b/src/Validation/DatabasePresenceVerifier.php @@ -5,6 +5,7 @@ namespace MongoDB\Laravel\Validation; use MongoDB\BSON\Regex; +use Override; use function array_map; use function implode; @@ -12,17 +13,8 @@ class DatabasePresenceVerifier extends \Illuminate\Validation\DatabasePresenceVerifier { - /** - * Count the number of objects in a collection having the given value. - * - * @param string $collection - * @param string $column - * @param string $value - * @param int $excludeId - * @param string $idColumn - * - * @return int - */ + /** Count the number of objects in a collection having the given value. */ + #[Override] public function getCount($collection, $column, $value, $excludeId = null, $idColumn = null, array $extra = []) { $query = $this->table($collection)->where($column, new Regex('^' . preg_quote($value) . '$', '/i')); @@ -38,16 +30,8 @@ public function getCount($collection, $column, $value, $excludeId = null, $idCol return $query->count(); } - /** - * Count the number of objects in a collection with the given values. - * - * @param string $collection - * @param string $column - * @param array $values - * @param array $extra - * - * @return int - */ + /** Count the number of objects in a collection with the given values. */ + #[Override] public function getMultiCount($collection, $column, array $values, array $extra = []) { // Nothing can match an empty array. Return early to avoid matching an empty string. diff --git a/src/Validation/ValidationServiceProvider.php b/src/Validation/ValidationServiceProvider.php index 1095e93a3..6f7ebd980 100644 --- a/src/Validation/ValidationServiceProvider.php +++ b/src/Validation/ValidationServiceProvider.php @@ -5,9 +5,11 @@ namespace MongoDB\Laravel\Validation; use Illuminate\Validation\ValidationServiceProvider as BaseProvider; +use Override; class ValidationServiceProvider extends BaseProvider { + #[Override] protected function registerPresenceVerifier() { $this->app->singleton('validation.presence', function ($app) { From 46fa408d8b725324b16b1af730a453b65705002b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 3 Jul 2025 13:13:29 +0200 Subject: [PATCH 24/28] PHPORM-361 Remove autocommit of CS fixes (#3420) --- .github/workflows/coding-standards.yml | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 4d0eda3f9..f3fc3e369 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -56,23 +56,9 @@ jobs: - name: "Install dependencies with Composer" uses: "ramsey/composer-install@3.1.1" - with: - composer-options: "--no-suggest" - - - name: "Validate PSR class names" - run: "composer dump-autoload --optimize --strict-psr" - - - name: "Format the code" - continue-on-error: true - run: | - mkdir .cache - ./vendor/bin/phpcbf # The -q option is required until phpcs v4 is released - name: "Run PHP_CodeSniffer" - run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr" - - - name: "Commit the changes" - uses: stefanzweifel/git-auto-commit-action@v6 - with: - commit_message: "apply phpcbf formatting" + run: | + mkdir .cache + vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr From 98b75683f0c68a0294f9b63e2a8bcc5b2a5a4d93 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Thu, 3 Jul 2025 09:45:22 -0400 Subject: [PATCH 25/28] DOCSP-51402: schema-flexible terminology (#3424) --- docs/database-collection.txt | 11 ++++++----- docs/feature-compatibility.txt | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/database-collection.txt b/docs/database-collection.txt index be081c97b..e8d97711a 100644 --- a/docs/database-collection.txt +++ b/docs/database-collection.txt @@ -213,8 +213,8 @@ methods in your application: .. note:: - MongoDB is a schemaless database, so the preceding schema builder methods - query the database data rather than the schema. + MongoDB is a schema-flexible database, so the preceding schema + builder methods query the database data rather than the schema. Example ``````` @@ -269,9 +269,10 @@ collection fields: - ``Schema::hasColumns(string $, string[] $)``: checks if each specified field exists in at least one document -MongoDB is a schemaless database, so the preceding methods query the collection -data rather than the database schema. If the specified collection doesn't exist -or is empty, these methods return a value of ``false``. +MongoDB is a schema-flexible database, so the preceding methods query +the collection data rather than the database schema. If the specified +collection doesn't exist or is empty, these methods return a value of +``false``. .. note:: id Alias diff --git a/docs/feature-compatibility.txt b/docs/feature-compatibility.txt index 707f12c70..cce0932cc 100644 --- a/docs/feature-compatibility.txt +++ b/docs/feature-compatibility.txt @@ -201,7 +201,7 @@ Migration Features ------------------ The {+odm-short+} supports all Laravel migration features, but the -implementation is specific to MongoDB's schemaless model. +implementation is specific to MongoDB's schema-flexible model. Seeding Features ---------------- From a77aa60df132a8ec0d3f18c0bae652e5ee5a969a Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Thu, 10 Jul 2025 03:29:46 -0400 Subject: [PATCH 26/28] DOCSP-50960: Install PHP ext with pie (#3419) --- docs/quick-start/download-and-install.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quick-start/download-and-install.txt b/docs/quick-start/download-and-install.txt index 293425791..f6a44a264 100644 --- a/docs/quick-start/download-and-install.txt +++ b/docs/quick-start/download-and-install.txt @@ -47,9 +47,9 @@ to a Laravel web application. .. step:: Install the {+php-extension+} {+odm-long+} requires the {+php-extension+} to manage MongoDB - connections and commands. - Follow the :php:`Installing the MongoDB PHP Driver with PECL - ` guide to install the {+php-extension+}. + connections and commands. To learn how to install the {+php-extension+}, see + the `Install the MongoDB PHP extension `__ + step of the Get Started with the PHP Library guide. .. step:: Install Laravel From 272414495b7f730dfe3aff73707757aca5c0b570 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 22 Jul 2025 02:53:42 -0400 Subject: [PATCH 27/28] Replace extra substr() call (#3430) This was possible after a $newkey assignment was introduced in bd9ef30f98c92b915e9f657504e9cf15e4a2b046 --- src/Query/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 8520603c5..5e0413929 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -1896,7 +1896,7 @@ private function aliasIdForQuery(array $values, bool $root = true): array throw new InvalidArgumentException(sprintf('Cannot have both "%s" and "%s" fields.', $key, $newkey)); } - $values[substr($key, 0, -3) . '._id'] = $value; + $values[$newkey] = $value; unset($values[$key]); } } From c483b99a69ea9916d17719b86e7e5ea2f8b44c8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 07:06:10 +0200 Subject: [PATCH 28/28] Bump actions/checkout from 4 to 5 (#3440) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-ci-atlas.yml | 2 +- .github/workflows/build-ci.yml | 2 +- .github/workflows/coding-standards.yml | 2 +- .github/workflows/merge-up.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-ci-atlas.yml b/.github/workflows/build-ci-atlas.yml index 41f14e376..a4ab45e1d 100644 --- a/.github/workflows/build-ci-atlas.yml +++ b/.github/workflows/build-ci-atlas.yml @@ -40,7 +40,7 @@ jobs: driver: 2 steps: - - uses: "actions/checkout@v4" + - uses: "actions/checkout@v5" - name: "Create MongoDB Atlas Local" run: | diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index f94a32e79..f55bb3104 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -65,7 +65,7 @@ jobs: laravel: "12.*" steps: - - uses: "actions/checkout@v4" + - uses: "actions/checkout@v5" - name: "Create MongoDB Replica Set" run: | diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index d81b9e455..e9cddccfe 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -26,7 +26,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v4" + uses: "actions/checkout@v5" - name: "Setup cache environment" id: "extcache" diff --git a/.github/workflows/merge-up.yml b/.github/workflows/merge-up.yml index 2ed3feaea..ad92517b5 100644 --- a/.github/workflows/merge-up.yml +++ b/.github/workflows/merge-up.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout id: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # fetch-depth 0 is required to fetch all branches, not just the branch being built fetch-depth: 0 diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 8bc18e0f9..fe76fb466 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -39,7 +39,7 @@ jobs: - 2 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}