CallbackData
in package
Abstract base for typed callback-data payloads embedded inside `InlineKeyboardButton::$callbackData`. Mirrors `aiogram.filters.callback_data.CallbackData` (`aiogram/filters/callback_data.py:34-149`).
Subclass shape
Subclasses declare a class-level #[CallbackPrefix('prefix', sep: ':')]
attribute plus constructor-promoted readonly public properties:
#[CallbackPrefix('order')] final class OrderCallback extends CallbackData { public function __construct( public readonly int $id, public readonly string $action, public readonly bool $deleted, ) } }
The wire form is prefix:val1:val2:.... Both pack() and unpack()
iterate the constructor parameter list to determine which fields to
serialise and in what order. This guarantees:
- Field order is always the constructor declaration order for both directions.
- Non-promoted
public readonlyproperties assigned in the constructor body (derived/computed fields) are excluded from the wire form, just as Pydantic'smodel_dump()only includes model fields (declared in__init__), not arbitrary attributes set inside the body.
The standard subclass shape remains constructor-promoted properties only, which satisfies both constraints automatically.
Type-encoding table
Mirrors aiogram/filters/callback_data.py:67-82:
null→''(empty wire segment; nullable typed properties decode the empty segment back tonull).bool→'1'/'0'(matches upstreamstr(int(value))).int,float→(string)$value.string→ as-is.\Stringable(Decimal/BigDecimal/Fraction equivalents) →(string)$value.\UnitEnum(backed enum) →$value->value.- Anything else →
\LogicException. Programming error rather than user input, henceLogicExceptionnotInvalidArgumentException.
UUID is mentioned in the upstream spec; we defer the dedicated branch
until ramsey/uuid is wired as a dependency. UUID objects that
implement \Stringable (the v4 default) flow through the Stringable
branch already.
Size cap
Telegram limits CallbackQuery::$data to 64 UTF-8 bytes
(MAX_CALLBACK_LENGTH = 64). pack() measures byte length via
strlen — PHP strings are byte sequences, and the protocol expects
UTF-8, so byte count == UTF-8 byte count as long as the input is
properly UTF-8 (responsibility of the caller). Throws \LogicException
when the payload overflows: it's a programming error, not user input.
Table of Contents
Constants
- MAX_CALLBACK_LENGTH : int = 64
- Telegram-imposed maximum wire length in UTF-8 bytes.
Methods
- filter() : CallbackQueryFilter
- Build a `CallbackQueryFilter` bound to this subclass. Handler:
- pack() : string
- Encode `$this` into the wire form `prefix:val1:val2:...`.
- unpack() : static
- Decode a wire payload back into an instance of `static`. Iterates the constructor parameters and maps each segment by position; the declaration order MUST match the public property declaration order (true by default for promoted-property subclasses).
- decodeBool() : bool
- Decode a boolean wire segment. Accepts `'1'`/`'0'` (our preferred pack form) plus `'true'`/`'false'` (case-insensitive) for forward compatibility with upstream's `_encode_value` accepting either.
- decodeComplex() : mixed
- Decode into a complex (non-scalar) target type. Currently supports backed enums via `::from($raw)`. Other complex types (Stringable value objects, UUIDs, etc.) require subclass-side overrides because we can't know how to reconstruct an arbitrary value object from a plain wire string.
- decodeValue() : mixed
- Decode a wire segment back to a typed value. The parameter reflection gives us the target type; we dispatch per scalar/complex.
- encodeEnum() : string
- Encode a UnitEnum value. Backed enums (`enum X: string {}` / `enum X: int {}`) expose `->value`; pure UnitEnums fall back to the case name. Mirrors upstream's `str(value.value)` which works for Python's `Enum` (both backed and unbacked, where the backing value is the case name string by default).
- encodeValue() : string
- Encode a single property value to its wire string form. See class docblock for the full type-encoding table.
- reflectMeta() : CallbackPrefix
- Read the `#[CallbackPrefix]` metadata for `$class`. Validates that the attribute is present and that the separator is not contained inside the prefix (mirroring upstream's `__init_subclass__` check at `aiogram/filters/callback_data.py:59-64`).
Constants
MAX_CALLBACK_LENGTH
Telegram-imposed maximum wire length in UTF-8 bytes.
public
int
MAX_CALLBACK_LENGTH
= 64
Methods
filter()
Build a `CallbackQueryFilter` bound to this subclass. Handler:
public
static filter() : CallbackQueryFilter
$router->callbackQuery()->register($fn, MyCallbackData::filter());
Mirrors upstream's cls.filter(rule=...) classmethod
(aiogram/filters/callback_data.py:141-149). The rule (MagicFilter)
argument lands in Phase 4.5+; the current Task 4.8 surface keeps the
factory parameter-less.
Return values
CallbackQueryFilterpack()
Encode `$this` into the wire form `prefix:val1:val2:...`.
public
pack() : string
Iterates the constructor's parameter list (the same source unpack()
uses) rather than getProperties(IS_PUBLIC). This guarantees:
- Field order is always the constructor declaration order, matching
unpack()'s iteration order exactly. - Non-promoted
public readonlyproperties that are computed inside the constructor body are excluded from the wire form — only constructor parameters are serialised, mirroring upstream'smodel_dump()which iterates Pydantic model fields (declared in the model's__init__signature), not arbitrary attributes set in the body.
Tags
Return values
stringunpack()
Decode a wire payload back into an instance of `static`. Iterates the constructor parameters and maps each segment by position; the declaration order MUST match the public property declaration order (true by default for promoted-property subclasses).
public
static unpack(string $data) : static
Parameters
- $data : string
Tags
Return values
staticdecodeBool()
Decode a boolean wire segment. Accepts `'1'`/`'0'` (our preferred pack form) plus `'true'`/`'false'` (case-insensitive) for forward compatibility with upstream's `_encode_value` accepting either.
private
static decodeBool(string $raw) : bool
Parameters
- $raw : string
Return values
booldecodeComplex()
Decode into a complex (non-scalar) target type. Currently supports backed enums via `::from($raw)`. Other complex types (Stringable value objects, UUIDs, etc.) require subclass-side overrides because we can't know how to reconstruct an arbitrary value object from a plain wire string.
private
static decodeComplex(string $raw, ReflectionNamedType $type) : mixed
Parameters
- $raw : string
- $type : ReflectionNamedType
decodeValue()
Decode a wire segment back to a typed value. The parameter reflection gives us the target type; we dispatch per scalar/complex.
private
static decodeValue(string $raw, ReflectionParameter $param) : mixed
Nullable parameters with an empty wire segment decode according to
upstream callback_data.py:131-137:
if v == "" and nullable and field.default != "": return field.default if field.default is not PydanticUndefined else None
Upstream contract decomposed:
- Empty wire + nullable + non-empty-string default → return default.
- Empty wire + nullable + no default → return
null. - Empty wire + nullable + empty-string default (
?string $foo = '') → fall through to$raw(i.e.'').field.default != ""fails, so upstream does NOT return default/null — the empty wire round-trips as the empty string. - Empty wire + non-nullable → fall through to scalar coercion path
(may raise
TypeErrorfor int/bool/float).
Parameters
- $raw : string
- $param : ReflectionParameter
encodeEnum()
Encode a UnitEnum value. Backed enums (`enum X: string {}` / `enum X: int {}`) expose `->value`; pure UnitEnums fall back to the case name. Mirrors upstream's `str(value.value)` which works for Python's `Enum` (both backed and unbacked, where the backing value is the case name string by default).
private
static encodeEnum(UnitEnum $value) : string
Parameters
- $value : UnitEnum
Return values
stringencodeValue()
Encode a single property value to its wire string form. See class docblock for the full type-encoding table.
private
static encodeValue(mixed $value) : string
Parameters
- $value : mixed
Return values
stringreflectMeta()
Read the `#[CallbackPrefix]` metadata for `$class`. Validates that the attribute is present and that the separator is not contained inside the prefix (mirroring upstream's `__init_subclass__` check at `aiogram/filters/callback_data.py:59-64`).
private
static reflectMeta(class-string $class) : CallbackPrefix
Parameters
- $class : class-string