|
6 | 6 |
|
7 | 7 | use Illuminate\Database\ConnectionInterface;
|
8 | 8 | use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
| 9 | +use InvalidArgumentException; |
9 | 10 | use MongoDB\Driver\Cursor;
|
| 11 | +use MongoDB\Laravel\Collection; |
10 | 12 | use MongoDB\Laravel\Helpers\QueriesRelationships;
|
| 13 | +use MongoDB\Laravel\Internal\FindAndModifyCommandSubscriber; |
11 | 14 | use MongoDB\Model\BSONDocument;
|
| 15 | +use MongoDB\Operation\FindOneAndUpdate; |
12 | 16 |
|
| 17 | +use function array_intersect_key; |
13 | 18 | use function array_key_exists;
|
14 | 19 | use function array_merge;
|
15 | 20 | use function collect;
|
@@ -183,6 +188,58 @@ public function raw($value = null)
|
183 | 188 | return $results;
|
184 | 189 | }
|
185 | 190 |
|
| 191 | + /** |
| 192 | + * Attempt to create the record if it does not exist with the matching attributes. |
| 193 | + * If the record exists, it will be returned. |
| 194 | + * |
| 195 | + * @param array $attributes The attributes to check for duplicate records |
| 196 | + * @param array $values The attributes to insert if no matching record is found |
| 197 | + */ |
| 198 | + public function createOrFirst(array $attributes = [], array $values = []): Model |
| 199 | + { |
| 200 | + if ($attributes === []) { |
| 201 | + throw new InvalidArgumentException('You must provide attributes to check for duplicates'); |
| 202 | + } |
| 203 | + |
| 204 | + // Apply casting and default values to the attributes |
| 205 | + // In case of duplicate key between the attributes and the values, the values have priority |
| 206 | + $instance = $this->newModelInstance($values + $attributes); |
| 207 | + |
| 208 | + /* @see \Illuminate\Database\Eloquent\Model::performInsert */ |
| 209 | + if ($instance->usesTimestamps()) { |
| 210 | + $instance->updateTimestamps(); |
| 211 | + } |
| 212 | + |
| 213 | + $values = $instance->getAttributes(); |
| 214 | + $attributes = array_intersect_key($attributes, $values); |
| 215 | + |
| 216 | + return $this->raw(function (Collection $collection) use ($attributes, $values) { |
| 217 | + $listener = new FindAndModifyCommandSubscriber(); |
| 218 | + $collection->getManager()->addSubscriber($listener); |
| 219 | + |
| 220 | + try { |
| 221 | + $document = $collection->findOneAndUpdate( |
| 222 | + $attributes, |
| 223 | + // Before MongoDB 5.0, $setOnInsert requires a non-empty document. |
| 224 | + // This is should not be an issue as $values includes the query filter. |
| 225 | + ['$setOnInsert' => (object) $values], |
| 226 | + [ |
| 227 | + 'upsert' => true, |
| 228 | + 'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER, |
| 229 | + 'typeMap' => ['root' => 'array', 'document' => 'array'], |
| 230 | + ], |
| 231 | + ); |
| 232 | + } finally { |
| 233 | + $collection->getManager()->removeSubscriber($listener); |
| 234 | + } |
| 235 | + |
| 236 | + $model = $this->model->newFromBuilder($document); |
| 237 | + $model->wasRecentlyCreated = $listener->created; |
| 238 | + |
| 239 | + return $model; |
| 240 | + }); |
| 241 | + } |
| 242 | + |
186 | 243 | /**
|
187 | 244 | * Add the "updated at" column to an array of values.
|
188 | 245 | * TODO Remove if https://github.com/laravel/framework/commit/6484744326531829341e1ff886cc9b628b20d73e
|
|
0 commit comments