Skip to content

[13.x] Refactor UUID handling to use Symfony's Uid component#59661

Closed
lukaskleinschmidt wants to merge 4 commits intolaravel:13.xfrom
lukaskleinschmidt:refactor-uuid
Closed

[13.x] Refactor UUID handling to use Symfony's Uid component#59661
lukaskleinschmidt wants to merge 4 commits intolaravel:13.xfrom
lukaskleinschmidt:refactor-uuid

Conversation

@lukaskleinschmidt
Copy link
Copy Markdown
Contributor

This PR refactors Uuids to also be generated and validated using the symfony/uid package, which is already used for Ulids.
This completely removes the currently used ramesy/uuid package from the framework.

The main reason behind this PR:
Originally, I only wanted to use brick/money@0.13.0 in a current project.
However, the required/allowed brick/math versions for brick/money and ramesy/uuid are incompatible.

There is an open issue and several open pull requests for this issue in the ramsey/uuid repository:
ramsey/uuid#632
ramsey/uuid#633
ramsey/uuid#634

I also found a (rather old) discussion on this topic:
#47631

Digging a bit deeper in the laravel framework it it looks like the ramesy/uuid package could be easily replaced by the already used symfony/uid package. The impact of this change should be minimal, as developers actively working with the ramesy/uuid package can simply include it manually.

@lukaskleinschmidt lukaskleinschmidt marked this pull request as draft April 13, 2026 09:33
@shaedrich
Copy link
Copy Markdown
Contributor

shaedrich commented Apr 13, 2026

Digging a bit deeper in the laravel framework it it looks like the ramesy/uuid package could be easily replaced by the already used symfony/uid package. The impact of this change should be minimal

⚠️ symfony/uid does not support all UUID variants as ramsey/uuid does (namely v2). This was a mistake (or misassumption) I made in #53341 which I had to correct in #53368

}

return $fields->getVersion() === $version;
return $version === (int) $uuid->toString()[14];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That is an implementation detail—can't we abstract that away?

Copy link
Copy Markdown
Contributor Author

@lukaskleinschmidt lukaskleinschmidt Apr 13, 2026

Choose a reason for hiding this comment

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

We do get back the specific class implementations like UuidV1 or UuidV7.
So doing something like this would also work:

return match ($version) {
    'nil', 0 => $uuid instanceof NilUuid,
    'max' => $uuid instanceof MaxUuid,
    1 => $uuid instanceof Uuid1,
    //...
    7 => $uuid instanceof Uuid7,
    default => false,
}

But will fail for v2 uuids.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would prefer that if not for v2—maybe, we would need a wrapper for this? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do you have something specific in mind.
I can't think of a different way to check for a v2 version right now, other than checking the string directly like above.

@lukaskleinschmidt
Copy link
Copy Markdown
Contributor Author

lukaskleinschmidt commented Apr 13, 2026

Yeah also running into v2 issues in some tests.
Right now it looks like that the only additional thing breaking is Carbon::createFromId.

Not sure if a "custom V2" implementation would be a viable solution.

@shaedrich
Copy link
Copy Markdown
Contributor

Not sure if a "custom V2" implementation would be a viable solution.

If we leave that out or handle this significantly different, this would be a breaking change

@lukaskleinschmidt
Copy link
Copy Markdown
Contributor Author

lukaskleinschmidt commented Apr 13, 2026

After some digging I do unterstand why ramsey/uuid was used to support UuidV2.

I totally see this being a breaking change to remove it altogether.
I do wonder though what the stand on UuidV2 is in general.
The current validation docs for example don't mention version 2 at all.
Even though I suppose it would work.

https://laravel.com/docs/13.x/validation#rule-uuid

Therefore, I am currently unsure whether it is worthwhile trying to find a solution for this PR.

@lukaskleinschmidt
Copy link
Copy Markdown
Contributor Author

One idea.
Add a dedicated Str::uuidVersion() method to determine the version of a given UUID string
This could also make the Carbon::createFromId() method a bit cleaner.

/**
 * Get the version of a given UUID.
 */
public static function uuidVersion(string $uuid): int|string
{
    $uuid = Uuid::fromString($uuid);

    return match (true) {
        // Not strictly necessary, but maybe preferred
        // $uuid instanceof UuidV1 => 1,
        // $uuid instanceof UuidV3 => 3,
        // $uuid instanceof UuidV4 => 4,
        // $uuid instanceof UuidV5 => 5,
        // $uuid instanceof UuidV6 => 6,
        // $uuid instanceof UuidV7 => 7,
        // $uuid instanceof UuidV8 => 8,
        $uuid instanceof NilUuid => 'nil',
        $uuid instanceof MaxUuid => 'max',
        $uuid instanceof Uuid => (int) $uuid->toRfc4122()[14],
    };
}

public static function isUuid($value, $version = null)
{
    //...

    return $version == static::uuidVersion($uuid);
}

/**
 * Create a Carbon instance from a given ordered UUID or ULID.
 */
public static function createFromId(Uuid|Ulid|string $id): static
{
    if (is_string($id)) {
        $id = Ulid::isValid($id) ? Ulid::fromString($id) : Uuid::fromString($id);
    }

    if ($id instanceof TimeBasedUidInterface) {
        return static::createFromInterface($id->getDateTime());
    }

    return match (Str::uuidVersion($id)) {
        2 => static::createFromInterface(BinaryUtil::hexToDateTime(
            '0'.substr($id->toString(), 15, 3).substr($id->toString(), 9, 4).'00000000'
        )),
        default => throw new InvalidArgumentException('The given ID must be a time-based UUID or a ULID.'),
    };
}

@lukaskleinschmidt lukaskleinschmidt marked this pull request as ready for review April 14, 2026 11:32
@taylorotwell
Copy link
Copy Markdown
Member

Thanks for your pull request to Laravel!

Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.

If applicable, please consider releasing your code as a package so that the community can still take advantage of your contributions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants