ramsey/uuid · error · UnsupportedOperationException

Attempting to decode a non-time-based UUID using OrderedTime

Error message

Attempting to decode a non-time-based UUID using OrderedTimeCodec

What it means

After rearranging bytes back to standard layout and building the UUID, OrderedTimeCodec::decodeBytes() verifies the result is a version 1 (time-based) RFC 4122 UUID and throws UnsupportedOperationException otherwise. So the input was 16 bytes but did not decode to v1: it is either another version (v4 random, v3/v5 name-based) or a standard-layout v1 binary that was never put through OrderedTimeCodec::encodeBinary().

Source

Thrown at src/Codec/OrderedTimeCodec.php:94

     */
    public function decodeBytes(string $bytes): UuidInterface
    {
        if (strlen($bytes) !== 16) {
            throw new InvalidArgumentException('$bytes string should contain 16 characters.');
        }

        // Rearrange the bytes to their original order.
        $rearrangedBytes = $bytes[4] . $bytes[5] . $bytes[6] . $bytes[7]
            . $bytes[2] . $bytes[3] . $bytes[0] . $bytes[1]
            . substr($bytes, 8);

        $uuid = parent::decodeBytes($rearrangedBytes);

        /** @phpstan-ignore possiblyImpure.methodCall */
        $fields = $uuid->getFields();

        if (!$fields instanceof Rfc4122FieldsInterface || $fields->getVersion() !== Uuid::UUID_TYPE_TIME) {
            throw new UnsupportedOperationException(
                'Attempting to decode a non-time-based UUID using OrderedTimeCodec',
            );
        }

        return $uuid;
    }
}

View on GitHub (pinned to da5b521600)

Solutions

  1. Decode with the same codec family that encoded the bytes: StringCodec::decodeBytes() (or the default factory) for standard-layout v1 binaries.
  2. Migrate legacy rows once: decode with StringCodec, re-encode with OrderedTimeCodec, so a single layout remains in storage.
  3. If input may be mixed, decode with StringCodec and branch on $uuid->getFields()->getVersion() === 1 instead of assuming layout.

Example fix

// before
$uuid = $orderedTimeCodec->decodeBytes($bytes); // bytes were written by the default codec

// after
$uuid = $stringCodec->decodeBytes($bytes); // standard layout decodes fine
// optional one-time migration to ordered layout:
// $orderedBytes = $orderedTimeCodec->encodeBinary($uuid);
Defensive patterns

Strategy: try-catch

Validate before calling

// If rows may be standard-layout, decode with StringCodec first and verify version:
$uuid = $stringCodec->decodeBytes($bytes);
$fields = $uuid->getFields();
if (!$fields instanceof \Ramsey\Uuid\Rfc4122\FieldsInterface || $fields->getVersion() !== Uuid::UUID_TYPE_TIME) {
    // not a v1 at all - handle before involving OrderedTimeCodec
}

Type guard

function decodesToTimeUuid(string $standardBytes, StringCodec $codec): bool
{
    $fields = $codec->decodeBytes($standardBytes)->getFields();

    return $fields instanceof \Ramsey\Uuid\Rfc4122\FieldsInterface
        && $fields->getVersion() === \Ramsey\Uuid\Uuid::UUID_TYPE_TIME;
}

Try / catch

try {
    $uuid = $orderedCodec->decodeBytes($bytes);
} catch (\Ramsey\Uuid\Exception\UnsupportedOperationException $e) {
    // Legacy standard-layout row: retry with the standard codec.
    $uuid = $stringCodec->decodeBytes($bytes);
    // optionally schedule migration: $orderedCodec->encodeBinary($uuid) if v1
}

Prevention

When it happens

Trigger: Calling OrderedTimeCodec::decodeBytes() on: the bytes of a v4/v3/v5 UUID; a regular (non-reordered) v1 binary produced by the default codec or StringCodec::encodeBinary(); arbitrary 16 bytes whose version nibble is not 1 after rearranging.

Common situations: Encode/decode codec mismatch after adopting ordered-time storage: legacy rows written by StringCodec (or getBytes() on a default factory) decoded by OrderedTimeCodec; mixed old/new rows in one BINARY(16) column; copy of example code that pairs the wrong codec pair.

Related errors


AI-assisted analysis of ramsey/uuid@da5b521600 (2026-08-21). Data as JSON: /api/errors/c6b3d761abc3dd1d. Report an issue: GitHub.