phpbotgram

MagicFilter
in package

FinalYes

Lazy, immutable chain of predicate operations that resolves against a subject value when `resolve()` is called.

Direct port of upstream magic_filter.magic.MagicFilter (magic_filter/magic.py:41-291) plus aiogram's local as_() extension from aiogram/utils/magic_filter.py.

The class exposes three ways to extend a chain:

  1. PHP __get($name)F->message->text resolves to two GetAttributeOperations on the chain.
  2. PHP __call($name, $args)F->message->text->casefold() resolves to [..., GetAttributeOperation('text'), GetAttributeOperation('casefold'), CallOperation([], [])]. We add a GetAttributeOperation for the name followed by a CallOperation for the parentheses — same as Python's __getattr__ then __call__.
  3. Named instance methods (equals, gt, func, cast, regexp, in_, contains, as_, …) — terminal-ish operations that append a purpose-built operation directly.

Each operation-append returns a NEW MagicFilter instance — the chain is immutable so $f = F->text; $g = $f->lower(); doesn't mutate $f.

Logical composition: PHP can't overload operators, so we expose method forms — $f->and_($g) for AND, $f->or_($g) for OR, $f->not() for negation. The static Filter::all($f1, $f2) and Filter::any(…) still work for Filter-level composition; this layer is for MagicFilter-to- MagicFilter composition mid-chain (the rules that ultimately get bridged to a Filter via asFilter()).

Resolution: resolve($value) walks the chain operation-by-operation. Rejections raised by an operation (missing attribute, failed cast, type mismatch) flip the resolver into a "skip-unless-important" state: the value becomes null and only important operations (e.g. NOT, OR) run until the chain ends or an important op rescues the verdict.

Tags
see

/tmp/magic_filter/magic.py for the Python original

Table of Contents

Constants

WILDCARD_ALL  : string = "\x00magic_filter:wildcard_all\x00"
Sentinel for `F->items[MagicFilter::WILDCARD_ALL]` — fan-out + ALL semantic. Matches Python's `F[:]` empty-slice case.
WILDCARD_ANY  : string = "\x00magic_filter:wildcard_any\x00"
Sentinel for `F->items[MagicFilter::WILDCARD_ANY]` — fan-out + ANY semantic. Matches Python's `F[...]` Ellipsis case.

Properties

$operations  : array<int, BaseOperation>

Methods

__call()  : self
Append a `MethodCallOperation` — PHP doesn't expose bound methods as first-class values the way Python does, so we collapse the upstream `__getattr__` then `__call__` pair into a single op that does `$value->{$name}(...$args)` at resolve time.
__construct()  : mixed
Direct construction is private-by-convention: the only public seeds are `MagicFilter::root()` (and the global `F` const). Users extend the chain via `__get` / `__call` / named methods rather than building a chain by hand.
__get()  : self
Append a `GetAttributeOperation` for `$name`. Equivalent of Python's `__getattr__` (`magic.py:99-103`); names starting with an underscore are reserved for PHP internals and raise rather than become chain operations.
__isset()  : bool
Honour `isset($magic->foo)` — without it PHP emits a warning when `??` / null-checks probe a chain attribute. Always returns true so `__get` runs and produces the chain operation.
all()  : self
Fan-out + ALL: shorthand for `$f->item(MagicFilter::WILDCARD_ALL)`.
and_()  : self
AND across chains: `$f->and_($g)` succeeds when both accept. Right- hand operand can be either a `MagicFilter` (resolved against the chain root) or a literal (truthy-AND).
any()  : self
Fan-out + ANY: shorthand for `$f->item(MagicFilter::WILDCARD_ANY)`.
as_()  : self
Append the terminal `AsFilterResultOperation` that converts the chain's final value into a kwarg map (`[$name => $value]`) when accepted, or `null` when rejected. Used downstream by `MagicFilterAsFilter` to thread the value into the dispatcher's kwargs bag.
asBool()  : bool
Truthy check — always `true`. PHP would coerce a `MagicFilter` to `bool` via `is_object($f)` (always truthy) anyway, but we declare the magic method explicitly so `if ($f)` works deterministically and PHPStan can reason about it. Matches upstream's `__bool__` (`magic.py:93-94`).
asFilter()  : Filter
Wrap this chain in a `Filter` instance so the dispatcher can consume it directly. The bridge's `__invoke` runs the chain against the incoming event and converts the result into a `bool|array` filter verdict — see `MagicFilterAsFilter::__invoke` for the exact rules.
attr()  : self
Explicit accessor equivalent to `$this->{$name}` — append a `GetAttributeOperation`. The magic-dispatch alias is the more ergonomic form for hand-written DSL chains; this entry point is for codegen and call sites that prefer static type safety.
casefold()  : self
Alias for `lower` (Python `casefold` is roughly equivalent for ASCII).
cast()  : self
Apply a unary transformation. Used by users for arbitrary projections: `F->count->cast(intval(...))` to coerce a string to int before a subsequent comparison.
contains()  : self
`F.text.contains('hello')` — substring (for strings) / containment (for iterables / `ArrayAccess`) predicate on the running value.
endsWith()  : self
`F.text.endswith('!')` — string-suffix predicate.
eq()  : self
Alias for `equals` so call sites can read more like Python `eq`.
equals()  : self
`F.text == 'hi'`.
extendWith()  : self
Internal-ish: append a generic operation to the chain. Used by the F-DSL builders in `Filters\F\*` and by call sites that need a custom `BaseOperation` subclass beyond the built-ins.
extract()  : self
Filter an iterable subject by a sub-filter. Mirrors upstream `MagicFilter.extract` (`magic.py:289-290`).
func()  : self
Apply an arbitrary callable to the running value. Extra args/kwargs are passed BEFORE the value to match upstream `magic.py:283-284`: `function(*args, value, **kwargs)`.
gt()  : self
gte()  : self
in_()  : self
`F.status.in_({'admin', 'mod'})` → `F->status->in_(['admin', 'mod'])`.
is()  : self
Strict identity comparison (`===`). PHP equivalent of Python's `is`.
isNot()  : self
item()  : self
Append a `GetItemOperation`: `$f->item('key')` is the PHP analogue of `F['key']` in Python (PHP doesn't have user-overloadable subscript on instances outside `ArrayAccess`, and we don't want to commit `MagicFilter` to `ArrayAccess` because the dispatcher distinguishes iterables-vs-MagicFilter via instance checks).
len()  : self
String length / collection size. Mirrors upstream `MagicFilter.len` (`magic.py:250-251`) which wraps `len(value)`. PHP equivalents are `strlen` for strings, `count` for arrays / Countable. The dispatch table picks the right one at resolve time.
lower()  : self
Lowercase. Equivalent of upstream `F.text.lower()` / `F.text.casefold()`.
lt()  : self
lte()  : self
ne()  : self
negate()  : self
Alias for `not_()` to read naturally in user code.
not_()  : self
Negate the chain so far: `$f->not_()` flips the verdict. Implemented via a `NotOperation` (an important function operation wrapping logical NOT) so even a rejected chain (null value) inverts to `true`.
notContains()  : self
Inverse of `contains`.
notEquals()  : self
`F.text != 'hi'`.
notIn()  : self
Inverse of `in_`. Matches upstream `F.not_in(...)` (`magic.py:241-242`).
or_()  : self
OR across chains. The OR combinator is "important" so a left-hand rejection still gives the right-hand a chance to vote. Mirrors upstream `magic.py:152-155`.
regexp()  : self
`F.text.regexp('^/(?<cmd>\\w+)')` — anchor / search / fullmatch the pattern against the running value. The resulting chain value is either a match object (a `string[]` for `MATCH`/`SEARCH`/`FULLMATCH`, a `list<string[]>` for `FINDALL`/`FINDITER`) or `null` on no match — which the resolver collapses to `false` at the chain's terminus.
resolve()  : mixed
Walk the chain against a subject value and return the final value.
root()  : self
Fresh, empty chain — the equivalent of upstream's bare `F`. Used by typed-builder factories (`MessageF::text()` does `new StringField(MagicFilter::root()->text)`).
startsWith()  : self
`F.text.startswith('/')` — string-prefix predicate. Rejects when the value isn't a string (the resolver collapses non-string subjects to a chain rejection via `FunctionOperation`'s catch-all).
upper()  : self
Uppercase. UTF-8 aware via `mb_strtoupper`.
xor_()  : self
Boolean XOR.
excludeLast()  : self
Internal: drop the trailing operation. Used by `__invert()` to fold `~~F` back to `F` (matches upstream `magic.py:69-70`).
extend()  : self
Internal: append an operation and return a new instance. Never mutates `$this`. Mirrors upstream `MagicFilter._extend` (`magic.py:63-64`).
resolveInner()  : mixed
splitArgs()  : array{0: list, 1: array}
Honour `MagicFilter::root()->foo` — without this `__getStatic` PHP still works because `__get` is instance-side; this is documented for completeness only.

Constants

WILDCARD_ALL

Sentinel for `F->items[MagicFilter::WILDCARD_ALL]` — fan-out + ALL semantic. Matches Python's `F[:]` empty-slice case.

public string WILDCARD_ALL = "\x00magic_filter:wildcard_all\x00"

WILDCARD_ANY

Sentinel for `F->items[MagicFilter::WILDCARD_ANY]` — fan-out + ANY semantic. Matches Python's `F[...]` Ellipsis case.

public string WILDCARD_ANY = "\x00magic_filter:wildcard_any\x00"

Properties

Methods

__call()

Append a `MethodCallOperation` — PHP doesn't expose bound methods as first-class values the way Python does, so we collapse the upstream `__getattr__` then `__call__` pair into a single op that does `$value->{$name}(...$args)` at resolve time.

public __call(string $name, array<int|string, mixed> $arguments) : self

Mirrors upstream magic_filter.magic.MagicFilter.__call__ (magic.py:141-142) — except the pair of operations becomes one MethodCallOperation here.

Parameters
$name : string
$arguments : array<int|string, mixed>
Return values
self

__construct()

Direct construction is private-by-convention: the only public seeds are `MagicFilter::root()` (and the global `F` const). Users extend the chain via `__get` / `__call` / named methods rather than building a chain by hand.

public __construct([array<int, BaseOperation$operations = [] ]) : mixed
Parameters
$operations : array<int, BaseOperation> = []

__get()

Append a `GetAttributeOperation` for `$name`. Equivalent of Python's `__getattr__` (`magic.py:99-103`); names starting with an underscore are reserved for PHP internals and raise rather than become chain operations.

public __get(string $name) : self

Both $f->message (magic dispatch) and $f->attr('message') (explicit accessor) reach this code path — call sites that want to keep PHPStan happy without bespoke property declarations should prefer attr().

Parameters
$name : string
Return values
self

__isset()

Honour `isset($magic->foo)` — without it PHP emits a warning when `??` / null-checks probe a chain attribute. Always returns true so `__get` runs and produces the chain operation.

public __isset(string $name) : bool
Parameters
$name : string
Return values
bool

all()

Fan-out + ALL: shorthand for `$f->item(MagicFilter::WILDCARD_ALL)`.

public all() : self

Equivalent of upstream F[:]. Use mid-chain to apply the rest of the chain to every element of an iterable.

Return values
self

and_()

AND across chains: `$f->and_($g)` succeeds when both accept. Right- hand operand can be either a `MagicFilter` (resolved against the chain root) or a literal (truthy-AND).

public and_(mixed $other) : self

and is a reserved word in PHP, hence the trailing underscore.

Parameters
$other : mixed
Return values
self

any()

Fan-out + ANY: shorthand for `$f->item(MagicFilter::WILDCARD_ANY)`.

public any() : self

Equivalent of upstream F[...].

Return values
self

as_()

Append the terminal `AsFilterResultOperation` that converts the chain's final value into a kwarg map (`[$name => $value]`) when accepted, or `null` when rejected. Used downstream by `MagicFilterAsFilter` to thread the value into the dispatcher's kwargs bag.

public as_(string $name) : self

1-for-1 port of aiogram's MagicFilter.as_ (aiogram/utils/magic_filter.py:21-22).

Named as_ (with trailing underscore) because as is a PHP keyword.

Parameters
$name : string
Return values
self

asBool()

Truthy check — always `true`. PHP would coerce a `MagicFilter` to `bool` via `is_object($f)` (always truthy) anyway, but we declare the magic method explicitly so `if ($f)` works deterministically and PHPStan can reason about it. Matches upstream's `__bool__` (`magic.py:93-94`).

public asBool() : bool
Return values
bool

asFilter()

Wrap this chain in a `Filter` instance so the dispatcher can consume it directly. The bridge's `__invoke` runs the chain against the incoming event and converts the result into a `bool|array` filter verdict — see `MagicFilterAsFilter::__invoke` for the exact rules.

public asFilter() : Filter
Return values
Filter

attr()

Explicit accessor equivalent to `$this->{$name}` — append a `GetAttributeOperation`. The magic-dispatch alias is the more ergonomic form for hand-written DSL chains; this entry point is for codegen and call sites that prefer static type safety.

public attr(string $name) : self

Upstream exposes this as MagicFilter.attr_ (magic.py:104).

Parameters
$name : string
Return values
self

casefold()

Alias for `lower` (Python `casefold` is roughly equivalent for ASCII).

public casefold() : self
Return values
self

cast()

Apply a unary transformation. Used by users for arbitrary projections: `F->count->cast(intval(...))` to coerce a string to int before a subsequent comparison.

public cast(callable $func) : self

Mirrors upstream MagicFilter.cast (magic.py:286-287).

Parameters
$func : callable
Return values
self

contains()

`F.text.contains('hello')` — substring (for strings) / containment (for iterables / `ArrayAccess`) predicate on the running value.

public contains(mixed $needle) : self

Mirrors upstream MagicFilter.contains (magic.py:244-245) which itself wraps the polymorphic contains_op from util.py:18-22.

Parameters
$needle : mixed
Return values
self

endsWith()

`F.text.endswith('!')` — string-suffix predicate.

public endsWith(string $suffix) : self
Parameters
$suffix : string
Return values
self

eq()

Alias for `equals` so call sites can read more like Python `eq`.

public eq(mixed $other) : self
Parameters
$other : mixed
Return values
self

equals()

`F.text == 'hi'`.

public equals(mixed $other) : self
Parameters
$other : mixed
Return values
self

extendWith()

Internal-ish: append a generic operation to the chain. Used by the F-DSL builders in `Filters\F\*` and by call sites that need a custom `BaseOperation` subclass beyond the built-ins.

public extendWith(BaseOperation $operation) : self

NOT a substitute for the named methods — equals, func, etc. are the public surface; this is the trapdoor for advanced users.

Parameters
$operation : BaseOperation
Return values
self

extract()

Filter an iterable subject by a sub-filter. Mirrors upstream `MagicFilter.extract` (`magic.py:289-290`).

public extract(self $magic) : self
Parameters
$magic : self
Return values
self

func()

Apply an arbitrary callable to the running value. Extra args/kwargs are passed BEFORE the value to match upstream `magic.py:283-284`: `function(*args, value, **kwargs)`.

public func(callable $function, mixed ...$args) : self
Parameters
$function : callable
$args : mixed

Positional args resolved against the chain root if any are MagicFilter instances.

Return values
self

gt()

public gt(mixed $other) : self
Parameters
$other : mixed
Return values
self

gte()

public gte(mixed $other) : self
Parameters
$other : mixed
Return values
self

in_()

`F.status.in_({'admin', 'mod'})` → `F->status->in_(['admin', 'mod'])`.

public in_(iterable<string|int, mixed>|MagicFilter $haystack) : self

Returns true iff the running value is ==-equal to one of $haystack's entries.

Parameters
$haystack : iterable<string|int, mixed>|MagicFilter
Return values
self

is()

Strict identity comparison (`===`). PHP equivalent of Python's `is`.

public is(mixed $other) : self

Not in upstream as a chain method (upstream uses F.is_() with the underlying operator.is_ callable) — added here for parity with Python's is operator semantics.

Parameters
$other : mixed
Return values
self

isNot()

public isNot(mixed $other) : self
Parameters
$other : mixed
Return values
self

item()

Append a `GetItemOperation`: `$f->item('key')` is the PHP analogue of `F['key']` in Python (PHP doesn't have user-overloadable subscript on instances outside `ArrayAccess`, and we don't want to commit `MagicFilter` to `ArrayAccess` because the dispatcher distinguishes iterables-vs-MagicFilter via instance checks).

public item(mixed $key) : self

When $key is a MagicFilter we treat the call as the "select where inner accepts" semantic — matches F[F.text == 'hi'] upstream.

Parameters
$key : mixed
Return values
self

len()

String length / collection size. Mirrors upstream `MagicFilter.len` (`magic.py:250-251`) which wraps `len(value)`. PHP equivalents are `strlen` for strings, `count` for arrays / Countable. The dispatch table picks the right one at resolve time.

public len() : self
Return values
self

lower()

Lowercase. Equivalent of upstream `F.text.lower()` / `F.text.casefold()`.

public lower() : self

In PHP mb_strtolower handles UTF-8 correctly; we use it instead of strtolower for the same Unicode-correctness guarantee Python gives.

Return values
self

lt()

public lt(mixed $other) : self
Parameters
$other : mixed
Return values
self

lte()

public lte(mixed $other) : self
Parameters
$other : mixed
Return values
self

ne()

public ne(mixed $other) : self
Parameters
$other : mixed
Return values
self

negate()

Alias for `not_()` to read naturally in user code.

public negate() : self
Return values
self

not_()

Negate the chain so far: `$f->not_()` flips the verdict. Implemented via a `NotOperation` (an important function operation wrapping logical NOT) so even a rejected chain (null value) inverts to `true`.

public not_() : self

$f->not_()->not_() folds back to $f — mirrors upstream __invert__ (magic.py:132-139). The fold uses instanceof NotOperation as a typed sentinel rather than a fragile zero-arg structural check.

Named not_ (with trailing underscore) is the user-visible name; PHP forbids a method literally named not because of the precedence / spacing rules around not. We expose not_() and the more readable alias negate().

Return values
self

notContains()

Inverse of `contains`.

public notContains(mixed $needle) : self
Parameters
$needle : mixed
Return values
self

notEquals()

`F.text != 'hi'`.

public notEquals(mixed $other) : self
Parameters
$other : mixed
Return values
self

notIn()

Inverse of `in_`. Matches upstream `F.not_in(...)` (`magic.py:241-242`).

public notIn(iterable<string|int, mixed>|MagicFilter $haystack) : self
Parameters
$haystack : iterable<string|int, mixed>|MagicFilter
Return values
self

or_()

OR across chains. The OR combinator is "important" so a left-hand rejection still gives the right-hand a chance to vote. Mirrors upstream `magic.py:152-155`.

public or_(mixed $other) : self
Parameters
$other : mixed
Return values
self

regexp()

`F.text.regexp('^/(?<cmd>\\w+)')` — anchor / search / fullmatch the pattern against the running value. The resulting chain value is either a match object (a `string[]` for `MATCH`/`SEARCH`/`FULLMATCH`, a `list<string[]>` for `FINDALL`/`FINDITER`) or `null` on no match — which the resolver collapses to `false` at the chain's terminus.

public regexp(string $pattern[, RegexpMode|null $mode = null ][, bool|null $search = null ]) : self

Port of upstream MagicFilter.regexp (magic.py:253-281). The deprecated search boolean (kept upstream for backwards compat) is NOT carried over — modern callers should use mode instead.

Parameters
$pattern : string

A raw PCRE pattern WITHOUT delimiters. The operation builds the final delimited form ('/.../u') internally to handle anchoring per mode.

$mode : RegexpMode|null = null
$search : bool|null = null
Return values
self

resolve()

Walk the chain against a subject value and return the final value.

public resolve(mixed $value) : mixed

null propagates through non-important operations after a rejection.

Mirrors upstream MagicFilter.resolve (magic.py:96-97) which delegates to _resolve.

Parameters
$value : mixed

root()

Fresh, empty chain — the equivalent of upstream's bare `F`. Used by typed-builder factories (`MessageF::text()` does `new StringField(MagicFilter::root()->text)`).

public static root() : self
Return values
self

startsWith()

`F.text.startswith('/')` — string-prefix predicate. Rejects when the value isn't a string (the resolver collapses non-string subjects to a chain rejection via `FunctionOperation`'s catch-all).

public startsWith(string $prefix) : self
Parameters
$prefix : string
Return values
self

upper()

Uppercase. UTF-8 aware via `mb_strtoupper`.

public upper() : self
Return values
self

xor_()

Boolean XOR.

public xor_(mixed $other) : self
Parameters
$other : mixed
Return values
self

excludeLast()

Internal: drop the trailing operation. Used by `__invert()` to fold `~~F` back to `F` (matches upstream `magic.py:69-70`).

private excludeLast() : self
Return values
self

extend()

Internal: append an operation and return a new instance. Never mutates `$this`. Mirrors upstream `MagicFilter._extend` (`magic.py:63-64`).

private extend(BaseOperation $operation) : self
Parameters
$operation : BaseOperation
Return values
self

splitArgs()

Honour `MagicFilter::root()->foo` — without this `__getStatic` PHP still works because `__get` is instance-side; this is documented for completeness only.

private splitArgs(array<int|string, mixed> $arguments) : array{0: list, 1: array}

Split a flat __call argument bag into positional vs named pieces. PHP gives us a numerically-indexed array for purely positional calls and string-keyed entries for any named-argument call. We preserve insertion order so func($a, b: 1, $c) is reassembled correctly.

Parameters
$arguments : array<int|string, mixed>
Return values
array{0: list, 1: array}
On this page

Search results