diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1478732..bb318f301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## [4.4.0] - unreleased * Support collection name prefix by @GromNaN in [#2930](https://github.com/mongodb/laravel-mongodb/pull/2930) +* Ignore `_id: null` to let MongoDB generate an `ObjectId` by @GromNaN in [#2969](https://github.com/mongodb/laravel-mongodb/pull/2969) * Add `mongodb` driver for Batching by @GromNaN in [#2904](https://github.com/mongodb/laravel-mongodb/pull/2904) * Rename queue option `table` to `collection` * Replace queue option `expire` with `retry_after` diff --git a/src/Eloquent/Builder.php b/src/Eloquent/Builder.php index 22dcfd081..678e7095b 100644 --- a/src/Eloquent/Builder.php +++ b/src/Eloquent/Builder.php @@ -21,6 +21,7 @@ use function collect; use function is_array; use function iterator_to_array; +use function json_encode; /** @method \MongoDB\Laravel\Query\Builder toBase() */ class Builder extends EloquentBuilder @@ -210,8 +211,8 @@ public function raw($value = null) */ public function createOrFirst(array $attributes = [], array $values = []): Model { - if ($attributes === []) { - throw new InvalidArgumentException('You must provide attributes to check for duplicates'); + if ($attributes === [] || $attributes === ['_id' => null]) { + throw new InvalidArgumentException('You must provide attributes to check for duplicates. Got ' . json_encode($attributes)); } // Apply casting and default values to the attributes diff --git a/src/Eloquent/Model.php b/src/Eloquent/Model.php index d47b62b8c..f7b4f1f36 100644 --- a/src/Eloquent/Model.php +++ b/src/Eloquent/Model.php @@ -745,6 +745,12 @@ protected function isBSON(mixed $value): bool */ public function save(array $options = []) { + // SQL databases would use autoincrement the id field if set to null. + // Apply the same behavior to MongoDB with _id only, otherwise null would be stored. + if (array_key_exists('_id', $this->attributes) && $this->attributes['_id'] === null) { + unset($this->attributes['_id']); + } + $saved = parent::save($options); // Clear list of unset fields diff --git a/tests/ModelTest.php b/tests/ModelTest.php index baa731799..ead5847ca 100644 --- a/tests/ModelTest.php +++ b/tests/ModelTest.php @@ -1151,4 +1151,21 @@ public function testUpdateOrCreate(array $criteria) $this->assertEquals($createdAt, $checkUser->created_at->getTimestamp()); $this->assertEquals($updatedAt, $checkUser->updated_at->getTimestamp()); } + + public function testCreateWithNullId() + { + $user = User::create(['_id' => null, 'email' => 'foo@bar']); + $this->assertNotNull(ObjectId::class, $user->id); + $this->assertSame(1, User::count()); + } + + public function testUpdateOrCreateWithNullId() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('You must provide attributes to check for duplicates'); + User::updateOrCreate( + ['_id' => null], + ['email' => 'jane.doe@example.com'], + ); + } }