Skip to content

Commit 6072551

Browse files
authored
Enable optimistic locking using an ObjectId as version field (#2815)
1 parent f7b1b8e commit 6072551

File tree

3 files changed

+92
-3
lines changed

3 files changed

+92
-3
lines changed

docs/en/reference/transactions-and-concurrency.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ a ``LockException`` is thrown, which indicates that the document was already mod
105105
.. note::
106106

107107
Only types implementing the ``\Doctrine\ODM\MongoDB\Types\Versionable`` interface can be used for versioning.
108-
Following ODM types can be used for versioning: ``int``, ``decimal128``, ``date``, and ``date_immutable``.
108+
Following ODM types can be used for versioning: ``int``, ``decimal128``, ``date``, ``date_immutable``, and ``object_id``.
109109

110110
Document Configuration
111111
^^^^^^^^^^^^^^^^^^^^^^
@@ -190,7 +190,8 @@ Choosing the Field Type
190190
"""""""""""""""""""""""
191191

192192
When using the date-based type in a high-concurrency environment, it is still possible to create multiple documents
193-
with the same version and cause a conflict. This can be avoided by using the ``int`` or ``decimal128`` type.
193+
with the same version and cause a conflict. This can be avoided by using the ``int``, ``decimal128``, or ``object_id`` type.
194+
The ``object_id`` type contains the timestamp of its creation, but also a random value to ensure uniqueness.
194195

195196
Usage
196197
"""""

lib/Doctrine/ODM/MongoDB/Types/ObjectIdType.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
/**
1010
* The ObjectId type.
1111
*/
12-
class ObjectIdType extends Type
12+
class ObjectIdType extends Type implements Versionable
1313
{
1414
public function convertToDatabaseValue($value)
1515
{
@@ -38,4 +38,9 @@ public function closureToPHP(): string
3838
{
3939
return '$return = (string) $value;';
4040
}
41+
42+
public function getNextVersion($current): ObjectId
43+
{
44+
return new ObjectId();
45+
}
4146
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ODM\MongoDB\Tests\Types;
6+
7+
use DateTime;
8+
use DateTimeImmutable;
9+
use Doctrine\ODM\MongoDB\Tests\BaseTestCase;
10+
use Doctrine\ODM\MongoDB\Types\Type;
11+
use Doctrine\ODM\MongoDB\Types\Versionable;
12+
use MongoDB\BSON\ObjectId;
13+
14+
class VersionableTest extends BaseTestCase
15+
{
16+
public function testIntNextVersion(): void
17+
{
18+
$type = $this->getType(Type::INT);
19+
self::assertSame(1, $type->getNextVersion(null));
20+
self::assertSame(2, $type->getNextVersion(1));
21+
}
22+
23+
public function testDecimal128NextVersion(): void
24+
{
25+
$type = $this->getType(Type::DECIMAL128);
26+
self::assertSame('1', $type->getNextVersion(null));
27+
self::assertSame('2', $type->getNextVersion('1'));
28+
}
29+
30+
public function testDateTimeNextVersion(): void
31+
{
32+
$type = $this->getType(Type::DATE);
33+
$current = new DateTime();
34+
$next = $type->getNextVersion(null);
35+
self::assertInstanceOf(DateTime::class, $next);
36+
self::assertGreaterThanOrEqual($current, $next);
37+
self::assertLessThanOrEqual(new DateTime(), $next);
38+
39+
$next = $type->getNextVersion(new DateTime('2000-01-01'));
40+
self::assertInstanceOf(DateTime::class, $next);
41+
self::assertGreaterThanOrEqual($current, $next);
42+
self::assertLessThanOrEqual(new DateTime(), $next);
43+
}
44+
45+
public function testDateTimeImmutableNextVersion(): void
46+
{
47+
$type = $this->getType(Type::DATE_IMMUTABLE);
48+
$current = new DateTime();
49+
$next = $type->getNextVersion(null);
50+
self::assertInstanceOf(DateTimeImmutable::class, $next);
51+
self::assertGreaterThanOrEqual($current, $next);
52+
self::assertLessThanOrEqual(new DateTimeImmutable(), $next);
53+
54+
$next = $type->getNextVersion(new DateTimeImmutable('2000-01-01'));
55+
self::assertInstanceOf(DateTimeImmutable::class, $next);
56+
self::assertGreaterThanOrEqual($current, $next);
57+
self::assertLessThanOrEqual(new DateTimeImmutable(), $next);
58+
}
59+
60+
public function testObjectIdNextVersion(): void
61+
{
62+
$type = $this->getType(Type::OBJECTID);
63+
$current = new ObjectId();
64+
$next = $type->getNextVersion(null);
65+
self::assertInstanceOf(ObjectId::class, $next);
66+
self::assertGreaterThan($current, $next);
67+
self::assertLessThan(new ObjectId(), $next);
68+
69+
$next = $type->getNextVersion($current);
70+
self::assertInstanceOf(ObjectId::class, $next);
71+
self::assertGreaterThan($current, $next);
72+
self::assertLessThan(new ObjectId(), $next);
73+
}
74+
75+
private function getType(string $name): Versionable
76+
{
77+
$type = Type::getType($name);
78+
79+
self::assertInstanceOf(Versionable::class, $type);
80+
81+
return $type;
82+
}
83+
}

0 commit comments

Comments
 (0)