Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ public function executeInserts(array $options = []): void
$this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
}

$data[$versionMapping['name']] = $type->convertPHPToDatabaseValue($nextVersion);
Copy link
Copy Markdown
Member Author

@GromNaN GromNaN Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a static method called on the instance. So it was possible to change the behavior for a custom type by overriding the method, but that's not how it's supposed to be used.
In fact, this method should be final.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting that the convertPHPToDatabaseValue() should be final? If so, I agree and we should probably track that with a separate ticket for the next major version.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's the idea. But a better approach would be to extract the static methods into a TypeRegistry, similar to DBAL. #2818

$data[$versionMapping['name']] = $type->convertToDatabaseValue($nextVersion);
}

$inserts[] = $data;
Expand Down Expand Up @@ -296,7 +296,7 @@ private function executeUpsert(object $document, array $options): void
$this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
}

$data['$set'][$versionMapping['name']] = $type->convertPHPToDatabaseValue($nextVersion);
$data['$set'][$versionMapping['name']] = $type->convertToDatabaseValue($nextVersion);
}

foreach (array_keys($criteria) as $field) {
Expand Down Expand Up @@ -377,8 +377,8 @@ public function update(object $document, array $options = []): void
$type = Type::getType($versionMapping['type']);
assert($type instanceof Versionable);
$nextVersion = $type->getNextVersion($currentVersion);
$update['$set'][$versionMapping['name']] = Type::convertPHPToDatabaseValue($nextVersion);
$query[$versionMapping['name']] = Type::convertPHPToDatabaseValue($currentVersion);
$update['$set'][$versionMapping['name']] = $type->convertToDatabaseValue($nextVersion);
$query[$versionMapping['name']] = $type->convertToDatabaseValue($currentVersion);
}

if (! empty($update)) {
Expand Down
88 changes: 88 additions & 0 deletions tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH2789Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);

namespace Doctrine\ODM\MongoDB\Tests\Functional\Ticket;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\Tests\BaseTestCase;
use Doctrine\ODM\MongoDB\Types\Type;
use Doctrine\ODM\MongoDB\Types\Versionable;
use MongoDB\BSON\Binary;
use PHPUnit\Framework\Attributes\BackupGlobals;

use function assert;
use function is_int;

#[BackupGlobals(true)]
class GH2789Test extends BaseTestCase
{
public function testVersionWithCustomType(): void
{
Type::addType(GH2789CustomType::class, GH2789CustomType::class);
$doc = new GH2789VersionedUuid('original message');

$this->dm->persist($doc);
$this->dm->flush();

$documents = $this->dm->getDocumentCollection(GH2789VersionedUuid::class)->find()->toArray();
self::assertCount(1, $documents);
self::assertEquals(new Binary('1', 142), $documents[0]['version'], 'The version field should be stored using the custom type');

$doc->message = 'new message';
$this->dm->persist($doc);
$this->dm->flush();

$documents = $this->dm->getDocumentCollection(GH2789VersionedUuid::class)->find()->toArray();
self::assertCount(1, $documents);
self::assertEquals(new Binary('2', 142), $documents[0]['version'], 'The version field should be incremented and stored using the custom type');
}
}

#[ODM\Document(collection: 'gh2789_versioned_uuid')]
class GH2789VersionedUuid
{
#[ODM\Id]
public string $id;

#[ODM\Version]
#[ODM\Field(type: GH2789CustomType::class)]
public int $version;

public function __construct(
#[ODM\Field(type: 'string')]
public string $message,
) {
}
}

/**
* Custom type that stores an integer as a MongoDB Binary subtype 142.
*/
class GH2789CustomType extends Type implements Versionable
{
public function convertToPHPValue($value): int
{
assert($value instanceof Binary);

return (int) $value->getData();
}

public function convertToDatabaseValue($value): Binary
{
assert(is_int($value));

return new Binary((string) $value, 142);
}

public function getNextVersion($current): int
{
if ($current === null) {
return 1;
}

assert(is_int($current));

return $current + 1;
}
}