Skip to content

Commit

Permalink
Use monitoring to track if model was created or found
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN committed Mar 7, 2024
1 parent 168f34b commit 5ffe58a
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 6 deletions.
32 changes: 26 additions & 6 deletions src/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use MongoDB\Driver\Cursor;
use MongoDB\Laravel\Collection;
use MongoDB\Laravel\Helpers\QueriesRelationships;
use MongoDB\Laravel\Internal\FindAndModifyCommandSubscriber;
use MongoDB\Model\BSONDocument;

use function array_intersect_key;
Expand Down Expand Up @@ -185,19 +186,38 @@ public function raw($value = null)
return $results;
}

public function createOrFirst(array $attributes = [], array $values = [])
/**
* Attempt to create the record if it does not exist with the matching attributes.
* If the record exists, it will be returned.
*
* @param array $attributes The attributes to check for duplicate records
* @param array $values The attributes to insert if no matching record is found
*/
public function createOrFirst(array $attributes = [], array $values = []): Model
{
// Apply casting and default values to the attributes
$instance = $this->newModelInstance($values + $attributes);
$values = $instance->getAttributes();
$attributes = array_intersect_key($attributes, $values);

return $this->raw(function (Collection $collection) use ($attributes, $values) {
return $collection->findOneAndUpdate(
$attributes,
['$setOnInsert' => $values],
['upsert' => true, 'new' => true],
);
$listener = new FindAndModifyCommandSubscriber();
$collection->getManager()->addSubscriber($listener);

try {
$document = $collection->findOneAndUpdate(
$attributes,
['$setOnInsert' => $values],
['upsert' => true, 'new' => true, 'typeMap' => ['root' => 'array', 'document' => 'array']],
);
} finally {
$collection->getManager()->removeSubscriber($listener);
}

$model = $this->model->newFromBuilder($document);
$model->wasRecentlyCreated = $listener->created;

return $model;
});
}

Expand Down
34 changes: 34 additions & 0 deletions src/Internal/FindAndModifyCommandSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace MongoDB\Laravel\Internal;

use MongoDB\Driver\Monitoring\CommandFailedEvent;
use MongoDB\Driver\Monitoring\CommandStartedEvent;
use MongoDB\Driver\Monitoring\CommandSubscriber;
use MongoDB\Driver\Monitoring\CommandSucceededEvent;

/**
* Track findAndModify command events to detect when a document is inserted or
* updated.
*
* @internal
*/
class FindAndModifyCommandSubscriber implements CommandSubscriber
{
public bool $created;

public function commandFailed(CommandFailedEvent $event)
{
}

public function commandStarted(CommandStartedEvent $event)
{
}

public function commandSucceeded(CommandSucceededEvent $event)
{
$this->created = ! $event->getReply()->lastErrorObject->updatedExisting;
}
}
4 changes: 4 additions & 0 deletions tests/ModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,7 @@ public function testCreateOrFirst()

$this->assertSame('taylorotwell@gmail.com', $user1->email);
$this->assertNull($user1->name);
$this->assertTrue($user1->wasRecentlyCreated);

$user2 = User::createOrFirst(
['email' => 'taylorotwell@gmail.com'],
Expand All @@ -1065,6 +1066,7 @@ public function testCreateOrFirst()
$this->assertSame('taylorotwell@gmail.com', $user2->email);
$this->assertNull($user2->name);
$this->assertNull($user2->birthday);
$this->assertFalse($user2->wasRecentlyCreated);

$user3 = User::createOrFirst(
['email' => 'abigailotwell@gmail.com'],
Expand All @@ -1075,12 +1077,14 @@ public function testCreateOrFirst()
$this->assertSame('abigailotwell@gmail.com', $user3->email);
$this->assertSame('Abigail Otwell', $user3->name);
$this->assertEquals(new DateTime('1987-05-28'), $user3->birthday);
$this->assertTrue($user3->wasRecentlyCreated);

$user4 = User::createOrFirst(
['name' => 'Dries Vints'],
['name' => 'Nuno Maduro', 'email' => 'nuno@laravel.com'],
);

$this->assertSame('Nuno Maduro', $user4->name);
$this->assertTrue($user4->wasRecentlyCreated);
}
}

0 comments on commit 5ffe58a

Please sign in to comment.