ramsey/uuid · error · InvalidArgumentException

Fields used to create a UuidV2 must represent a version 2 (D

Error message

Fields used to create a UuidV2 must represent a version 2 (DCE Security) UUID

What it means

Ramsey\Uuid\Rfc4122\UuidV2 wraps an existing field set that must represent a version 2 (DCE Security) UUID. Its constructor verifies the version nibble via $fields->getVersion() against Uuid::UUID_TYPE_DCE_SECURITY (2) and throws Ramsey\Uuid\Exception\InvalidArgumentException on any other value. The guard exists because UuidV2 exposes version-specific accessors (getLocalDomain(), getLocalIdentifier(), getLocalNode()) that would return meaningless data for mismatched fields. In normal usage you never call this constructor directly; the factory and builder call it only for bytes whose version nibble is 2.

Source

Thrown at src/Rfc4122/UuidV2.php:70

    use TimeTrait;

    /**
     * Creates a version 2 (DCE Security) UUID
     *
     * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID
     * @param NumberConverterInterface $numberConverter The number converter to use for converting hex values to/from integers
     * @param CodecInterface $codec The codec to use when encoding or decoding UUID strings
     * @param TimeConverterInterface $timeConverter The time converter to use for converting timestamps extracted from a
     *     UUID to unix timestamps
     */
    public function __construct(
        Rfc4122FieldsInterface $fields,
        NumberConverterInterface $numberConverter,
        CodecInterface $codec,
        TimeConverterInterface $timeConverter,
    ) {
        if ($fields->getVersion() !== Uuid::UUID_TYPE_DCE_SECURITY) {
            throw new InvalidArgumentException(
                'Fields used to create a UuidV2 must represent a version 2 (DCE Security) UUID'
            );
        }

        parent::__construct($fields, $numberConverter, $codec, $timeConverter);
    }

    /**
     * Returns the local domain used to create this version 2 UUID
     */
    public function getLocalDomain(): int
    {
        /** @var Rfc4122FieldsInterface $fields */
        $fields = $this->getFields();

        return (int) hexdec($fields->getClockSeqLow()->toString());
    }

View on GitHub (pinned to da5b521600)

Solutions

  1. Generate version 2 UUIDs through the factory: Uuid::uuid2($localDomain, $identifier, $node, $clockSeq) instead of new UuidV2(...)
  2. When wrapping existing values, use Uuid::fromString($string) or Uuid::fromBytes($bytes) — the builder selects the version-matching class automatically
  3. If you must construct manually, first verify $fields->getVersion() === Uuid::UUID_TYPE_DCE_SECURITY and fix the version bits in the input bytes when it differs
  4. In a custom builder, switch on $fields->getVersion() and instantiate UuidV2 only for version 2; fall back to the base Ramsey\Uuid\Uuid class for other versions

Example fix

// before: fields carry version 4 bits, but UuidV2 is constructed directly
$uuid = new UuidV2($fields, $numberConverter, $codec, $timeConverter);
// InvalidArgumentException: Fields used to create a UuidV2 must represent a version 2 (DCE Security) UUID

// after: generate v2 UUIDs through the factory
$uuid = Uuid::uuid2(Uuid::DCE_DOMAIN_PERSON, 1001);

// or wrap existing bytes with the version-agnostic builder
$uuid = Uuid::fromBytes($bytes); // returns UuidV2 only when the version nibble is 2
Defensive patterns

Strategy: validation

Validate before calling

use Ramsey\Uuid\Rfc4122\FieldsInterface;
use Ramsey\Uuid\Uuid;

// Run before constructing UuidV2
if ($fields->getVersion() !== Uuid::UUID_TYPE_DCE_SECURITY) {
    // do not construct UuidV2 with these fields
    throw new \InvalidArgumentException(
        'Cannot build UuidV2 from version ' . $fields->getVersion() . ' fields'
    );
}

Type guard

use Ramsey\Uuid\Rfc4122\FieldsInterface;
use Ramsey\Uuid\Rfc4122\UuidV2;
use Ramsey\Uuid\Uuid;

function isVersion2Fields(FieldsInterface $fields): bool
{
    return $fields->getVersion() === Uuid::UUID_TYPE_DCE_SECURITY;
}

// after decoding, narrow instead of assuming
$uuid = Uuid::fromString($value);
if ($uuid instanceof UuidV2) {
    $domain = $uuid->getLocalDomain();
}

Try / catch

use Ramsey\Uuid\Exception\InvalidArgumentException;

try {
    $uuid = new UuidV2($fields, $numberConverter, $codec, $timeConverter);
} catch (InvalidArgumentException $e) {
    // Log the actual version and fall back to the version-agnostic class
    $uuid = new Ramsey\Uuid\Uuid($fields, $numberConverter, $codec, $timeConverter);
}

Prevention

When it happens

Trigger: Calling new UuidV2($fields, $numberConverter, $codec, $timeConverter) with a Ramsey\Uuid\Rfc4122\Fields instance whose version nibble is not 2 — for example fields decoded from a v4 string via a codec, or bytes produced by Uuid::uuid4()/uuid1(). Also a custom Builder or Codec that unconditionally instantiates UuidV2 for every UUID it decodes.

Common situations: Copying low-level constructor examples instead of using the factory; custom codecs/builders (e.g. GUID byte-order handling on little-endian systems) that map every decoded UUID to one version class; test fixtures that hand-assemble field sets with the wrong version bits; mutating UUID bytes without fixing the version nibble.

Related errors


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