ChatMemberUpdatedFilter
extends Filter
in package
Dispatcher-side filter that matches a `ChatMemberUpdated` event by comparing the wire-level statuses of `old_chat_member` and `new_chat_member`.
Port of aiogram.filters.chat_member_updated.ChatMemberUpdatedFilter
(aiogram/filters/chat_member_updated.py:192-219).
Aiogram DSL → PHP factories
Upstream exposes a Python-only operator DSL on _MemberStatusMarker
instances:
KICKED | LEFT >> +MEMBER # was kicked/left, now member JOIN_TRANSITION = IS_NOT_MEMBER >> IS_MEMBER LEAVE_TRANSITION = ~JOIN_TRANSITION PROMOTED_TRANSITION = (MEMBER | RESTRICTED | LEFT | KICKED) >> ADMINISTRATOR
PHP can't reuse | / >> / ~ between class instances (operator
overloading is limited to a handful of arithmetic methods on numeric
objects via Pure\Math). The port replaces the operator DSL with THREE
factory shapes matching upstream's three rule shapes:
-
newStatus(array $statuses)— constrain only the NEW status (wildcard old side). Mirrors upstream's_MemberStatusMarker/_MemberStatusGroupMarkersingle-axis rule. Internally stored asnew self([], $statuses)whereoldStatuses = []signals "match any". -
transition(array $from, array $to)/ direct constructor: explicitoldStatusesANDnewStatuses. Mirrors upstream's_MemberStatusTransitionold/new rule. -
Named pre-built factories —
join(),leave(),promotion(),demotion()— mirroring upstream'sJOIN_TRANSITION/LEAVE_TRANSITION/PROMOTED_TRANSITIONconstants and a reverse-promotion pre-built. The+/-is_membermodifier onRESTRICTED(upstream'sIS_MEMBERincludes+RESTRICTED,IS_NOT_MEMBERincludes-RESTRICTED) is collapsed: restricted users count as members for the named factories. Callers that need the finer-grainedis_membergate can build a transition by hand and post-filter onoldChatMember->isMember/newChatMember->isMembervia a Logic AND combinator.
Reading status off ChatMember
The abstract ChatMember parent does NOT declare a status property —
each concrete subclass (ChatMemberOwner, ChatMemberAdministrator,
ChatMemberMember, ChatMemberRestricted, ChatMemberLeft,
ChatMemberBanned) carries its own public readonly string $status
defaulted to the discriminator value. The filter resolves the wire
string via a static match-on-class helper so PHPStan level 9 stays
happy without a @property declaration on the abstract parent.
Return shape
Pure bool. The filter contributes no kwargs to the handler — upstream's
__call__ returns plain bool for matching/non-matching transitions.
Table of Contents
Constants
- ADMINISTRATOR : array<string|int, mixed> = ['administrator']
- CREATOR : array<string|int, mixed> = ['creator']
- Singleton-status sets. The names mirror upstream's `_MemberStatusMarker` instances at `chat_member_updated.py:176-181`, exposed here as `list<string>` so they slot directly into the constructor's `oldStatuses` / `newStatuses` parameters.
- IS_ADMIN : array<string|int, mixed> = ['creator', 'administrator']
- Statuses with elevated privileges. Mirrors upstream's `IS_ADMIN = CREATOR | ADMINISTRATOR` at `chat_member_updated.py:184`.
- IS_MEMBER : array<string|int, mixed> = ['creator', 'administrator', 'member', 'restric...
- Statuses representing "currently in the chat". Mirrors upstream's `IS_MEMBER = CREATOR | ADMINISTRATOR | MEMBER | +RESTRICTED` at `chat_member_updated.py:183`. The `+RESTRICTED` nuance (only restricted users with `is_member=True` are members) is collapsed — see class docblock for the workaround.
- IS_NOT_MEMBER : array<string|int, mixed> = ['left', 'kicked']
- Statuses representing "not in the chat". Mirrors upstream's `IS_NOT_MEMBER = LEFT | KICKED | -RESTRICTED` at `chat_member_updated.py:185`. Same `-RESTRICTED` collapse caveat.
- KICKED : array<string|int, mixed> = ['kicked']
- LEFT : array<string|int, mixed> = ['left']
- MEMBER : array<string|int, mixed> = ['member']
- RESTRICTED : array<string|int, mixed> = ['restricted']
Properties
- $newStatuses : array<string|int, mixed>
- $oldStatuses : array<string|int, mixed>
Methods
- __construct() : mixed
- __invoke() : array<string, mixed>|bool
- Evaluate the filter against an update.
- all() : AndFilter
- Compose an AND across filters: every child must accept, kwargs cascade. PHP equivalent of Python's `f1 & f2`.
- any() : OrFilter
- Compose an OR across filters: the first accepting child wins, no cascade. PHP equivalent of Python's `f1 | f2`.
- demotion() : self
- Pre-built `IS_ADMIN → MEMBER`. Symmetric inverse of `promotion()`. No upstream constant — falls out of the marker DSL naturally (`ADMINISTRATOR >> MEMBER` etc).
- invertOf() : InvertFilter
- Invert a filter's accept/reject decision. Named `invertOf` rather than `not` because PHP forbids a static and an instance method sharing one name in a single class (the instance-side `$f->not()` convenience may land in a later task).
- join() : self
- Pre-built `IS_NOT_MEMBER → IS_MEMBER`. Mirrors upstream's `JOIN_TRANSITION` constant at `chat_member_updated.py:187`. Matches a user (re)joining the chat — left/kicked → creator/administrator/ member/restricted.
- leave() : self
- Pre-built `IS_MEMBER → IS_NOT_MEMBER`. Mirrors upstream's `LEAVE_TRANSITION = ~JOIN_TRANSITION` at `chat_member_updated.py:188`.
- newStatus() : self
- Constrain only the NEW status (wildcard old side). Mirrors upstream's `_MemberStatusMarker` / `_MemberStatusGroupMarker` single-axis rule shape — the first of upstream's three rule shapes. Passing `oldStatuses = []` signals "match any old status" inside `__invoke`.
- promotion() : self
- Pre-built `MEMBER → IS_ADMIN`. Narrower than upstream's `PROMOTED_TRANSITION = (MEMBER | RESTRICTED | LEFT | KICKED) >> ADMINISTRATOR` (`chat_member_updated.py:189`) — we restrict the source to a plain `MEMBER` so `promotion()` and `demotion()` stay symmetric. Callers needing the wider upstream rule can call `transition()` directly.
- transition() : self
- Build a transition rule from arbitrary old/new status sets. Equivalent to the constructor but reads declaratively at call sites:
- statusOf() : string
- Resolve the wire-level `status` string for a `ChatMember` union value.
Constants
ADMINISTRATOR
public
array<string|int, mixed>
ADMINISTRATOR
= ['administrator']
CREATOR
Singleton-status sets. The names mirror upstream's `_MemberStatusMarker` instances at `chat_member_updated.py:176-181`, exposed here as `list<string>` so they slot directly into the constructor's `oldStatuses` / `newStatuses` parameters.
public
array<string|int, mixed>
CREATOR
= ['creator']
IS_ADMIN
Statuses with elevated privileges. Mirrors upstream's `IS_ADMIN = CREATOR | ADMINISTRATOR` at `chat_member_updated.py:184`.
public
array<string|int, mixed>
IS_ADMIN
= ['creator', 'administrator']
IS_MEMBER
Statuses representing "currently in the chat". Mirrors upstream's `IS_MEMBER = CREATOR | ADMINISTRATOR | MEMBER | +RESTRICTED` at `chat_member_updated.py:183`. The `+RESTRICTED` nuance (only restricted users with `is_member=True` are members) is collapsed — see class docblock for the workaround.
public
array<string|int, mixed>
IS_MEMBER
= ['creator', 'administrator', 'member', 'restricted']
IS_NOT_MEMBER
Statuses representing "not in the chat". Mirrors upstream's `IS_NOT_MEMBER = LEFT | KICKED | -RESTRICTED` at `chat_member_updated.py:185`. Same `-RESTRICTED` collapse caveat.
public
array<string|int, mixed>
IS_NOT_MEMBER
= ['left', 'kicked']
KICKED
public
array<string|int, mixed>
KICKED
= ['kicked']
LEFT
public
array<string|int, mixed>
LEFT
= ['left']
MEMBER
public
array<string|int, mixed>
MEMBER
= ['member']
RESTRICTED
public
array<string|int, mixed>
RESTRICTED
= ['restricted']
Properties
$newStatuses read-only
public
array<string|int, mixed>
$newStatuses
$oldStatuses read-only
public
array<string|int, mixed>
$oldStatuses
Methods
__construct()
public
__construct(array<int, string> $oldStatuses, array<int, string> $newStatuses) : mixed
Parameters
- $oldStatuses : array<int, string>
-
Accepted statuses for
old_chat_member. - $newStatuses : array<int, string>
-
Accepted statuses for
new_chat_member.
__invoke()
Evaluate the filter against an update.
public
__invoke(object $event, mixed ...$kwargs) : array<string, mixed>|bool
Parameters
- $event : object
- $kwargs : mixed
-
Dispatcher kwargs bag — captured variadically so the full bag passes through
CallableObject::prepareKwargs. Unused by this filter (event-only decision).
Return values
array<string, mixed>|bool —See class docblock for the interpretation contract.
all()
Compose an AND across filters: every child must accept, kwargs cascade. PHP equivalent of Python's `f1 & f2`.
public
static all(Filter ...$filters) : AndFilter
Parameters
- $filters : Filter
Return values
AndFilterany()
Compose an OR across filters: the first accepting child wins, no cascade. PHP equivalent of Python's `f1 | f2`.
public
static any(Filter ...$filters) : OrFilter
Parameters
- $filters : Filter
Return values
OrFilterdemotion()
Pre-built `IS_ADMIN → MEMBER`. Symmetric inverse of `promotion()`. No upstream constant — falls out of the marker DSL naturally (`ADMINISTRATOR >> MEMBER` etc).
public
static demotion() : self
Return values
selfinvertOf()
Invert a filter's accept/reject decision. Named `invertOf` rather than `not` because PHP forbids a static and an instance method sharing one name in a single class (the instance-side `$f->not()` convenience may land in a later task).
public
static invertOf(Filter $filter) : InvertFilter
Parameters
- $filter : Filter
Return values
InvertFilterjoin()
Pre-built `IS_NOT_MEMBER → IS_MEMBER`. Mirrors upstream's `JOIN_TRANSITION` constant at `chat_member_updated.py:187`. Matches a user (re)joining the chat — left/kicked → creator/administrator/ member/restricted.
public
static join() : self
Return values
selfleave()
Pre-built `IS_MEMBER → IS_NOT_MEMBER`. Mirrors upstream's `LEAVE_TRANSITION = ~JOIN_TRANSITION` at `chat_member_updated.py:188`.
public
static leave() : self
Matches a user voluntarily leaving or being kicked/banned.
Return values
selfnewStatus()
Constrain only the NEW status (wildcard old side). Mirrors upstream's `_MemberStatusMarker` / `_MemberStatusGroupMarker` single-axis rule shape — the first of upstream's three rule shapes. Passing `oldStatuses = []` signals "match any old status" inside `__invoke`.
public
static newStatus(array<int, string> $statuses) : self
Example — match any member who becomes an administrator, regardless of their previous status:
ChatMemberUpdatedFilter::newStatus(ChatMemberUpdatedFilter::ADMINISTRATOR)
Parameters
- $statuses : array<int, string>
-
The accepted statuses for
new_chat_member.
Return values
selfpromotion()
Pre-built `MEMBER → IS_ADMIN`. Narrower than upstream's `PROMOTED_TRANSITION = (MEMBER | RESTRICTED | LEFT | KICKED) >> ADMINISTRATOR` (`chat_member_updated.py:189`) — we restrict the source to a plain `MEMBER` so `promotion()` and `demotion()` stay symmetric. Callers needing the wider upstream rule can call `transition()` directly.
public
static promotion() : self
Return values
selftransition()
Build a transition rule from arbitrary old/new status sets. Equivalent to the constructor but reads declaratively at call sites:
public
static transition(array<int, string> $from, array<int, string> $to) : self
ChatMemberUpdatedFilter::transition( from: ChatMemberUpdatedFilter::IS_NOT_MEMBER, to: ChatMemberUpdatedFilter::MEMBER, );
Parameters
- $from : array<int, string>
- $to : array<int, string>
Return values
selfstatusOf()
Resolve the wire-level `status` string for a `ChatMember` union value.
private
static statusOf(ChatMember $member) : string
The abstract ChatMember parent doesn't declare status — each
concrete subclass carries its own public readonly string $status
defaulted to its discriminator. PHPStan level 9 won't accept
$member->status on an abstract typed variable; the match over
concrete classes gives the static analyser the discriminator without
runtime reflection. Mirrors upstream's getattr(member, "status", None) indirection at chat_member_updated.py:89 but without the
None fallback because every constructible ChatMember subclass in
the regenerated types module declares a non-null status.
Parameters
- $member : ChatMember