diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 2b8f298592..6296fa4c12 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -14,6 +14,11 @@ The `Doctrine\ODM\MongoDB\Id\AbstractIdGenerator` class has been removed. Custom ID generators must implement the `Doctrine\ODM\MongoDB\Id\IdGenerator` interface. +The `Doctrine\ODM\MongoDB\Id\UuidGenerator` class has been removed. Use a custom +generator to generate string UUIDs. For more efficient storage of UUIDs, use the +`Doctrine\ODM\MongoDB\Types\BinaryUuidType` type in combination with the +`Doctrine\ODM\MongoDB\Id\SymfonyUuidGenerator` generator. + ## Metadata The `Doctrine\ODM\MongoDB\Mapping\ClassMetadata` class has been marked final and will no longer be extendable. diff --git a/composer.json b/composer.json index 86dc1ad247..60de393a3a 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,8 @@ "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^10.4", "squizlabs/php_codesniffer": "^3.5", - "symfony/cache": "^5.4 || ^6.0 || ^7.0" + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/uid": "^5.4 || ^6.0 || ^7.0" }, "conflict": { "doctrine/annotations": "<1.12 || >=3.0" diff --git a/docs/en/reference/basic-mapping.rst b/docs/en/reference/basic-mapping.rst index 397ff3df91..f9bc421270 100644 --- a/docs/en/reference/basic-mapping.rst +++ b/docs/en/reference/basic-mapping.rst @@ -151,6 +151,7 @@ Here is a quick overview of the built-in mapping types: - ``raw`` - ``string`` - ``timestamp`` +- ``uuid`` You can read more about the available MongoDB types on `php.net `_. @@ -178,6 +179,7 @@ This list explains some of the less obvious mapping types: - ``id``: string to ObjectId by default, but other formats are possible - ``timestamp``: string to ``MongoDB\BSON\Timestamp`` - ``raw``: any type +- ``uuid``: `Symfony UID `_ to ``MongoDB\BSON\Binary`` instance with a "uuid" type .. note:: @@ -206,6 +208,7 @@ follows: - ``float``: ``float`` - ``int``: ``int`` - ``string``: ``string`` +- ``Symfony\Component\Uid\Uuid``: ``uuid`` Doctrine can also autoconfigure any backed ``enum`` it encounters: ``type`` will be set to ``string`` or ``int``, depending on the enum's backing type, @@ -269,13 +272,23 @@ Here is an example: You can configure custom ID strategies if you don't want to use the default object ID. The available strategies are: -- ``AUTO`` - Uses the native generated ObjectId. +- ``AUTO`` - Automatically generates an ObjectId or Symfony UUID depending on the identifier type. - ``ALNUM`` - Generates an alpha-numeric string (based on an incrementing value). - ``CUSTOM`` - Defers generation to an implementation of ``IdGenerator`` specified in the ``class`` option. - ``INCREMENT`` - Uses another collection to auto increment an integer identifier. -- ``UUID`` - Generates a UUID identifier. +- ``UUID`` - Generates a UUID identifier (deprecated). - ``NONE`` - Do not generate any identifier. ID must be manually set. +When using the ``AUTO`` strategy in combination with a UUID identifier, the generator can create UUIDs of type 1, type 4, +and type 7 automatically. For all other UUID types, assign the identifier manually in combination with the ``NONE`` +strategy. + +.. note:: + + The ``UUID`` generator is deprecated, as it stores UUIDs as strings. It is recommended to use the ``AUTO`` strategy + with a ``uuid`` type identifier field instead. If you need to keep generating string UUIDs, you can use the + ``CUSTOM`` strategy with your own generator. + Here is an example how to manually set a string identifier for your documents: .. configuration-block:: diff --git a/lib/Doctrine/ODM/MongoDB/Id/AutoGenerator.php b/lib/Doctrine/ODM/MongoDB/Id/AutoGenerator.php index c5b3b132ee..7d0a958da5 100644 --- a/lib/Doctrine/ODM/MongoDB/Id/AutoGenerator.php +++ b/lib/Doctrine/ODM/MongoDB/Id/AutoGenerator.php @@ -9,6 +9,8 @@ /** * AutoGenerator generates a native ObjectId + * + * @deprecated use ObjectIdGenerator instead */ final class AutoGenerator extends AbstractIdGenerator { diff --git a/lib/Doctrine/ODM/MongoDB/Id/ObjectIdGenerator.php b/lib/Doctrine/ODM/MongoDB/Id/ObjectIdGenerator.php new file mode 100644 index 0000000000..d98f3088da --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Id/ObjectIdGenerator.php @@ -0,0 +1,17 @@ + UuidV1::class, + 4 => UuidV4::class, + 7 => UuidV7::class, + ]; + + public function __construct(private readonly string $class) + { + if (! in_array($this->class, self::SUPPORTED_TYPES, true)) { + throw new InvalidArgumentException(sprintf('Invalid UUID type "%s". Expected one of: %s.', $this->class, implode(', ', array_values(self::SUPPORTED_TYPES)))); + } + } + + public function generate(DocumentManager $dm, object $document): Uuid + { + return new $this->class(); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Id/UuidGenerator.php b/lib/Doctrine/ODM/MongoDB/Id/UuidGenerator.php index 5fe5004089..ac2a188290 100644 --- a/lib/Doctrine/ODM/MongoDB/Id/UuidGenerator.php +++ b/lib/Doctrine/ODM/MongoDB/Id/UuidGenerator.php @@ -18,9 +18,7 @@ use function strlen; use function substr; -/** - * Generates UUIDs. - */ +/** @deprecated without replacement. Use a custom generator or switch to binary UUIDs. */ final class UuidGenerator extends AbstractIdGenerator { /** diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php index 741b3ea1b0..3bd24c9d72 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php @@ -34,6 +34,9 @@ use ReflectionEnum; use ReflectionNamedType; use ReflectionProperty; +use Symfony\Component\Uid\UuidV1; +use Symfony\Component\Uid\UuidV4; +use Symfony\Component\Uid\UuidV7; use function array_column; use function array_filter; @@ -300,6 +303,8 @@ /** * UUID means Doctrine will generate a uuid for us. + * + * @deprecated without replacement. Use a custom generator or switch to binary UUIDs. */ public const GENERATOR_TYPE_UUID = 3; @@ -942,6 +947,16 @@ public function getIdentifier(): array return [$this->identifier]; } + /** + * Gets the mapping of the identifier field + * + * @phpstan-return FieldMapping + */ + public function getIdentifierMapping(): array + { + return $this->fieldMappings[$this->identifier]; + } + /** * Since MongoDB only allows exactly one identifier field * this will always return an array with only one value @@ -2391,22 +2406,18 @@ public function mapField(array $mapping): array } $this->generatorOptions = $mapping['options'] ?? []; - switch ($this->generatorType) { - case self::GENERATOR_TYPE_AUTO: - $mapping['type'] = 'id'; - break; - default: - if (! empty($this->generatorOptions['type'])) { - $mapping['type'] = (string) $this->generatorOptions['type']; - } elseif (empty($mapping['type'])) { - $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? Type::INT : Type::CUSTOMID; - } + if ($this->generatorType !== self::GENERATOR_TYPE_AUTO) { + if (! empty($this->generatorOptions['type'])) { + $mapping['type'] = (string) $this->generatorOptions['type']; + } elseif (empty($mapping['type'])) { + $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? Type::INT : Type::CUSTOMID; + } + } elseif ($mapping['type'] !== Type::UUID) { + $mapping['type'] = Type::ID; } unset($this->generatorOptions['type']); - } - - if (! isset($mapping['type'])) { + } elseif (! isset($mapping['type'])) { // Default to string $mapping['type'] = Type::STRING; } @@ -2798,6 +2809,11 @@ private function validateAndCompleteTypedFieldMapping(array $mapping): array } switch ($type->getName()) { + case UuidV1::class: + case UuidV4::class: + case UuidV7::class: + $mapping['type'] = Type::UUID; + break; case DateTime::class: $mapping['type'] = Type::DATE; break; diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php index 1b78b1e62e..87b073d753 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php @@ -12,15 +12,17 @@ use Doctrine\ODM\MongoDB\Event\OnClassMetadataNotFoundEventArgs; use Doctrine\ODM\MongoDB\Events; use Doctrine\ODM\MongoDB\Id\AlnumGenerator; -use Doctrine\ODM\MongoDB\Id\AutoGenerator; use Doctrine\ODM\MongoDB\Id\IdGenerator; use Doctrine\ODM\MongoDB\Id\IncrementGenerator; +use Doctrine\ODM\MongoDB\Id\ObjectIdGenerator; +use Doctrine\ODM\MongoDB\Id\SymfonyUuidGenerator; use Doctrine\ODM\MongoDB\Id\UuidGenerator; use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory; use Doctrine\Persistence\Mapping\ClassMetadata as ClassMetadataInterface; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Persistence\Mapping\ReflectionService; use ReflectionException; +use ReflectionNamedType; use function assert; use function get_class_methods; @@ -186,7 +188,7 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS if ($parent->idGenerator) { $class->setIdGenerator($parent->idGenerator); } - } else { + } elseif ($class->identifier) { $this->completeIdGeneratorMapping($class); } @@ -230,12 +232,36 @@ protected function newClassMetadataInstance($className): ClassMetadata return new ClassMetadata($className); } + private function generateAutoIdGenerator(ClassMetadata $class): void + { + $identifierMapping = $class->getIdentifierMapping(); + switch ($identifierMapping['type']) { + case 'id': + case 'objectId': + $class->setIdGenerator(new ObjectIdGenerator()); + break; + case 'uuid': + $reflectionProperty = $class->getReflectionProperty($identifierMapping['fieldName']); + if (! $reflectionProperty->getType() instanceof ReflectionNamedType) { + throw MappingException::autoIdGeneratorNeedsType($class->name, $identifierMapping['fieldName']); + } + + $class->setIdGenerator(new SymfonyUuidGenerator($reflectionProperty->getType()->getName())); + break; + default: + throw MappingException::unsupportedTypeForAutoGenerator( + $class->name, + $identifierMapping['type'], + ); + } + } + private function completeIdGeneratorMapping(ClassMetadata $class): void { $idGenOptions = $class->generatorOptions; switch ($class->generatorType) { case ClassMetadata::GENERATOR_TYPE_AUTO: - $class->setIdGenerator(new AutoGenerator()); + $this->generateAutoIdGenerator($class); break; case ClassMetadata::GENERATOR_TYPE_INCREMENT: $incrementGenerator = new IncrementGenerator(); diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php index c0a32639d4..abd19c1535 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php @@ -319,4 +319,22 @@ public static function rootDocumentCannotBeEncrypted(string $className): self $className, )); } + + public static function unsupportedTypeForAutoGenerator(string $className, string $type): self + { + return new self(sprintf( + 'The type "%s" can not be used for auto ID generation in class "%s".', + $type, + $className, + )); + } + + public static function autoIdGeneratorNeedsType(string $className, string $identifierFieldName): self + { + return new self(sprintf( + 'The auto ID generator for class "%s" requires the identifier field "%s" to have a type.', + $className, + $identifierFieldName, + )); + } } diff --git a/lib/Doctrine/ODM/MongoDB/Types/BinaryUuidType.php b/lib/Doctrine/ODM/MongoDB/Types/BinaryUuidType.php new file mode 100644 index 0000000000..0038dfc7f2 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Types/BinaryUuidType.php @@ -0,0 +1,77 @@ + null, + $value instanceof Binary => $value, + $value instanceof Uuid => new Binary($value->toBinary(), Binary::TYPE_UUID), + is_string($value) => new Binary(Uuid::fromString($value)->toBinary(), Binary::TYPE_UUID), + default => throw new InvalidArgumentException(sprintf('Invalid data type %s received for UUID', get_debug_type($value))), + }; + } + + public function convertToPHPValue(mixed $value): Uuid + { + if ($value instanceof Uuid) { + return $value; + } + + if (! $value instanceof Binary) { + throw new InvalidArgumentException(sprintf('Invalid data of type "%s" received for Uuid', get_debug_type($value))); + } + + if ($value->getType() !== Binary::TYPE_UUID) { + throw new InvalidArgumentException(sprintf('Invalid binary data of type %d received for Uuid', $value->getType())); + } + + return Uuid::fromBinary($value->getData()); + } + + public function closureToMongo(): string + { + return <<<'PHP' +$return = match (true) { + $value === null => null, + $value instanceof \MongoDB\BSON\Binary => $value, + $value instanceof \Symfony\Component\Uid\Uuid => new \MongoDB\BSON\Binary($value->toBinary(), \MongoDB\BSON\Binary::TYPE_UUID), + is_string($value) => new \MongoDB\BSON\Binary(\Symfony\Component\Uid\Uuid::fromString($value)->toBinary(), \MongoDB\BSON\Binary::TYPE_UUID), + default => throw new \InvalidArgumentException(sprintf('Invalid data type %s received for UUID', get_debug_type($value))), +}; +PHP; + } + + public function closureToPHP(): string + { + return <<<'PHP' + if ($value instanceof \Symfony\Component\Uid\Uuid) { + $return = $value; + return; + } + + if (! $value instanceof \MongoDB\BSON\Binary) { + throw new \InvalidArgumentException(sprintf('Invalid data of type "%s" received for Uuid', get_debug_type($value))); + } + + if ($value->getType() !== \MongoDB\BSON\Binary::TYPE_UUID) { + throw new \InvalidArgumentException(sprintf('Invalid binary data of type %d received for Uuid', $value->getType())); + } + + $return = \Symfony\Component\Uid\Uuid::fromBinary($value->getData()); +PHP; + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Types/Type.php b/lib/Doctrine/ODM/MongoDB/Types/Type.php index a6399cd645..67b18da8cc 100644 --- a/lib/Doctrine/ODM/MongoDB/Types/Type.php +++ b/lib/Doctrine/ODM/MongoDB/Types/Type.php @@ -9,6 +9,7 @@ use Doctrine\ODM\MongoDB\Types; use InvalidArgumentException; use MongoDB\BSON\ObjectId; +use Symfony\Component\Uid\Uuid; use function end; use function explode; @@ -45,6 +46,7 @@ abstract class Type public const OBJECTID = 'object_id'; public const RAW = 'raw'; public const DECIMAL128 = 'decimal128'; + public const UUID = 'uuid'; /** @deprecated const was deprecated in doctrine/mongodb-odm 2.1 and will be removed in 3.0. Use Type::INT instead */ public const INTID = 'int_id'; @@ -86,6 +88,7 @@ abstract class Type self::OBJECTID => Types\ObjectIdType::class, self::RAW => Types\RawType::class, self::DECIMAL128 => Types\Decimal128Type::class, + self::UUID => Types\BinaryUuidType::class, ]; /** Prevent instantiation and force use of the factory method. */ @@ -167,11 +170,15 @@ public static function getTypeFromPHPVariable($variable): ?Type { if (is_object($variable)) { if ($variable instanceof DateTimeInterface) { - return self::getType('date'); + return self::getType(self::DATE); } if ($variable instanceof ObjectId) { - return self::getType('id'); + return self::getType(self::ID); + } + + if ($variable instanceof Uuid) { + return self::getType(self::UUID); } } else { $type = gettype($variable); diff --git a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php index 2ae5830b86..2d7b7a9150 100644 --- a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php +++ b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php @@ -1139,7 +1139,7 @@ private function persistNew(ClassMetadata $class, object $document): void )); } - if ($class->generatorType === ClassMetadata::GENERATOR_TYPE_AUTO && $idValue !== null && ! preg_match('#^[0-9a-f]{24}$#', (string) $idValue)) { + if ($class->getIdentifierMapping()['type'] === Type::ID && $idValue !== null && $class->generatorType === ClassMetadata::GENERATOR_TYPE_AUTO && ! preg_match('#^[0-9a-f]{24}$#', (string) $idValue)) { throw new InvalidArgumentException(sprintf( '%s uses AUTO identifier generation strategy but provided identifier is not a valid ObjectId.', $document::class, diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a7f53f50e5..13c4c85d1b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -882,6 +882,12 @@ parameters: count: 1 path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:generateAutoIdGenerator\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php + - message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:initializeReflection\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' identifier: missingType.generics @@ -2106,6 +2112,18 @@ parameters: count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php + - + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\) with arguments MongoDB\\BSON\\Binary, null and ''Binary UUIDs are…'' will always evaluate to false\.$#' + identifier: staticMethod.impossibleType + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Types/BinaryUuidTypeTest.php + + - + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\) with arguments Symfony\\Component\\Uid\\UuidV4, null and ''Uuid objects are…'' will always evaluate to false\.$#' + identifier: staticMethod.impossibleType + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Types/BinaryUuidTypeTest.php + - message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\ArrayTest\:\:\$id is unused\.$#' identifier: property.unused diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/UuidMappingTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/UuidMappingTest.php new file mode 100644 index 0000000000..ad737f94e9 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/UuidMappingTest.php @@ -0,0 +1,64 @@ +dm->getClassMetadata(UuidTestDocument::class); + $idMapping = $metadata->getIdentifierMapping(); + self::assertSame(Type::UUID, $idMapping['type'], 'Id field should have UUID type'); + } + + public function testExplicitValue(): void + { + $uuid = new UuidV4(); + $document = new UuidTestDocument(); + + $document->id = $uuid; + $document->explicitlyTypedUuid = $uuid; + $document->untypedUuid = $uuid; + + $this->dm->persist($document); + $this->dm->flush(); + + $check = $this->dm->find(UuidTestDocument::class, $document->id); + self::assertInstanceOf(UuidTestDocument::class, $check); + self::assertEquals($uuid, $check->id); + self::assertEquals($uuid, $check->explicitlyTypedUuid); + self::assertEquals($uuid, $check->untypedUuid); + } + + public function testAutoGenerateIdV4(): void + { + $document = new UuidTestDocument(); + + $this->dm->persist($document); + $this->dm->flush(); + + $check = $this->dm->find(UuidTestDocument::class, $document->id); + self::assertInstanceOf(UuidTestDocument::class, $check); + self::assertInstanceOf(UuidV4::class, $check->id); + } +} + +#[ODM\Document] +class UuidTestDocument +{ + #[ODM\Id] + public UuidV4 $id; + + #[ODM\Field(type: Type::UUID)] + public ?UuidV4 $explicitlyTypedUuid = null; + + #[ODM\Field] + public ?UuidV4 $untypedUuid = null; +} diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Types/BinaryUuidTypeTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Types/BinaryUuidTypeTest.php new file mode 100644 index 0000000000..407ce66ab1 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Types/BinaryUuidTypeTest.php @@ -0,0 +1,99 @@ +toRfc4122(); + $binaryUuid = new Binary($uuid->toBinary(), Binary::TYPE_UUID); + + self::assertNull($type->convertToDatabaseValue(null), 'null is not converted'); + self::assertEquals($binaryUuid, $type->convertToDatabaseValue($uuid), 'Uuid objects are converted to Binary objects'); + self::assertEquals($binaryUuid, $type->convertToDatabaseValue($stringUuid), 'String UUIDs are converted to Binary objects'); + self::assertSame($binaryUuid, $type->convertToDatabaseValue($binaryUuid), 'Binary UUIDs are returned as is'); + } + + public function testConvertInvalidUuid(): void + { + $type = Type::getType(Type::UUID); + + $this->expectException(InvalidArgumentException::class); + $type->convertToDatabaseValue('invalid'); + } + + public function testConvertToPHPValue(): void + { + $type = Type::getType(Type::UUID); + $uuid = new UuidV4(); + $binaryUuid = new Binary($uuid->toBinary(), Binary::TYPE_UUID); + + self::assertEquals($uuid, $type->convertToPHPValue($binaryUuid), 'Binary UUIDs are converted to Uuid objects'); + self::assertSame($uuid, $type->convertToPHPValue($uuid), 'Uuid objects are returned as is'); + } + + public function testConvertInvalidBinaryUuid(): void + { + $type = Type::getType(Type::UUID); + + $this->expectException(InvalidArgumentException::class); + $type->convertToPHPValue(new Binary('invalid', Binary::TYPE_UUID)); + } + + public function testConvertInvalidBinary(): void + { + $type = Type::getType(Type::UUID); + + $this->expectException(Throwable::class); + $type->convertToPHPValue(new Binary('invalid', Binary::TYPE_GENERIC)); + } + + public function testClosureToMongo(): void + { + $type = Type::getType(Type::UUID); + $uuid = new UuidV4(); + $stringUuid = $uuid->toRfc4122(); + $binaryUuid = new Binary($uuid->toBinary(), Binary::TYPE_UUID); + + $convertToDatabaseValue = static function ($value) use ($type) { + $return = null; + eval($type->closureToMongo()); + + return $return; + }; + + self::assertNull($convertToDatabaseValue(null), 'null is not converted'); + self::assertEquals($binaryUuid, $convertToDatabaseValue($uuid), 'Uuid objects are converted to Binary objects'); + self::assertEquals($binaryUuid, $convertToDatabaseValue($stringUuid), 'String UUIDs are converted to Binary objects'); + self::assertSame($binaryUuid, $convertToDatabaseValue($binaryUuid), 'Binary UUIDs are returned as is'); + } + + public function testClosureToPhp(): void + { + $type = Type::getType(Type::UUID); + $uuid = new UuidV4(); + $binaryUuid = new Binary($uuid->toBinary(), Binary::TYPE_UUID); + + $convertToPHPValue = static function ($value) use ($type) { + $return = null; + eval($type->closureToPHP()); + + return $return; + }; + + self::assertEquals($uuid, $convertToPHPValue($binaryUuid), 'Binary UUIDs are converted to Uuid objects'); + self::assertSame($uuid, $convertToPHPValue($uuid), 'Uuid objects are returned as is'); + } +} diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Types/TypeTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Types/TypeTest.php index 4013e7e8b7..25fa434917 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Types/TypeTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Types/TypeTest.php @@ -17,8 +17,10 @@ use MongoDB\BSON\Timestamp; use MongoDB\BSON\UTCDateTime; use PHPUnit\Framework\Attributes\DataProvider; +use Symfony\Component\Uid\UuidV4; use function get_debug_type; +use function hex2bin; use function md5; use function str_pad; use function str_repeat; @@ -66,6 +68,7 @@ public static function provideTypes(): array 'objectId' => [Type::OBJECTID, '507f1f77bcf86cd799439011', new ObjectId('507f1f77bcf86cd799439011')], 'raw' => [Type::RAW, (object) ['foo' => 'bar']], 'decimal128' => [Type::DECIMAL128, '4.20', new Decimal128('4.20')], + 'uuid' => [Type::UUID, new UuidV4('550e8400-e29b-41d4-a716-446655440000'), new Binary(hex2bin('550e8400e29b41d4a716446655440000'), Binary::TYPE_UUID)], ]; }