ramsey / uuid-doctrine

:snowflake::file_cabinet: Allow the use of a ramsey/uuid UUID as Doctrine field type.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

$uuid->getBytes() returns different value after database roundtrip with uuid_ordered_time

weirdan opened this issue · comments

Consider the following test case:

// tests/UuidBinaryOrderedTimeTypeTest.php
    public function testGettersReturnSameValuesAfterDatabaseRoundtrip()
    {
        $uuid = Uuid::fromString('ff6f8cb0-c57d-11e1-9b21-0800200c9a66');

        $dbValue = $this->type->convertToDatabaseValue($uuid, $this->platform);
        $rehydrated = $this->type->convertToPHPValue($dbValue, $this->platform);

        // all of these assertions are passing
        $this->assertEquals($uuid->getHex(), $rehydrated->getHex());
        $this->assertEquals($uuid->toString(), $rehydrated->toString());
        $this->assertEquals($uuid->jsonSerialize(), $rehydrated->jsonSerialize());
        $this->assertEquals($uuid->getFieldsHex(), $rehydrated->getFieldsHex());
        $this->assertEquals($uuid->getUrn(), $rehydrated->getUrn());
        $this->assertEquals($uuid->serialize(), $rehydrated->serialize());

        // except this one:
        $this->assertEquals($uuid->getBytes(), $rehydrated->getBytes());
    }

Here, I expect the Uuid object methods to return same values after database roundtrip ( = I expect storage to behave transparently, not changing any substantial behavior of the object). All of the getters do behave as I expect, except the getBytes.

The cause of this is that the custom codec is being stored into rehydrated object.

To add to the peculiarity of this, if I do unserialize(serialize($rehydrated)), getBytes starts to return expected values, because custom codec gets dropped.

You're right. The test fails:

1) Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeTypeTest::testGettersReturnSameValuesAfterDatabaseRoundtrip
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-Binary String: 0xff6f8cb0c57d11e19b210800200c9a66
+Binary String: 0x11e1c57dff6f8cb09b210800200c9a66

I know it seems awkward, but this is how the OrderedTimeCodec works.

When it decodes the bytes, it puts them back into the order specified by the RFC, but when you get the bytes (using getBytes()) to put them into a database, it returns the bytes in the "ordered time" order, which rearranges the time fields, so that it can be stored in such a way that the table can be sorted by the UUIDs, and they'll be sorted in the order in which they were created.

What's happening in the test you've provided is that Uuid::fromString() is creating a standard Uuid object. There's nothing special about it. So, when you call getBytes(), they're in the proper order, as defined by the RFC. But when you pass that Uuid object into convertToDatabaseValue() for the UuidBinaryOrderedTimeType, then it gets converted into a special type of Uuid, where the bytes are re-arranged for sorting by "ordered time." When you call convertToPHPValue() for the same type, it maintains this special type and returns a Uuid object in which the return value of getBytes() will always return the bytes in the "ordered time" format, even though the hex values a rendered properly, according to the RFC definition.

Does that help clear things up? If not, feel free to ask more questions.

Well, you've just reiterated what I already stated in the initial bug report. Rehydrated UUID has a different codec, that affects its behavior (= 'converted into a special type of Uuid, where the bytes are re-arranged for sorting by "ordered time."'). It wasn't there before it hit the db, it doesn't need to be there when it gets back.

It wasn't there before it hit the db, it doesn't need to be there when it gets back.

I’m not sure what this means.

When you use the UuidBinaryOrderedTimeType, it converts the UUID into this special type. If you need the bytes to remain the same before and after, use the UuidBinaryType instead to pass the UUID into the database and back.

it converts the UUID into this special type.

Right. And it doesn't need to do that, as far as Doctrine type handling is concerned. Type converter gets binary string and returns a UUID. Which has no reason to be any kind of special sub-specie. Could have been ordinary UUID just as well, so it wouldn't be breaking storage transparency.

The point of using the OrderedTimeCodec is to rearrange the bytes in just this way.