ramsey/uuid · error · InvalidArgumentException

Expected version 1 (time-based) UUID

Error message

Expected version 1 (time-based) UUID

What it means

OrderedTimeCodec::encodeBinary() rearranges the timestamp bytes of a UUID so v1 UUIDs sort naturally in database indexes; it accepts only version 1 (time-based) RFC 4122 UUIDs. If the fields object is not a Rfc4122FieldsInterface or the version nibble is not 1 (Uuid::UUID_TYPE_TIME), it throws InvalidArgumentException. You hit it by asking a non-v1 UUID carrying this codec for its bytes (getBytes() delegates to encodeBinary()).

Source

Thrown at src/Codec/OrderedTimeCodec.php:59

 *
 * @immutable
 */
class OrderedTimeCodec extends StringCodec
{
    /**
     * Returns a binary string representation of a UUID, with the timestamp fields rearranged for optimized storage
     *
     * @return non-empty-string
     */
    public function encodeBinary(UuidInterface $uuid): string
    {
        if (
            /** @phpstan-ignore possiblyImpure.methodCall */
            !($uuid->getFields() instanceof Rfc4122FieldsInterface)
            /** @phpstan-ignore possiblyImpure.methodCall */
            || $uuid->getFields()->getVersion() !== Uuid::UUID_TYPE_TIME
        ) {
            throw new InvalidArgumentException('Expected version 1 (time-based) UUID');
        }

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

        return $bytes[6] . $bytes[7] . $bytes[4] . $bytes[5]
            . $bytes[0] . $bytes[1] . $bytes[2] . $bytes[3]
            . substr($bytes, 8);
    }

    /**
     * Returns a UuidInterface derived from an ordered-time binary string representation
     *
     * @throws InvalidArgumentException if $bytes is an invalid length
     *
     * @inheritDoc
     */
    public function decodeBytes(string $bytes): UuidInterface

View on GitHub (pinned to da5b521600)

Solutions

  1. Use OrderedTimeCodec only with version 1 UUIDs (Uuid::uuid1()); leave the default codec (or StringCodec) on factories that produce other versions.
  2. Keep two factory/codec contexts: one OrderedTimeCodec factory for v1, one default factory for everything else.
  3. For time-ordered non-v1 identifiers prefer UUIDv7 (Uuid::uuid7()) or a COMB with TimestampFirstCombCodec instead of ordered-time v1.

Example fix

// before
$factory->setCodec(new OrderedTimeCodec($factory->getUuidBuilder()));
$bytes = $factory->uuid4()->getBytes(); // InvalidArgumentException

// after
$factory->setCodec(new OrderedTimeCodec($factory->getUuidBuilder()));
$bytes = $factory->uuid1()->getBytes(); // 16 ordered-time bytes
Defensive patterns

Strategy: type-guard

Validate before calling

// Only encode time-based UUIDs through the ordered-time codec.
$fields = $uuid->getFields();
if ($fields instanceof \Ramsey\Uuid\Rfc4122\FieldsInterface && $fields->getVersion() === Uuid::UUID_TYPE_TIME) {
    $orderedBytes = $orderedCodec->encodeBinary($uuid);
}

Type guard

/** @psalm-assert-if-true \Ramsey\Uuid\Rfc4122\UuidV1 $uuid */
function isUuidV1(UuidInterface $uuid): bool
{
    return $uuid instanceof \Ramsey\Uuid\Rfc4122\UuidV1;
}

Try / catch

try {
    $bytes = $uuid->getBytes();
} catch (\Ramsey\Uuid\Exception\InvalidArgumentException $e) {
    // Non-v1 UUID reached the ordered-time codec; fall back to standard bytes.
    $bytes = $uuid->getFields()->getBytes();
}

Prevention

When it happens

Trigger: $factory->setCodec(new OrderedTimeCodec($factory->getUuidBuilder())) set factory-wide, then $factory->uuid4()->getBytes() (or uuid3/uuid5); building a v3/v4/v5 Uuid with an OrderedTimeCodec and serializing it to bytes.

Common situations: Enabling ordered-time storage for index friendliness while still generating v4 random UUIDs through the same factory; mixing COMB/ordered-time strategy with non-time UUID versions; codec switched globally instead of per-version.

Related errors


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