phpbotgram

CallbackData
in package

AbstractYes

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 readonly properties assigned in the constructor body (derived/computed fields) are excluded from the wire form, just as Pydantic's model_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 to null).
  • bool'1' / '0' (matches upstream str(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, hence LogicException not InvalidArgumentException.

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
CallbackQueryFilter

pack()

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:

  1. Field order is always the constructor declaration order, matching unpack()'s iteration order exactly.
  2. Non-promoted public readonly properties that are computed inside the constructor body are excluded from the wire form — only constructor parameters are serialised, mirroring upstream's model_dump() which iterates Pydantic model fields (declared in the model's __init__ signature), not arbitrary attributes set in the body.
Tags
throws
LogicException

If the subclass is missing the #[CallbackPrefix] attribute, has no constructor, contains an unencodable property value, or the result exceeds 64 bytes.

Return values
string

unpack()

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
throws
InvalidArgumentException

When the prefix doesn't match.

LogicException

When the subclass is malformed (missing attribute, missing constructor, undecodable target type).

Return values
static

decodeBool()

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
bool

decodeComplex()

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:

  1. Empty wire + nullable + non-empty-string default → return default.
  2. Empty wire + nullable + no default → return null.
  3. 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.
  4. Empty wire + non-nullable → fall through to scalar coercion path (may raise TypeError for 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
string

encodeValue()

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
string

reflectMeta()

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
Return values
CallbackPrefix
On this page

Search results