Skip to content

Conversation

@plumthedev
Copy link
Contributor

@plumthedev plumthedev commented Jan 3, 2026

first-class support for binary-stored identifiers in Laravel while preserving a string-based API in application code.

What’s included

  • AsBinary Eloquent cast
    Transparent binary ↔ string conversion with an explicit required / optional contract.

  • BinaryCodec utility
    Centralized, pluggable encode/decode registry.

  • Built-in codecs

    • uuid
    • ulid
  • is_binary() helper
    Detects raw binary vs UTF-8 strings.

Why

Binary storage of UUID/ULID provides smaller indexes and better performance, but Laravel lacks a native, generic solution. This PR adds a framework-level primitive, not a UUID-specific abstraction.

How it works

Eloquent cast

protected $casts = [
    'id' => AsBinary::uuid(isRequired: true),
    'public_id' => AsBinary::ulid(), // optional by default
    'custom' => AsBinary::of('xor1', false),
];
  • Values are stored as binary bytes

  • Values are exposed as strings in PHP

  • The isRequired flag:

    • enforces non-blank decoded values at runtime
    • explicitly communicates nullability / presence intent
    • can be leveraged by static analysis, IDE tooling, or future framework validation

This allows the cast definition itself to describe whether an attribute is logically required, not just how it is encoded.

Custom codecs

BinaryCodec::register(
    'xor1',
    encode: fn (?string $v) => $v === null ? null : ($v ^ "\xFF"),
    decode: fn (?string $v) => $v === null ? null : ($v ^ "\xFF"),
);
  • Codecs are resolved by name
  • Registration is runtime-safe (e.g. service providers)
  • Codecs accept either string or binary input

When to use

Recommended for:

  • UUID/ULID primary or foreign keys
  • high-cardinality indexed columns
  • large datasets where index size and cache locality matter

Guarantees

  • Fully opt-in
  • No breaking changes
  • No schema assumptions
  • Explicit format validation
  • Extensible without modifying framework code

@plumthedev plumthedev changed the title [12.x] Add AsBinary castable class with support for binary UUID [12.x] Add AsBinary castable class Jan 3, 2026
@plumthedev
Copy link
Contributor Author

I believe this is ready for a final review.

The scope is intentionally minimal and fully opt-in. The implementation introduces a generic binary codec primitive, a corresponding Eloquent cast, and clear semantics around required vs optional attributes, without imposing schema or identifier strategy changes.

Unless there are objections or additional edge cases to cover, this should be ready to merge.

@taylorotwell taylorotwell merged commit 5e6fa3b into laravel:12.x Jan 5, 2026
70 checks passed
@plumthedev
Copy link
Contributor Author

Thanks @taylorotwell for accepting the merge request.

I noticed that the PHPStan generic annotation CastsAttributes<string|null, string|null> and the corresponding requirement check were removed from the get method.

Could you share the reasoning behind this change? From my perspective, keeping this information could be beneficial for static analysis — for example, tools like IDE helpers or Larastan could infer more precise types from it.

I’m curious to understand the trade-offs that led to this decision.

@smares
Copy link

smares commented Jan 6, 2026

Is this feature going to appear and be explained in the documentation?

@shaedrich
Copy link
Contributor

@smares Do you mean the casts table or the model keys section?

@smares
Copy link

smares commented Jan 6, 2026

The AsBinary cast what this MR implemented

@plumthedev
Copy link
Contributor Author

@smares I think so, but I would suggest documenting it once we introduce full, first-class support for binary primary and foreign keys. At that point, the documentation can clearly describe the complete, end-to-end experience rather than just the isolated AsBinary cast.

Here is the roadmap: #51605 (reply in thread)

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.

4 participants