DiscordPHP
AGENTS.md1.1k starsChanged 35 days agoMITView on GitHub
This file is the repo operating manual for AI agents. It describes how to work inside this repository without breaking its design. Specialist skills in .agents/skills/ provide deeper playbooks for specific layers. If a change crosses layers, load multiple skills and use the playbooks in this file to keep boundaries clean. Before changing anything: In this repo, companion surfaces usually matter as much as the line you edit. For Compatibility Watch findings, Discord API/schema drift, or an issue labelled openapi,…
- Reads credentials
Trending
More talk lately.about the whole repository
Who's talking about discord-php/DiscordPHP
- @throwable.bsky.social“📦 team-reflex/discord-php v10.45.21 An unofficial API to interact with the voice and text service Discord. …”Bluesky · Feb 27, 2026 · 1 likes
- crimsonstrife/streamer.live“Bump team-reflex/discord-php from 10.18.24 to 10.18.45”GitHub · Aug 19, 2025 · 1 reactions
- crimsonstrife/streamer.live“Bump team-reflex/discord-php from 10.18.0 to 10.18.5”GitHub · Jul 8, 2025 · 1 reactions
- crimsonstrife/streamer.live“build(deps): bump team-reflex/discord-php from 10.17.0 to 10.17.3”GitHub · Jun 16, 2025 · 1 reactions
- crimsonstrife/streamer.live“build(deps): bump team-reflex/discord-php from 10.13.1 to 10.14.0”GitHub · Jun 9, 2025 · 1 reactions
The biggest 5 of 52 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 52
Talked about on GitHub and Bluesky (52 times).
On GitHub · 50
- Aug 19, 2025Bump team-reflex/discord-php from 10.18.24 to 10.18.45 — crimsonstrife/streamer.live1 reaction · 2 comments
- Jul 8, 2025Bump team-reflex/discord-php from 10.18.0 to 10.18.5 — crimsonstrife/streamer.live1 reaction · 3 comments
- Jun 16, 2025build(deps): bump team-reflex/discord-php from 10.17.0 to 10.17.3 — crimsonstrife/streamer.live1 reaction · 3 comments
- Jun 9, 2025build(deps): bump team-reflex/discord-php from 10.13.1 to 10.14.0 — crimsonstrife/streamer.live1 reaction · 3 comments
- Jun 2, 2025build(deps): bump team-reflex/discord-php from 10.10.12 to 10.11.6 — crimsonstrife/streamer.live1 reaction · 3 comments
45 more on GitHub
- May 26, 2025build(deps): bump team-reflex/discord-php from 10.10.0 to 10.10.6 — crimsonstrife/streamer.live1 reaction · 2 comments
- May 19, 2025build(deps): bump team-reflex/discord-php from 10.7.3 to 10.8.5 — crimsonstrife/streamer.live1 reaction · 2 comments
- May 5, 2025build(deps): bump team-reflex/discord-php from 10.7.1 to 10.7.2 — crimsonstrife/streamer.live1 reaction · 2 comments
- Apr 28, 2025build(deps): bump team-reflex/discord-php from 10.4.7 to 10.7.1 — crimsonstrife/streamer.live1 reaction · 3 comments
- Mar 25, 2025build(deps): bump team-reflex/discord-php from 10.4.3 to 10.4.4 — crimsonstrife/streamer.live1 reaction · 3 comments
- Jul 18, 2024✨ Add support for polls — laracord/framework1 reaction
- May 11, 2024update d.py/d.php polls — advaith1/libs1 reaction · 1 comment
- Nov 2, 2026build(deps): update team-reflex/discord-php requirement from 10.66.* to 10.71.* — smrealms/smr0 reactions
- Oct 26, 2026build(deps): update team-reflex/discord-php requirement from 10.66.* to 10.69.* — smrealms/smr0 reactions · 1 comment
- Oct 20, 2026build(deps): update team-reflex/discord-php requirement from 10.66.* to 10.68.* — smrealms/smr0 reactions · 1 comment
- Oct 19, 2026Bump team-reflex/discord-php from 10.59.0 to 10.67.0 — omnicolor/commlink0 reactions · 1 comment
- Oct 14, 2026build(deps): update team-reflex/discord-php requirement from 10.66.* to 10.67.* — smrealms/smr0 reactions · 1 comment
- Oct 13, 2026Bump team-reflex/discord-php from 10.59.0 to 10.66.3 — omnicolor/commlink0 reactions · 1 comment
- Oct 6, 2026Bump team-reflex/discord-php from 10.59.0 to 10.66.1 — omnicolor/commlink0 reactions · 1 comment
- Oct 5, 2026Bump team-reflex/discord-php from 10.59.0 to 10.65.0 — omnicolor/commlink0 reactions · 1 comment
- Oct 1, 2026build(deps): update team-reflex/discord-php requirement from 10.49.* to 10.66.* — smrealms/smr0 reactions
- Oct 1, 2026Bump team-reflex/discord-php from 10.57.0 to 10.59.0 — omnicolor/commlink0 reactions
- Sep 30, 2026build(deps): update team-reflex/discord-php requirement from 10.49.* to 10.62.* — smrealms/smr0 reactions · 1 comment
- Sep 29, 2026Bump team-reflex/discord-php from 10.57.0 to 10.58.0 — omnicolor/commlink0 reactions · 1 comment
- Sep 28, 2026build(deps): update team-reflex/discord-php requirement from 10.49.* to 10.59.* — smrealms/smr0 reactions · 1 comment
- Sep 25, 2026build(deps): update team-reflex/discord-php requirement from 10.49.* to 10.58.* — smrealms/smr0 reactions · 1 comment
- Sep 25, 2026Bump team-reflex/discord-php from 10.56.18 to 10.57.0 — omnicolor/commlink0 reactions
- Sep 23, 2026Bump team-reflex/discord-php from 10.56.17 to 10.56.18 — omnicolor/commlink0 reactions
- Sep 21, 2026build(deps): update team-reflex/discord-php requirement from 10.49.* to 10.57.* — smrealms/smr0 reactions · 1 comment
- Sep 14, 2026Bump team-reflex/discord-php from 10.56.15 to 10.56.17 — omnicolor/commlink0 reactions
- Sep 7, 2026Bump team-reflex/discord-php from 10.56.10 to 10.56.15 — omnicolor/commlink0 reactions
- Sep 7, 2026build: Bump team-reflex/discord-php from 10.48.14 to 10.56.15 — VATSIM-UK/core0 reactions
- Sep 4, 2026Bump team-reflex/discord-php from 10.56.10 to 10.56.13 — omnicolor/commlink0 reactions · 1 comment
- Sep 2, 2026Bump team-reflex/discord-php from 10.56.8 to 10.56.10 — omnicolor/commlink0 reactions
- Sep 1, 2026Bump team-reflex/discord-php from 10.56.8 to 10.56.9 — omnicolor/commlink0 reactions · 1 comment
- Aug 26, 2026Bump team-reflex/discord-php from 10.55.1 to 10.56.5 — omnicolor/commlink0 reactions
- Aug 25, 2026Bump team-reflex/discord-php from 10.55.1 to 10.56.3 — omnicolor/commlink0 reactions · 1 comment
- Aug 18, 2026build(deps): update team-reflex/discord-php requirement from 10.49.* to 10.56.* — smrealms/smr0 reactions · 1 comment
- Apr 20, 2026Bump team-reflex/discord-php from 10.47.12 to 10.48.1 — crimsonstrife/streamer.live0 reactions · 1 comment
- Feb 10, 2026Bump team-reflex/discord-php from 10.45.15 to 10.45.16 — crimsonstrife/streamer.live0 reactions · 3 comments
- Nov 4, 2025Add Discord Announcement Bot System — k33bz/lastwar-server15860 reactions · 4 comments
- Oct 20, 2025Tag "@property" has error when both ? and null types are included — phpDocumentor/phpDocumentor0 reactions · 1 comment
- Nov 15, 2024Update dependency team-reflex/discord-php to v10 — System3-2/discord-bot0 reactions
- Feb 11, 2024Thread gets terminated when using a slash command with options. — DiscordBot-PMMP/DiscordBot0 reactions · 3 comments
- Jan 14, 2024Update dependency team-reflex/discord-php to v7.3.5 — Karhal/discord-gpt0 reactions
- Jan 14, 2024Update dependency team-reflex/discord-php to v7.3.5 — System3-2/discord-bot0 reactions
- Jan 31, 2023Invalid Activity URL — DiscordBot-PMMP/DiscordBot0 reactions · 3 comments
- Dec 20, 2022Another message type not supported when delete the message — DiscordBot-PMMP/DiscordBot0 reactions · 6 comments
- Aug 19, 2022[Egg Request]: DiscordPHP — pelican-eggs/eggs0 reactions · 1 comment
- Apr 17, 2022Intents help — DiscordBot-PMMP/DiscordBot0 reactions · 4 comments
On Bluesky · 2
- Feb 27, 2026📦 team-reflex/discord-php v10.45.21 An unofficial API to interact with the voice and text service Discord. 🔗… — @throwable.bsky.social1 likes and repost
- Aug 10, 2026📦 team-reflex/discord-php v10.55.0 An unofficial API to interact with the voice and text service Discord. 🔗… — @throwable.bsky.social0 likes and reposts · 1 reply
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository14 other files it gives its agents.
- async-test-and-doc-syncSkill
- builder-payload-smithSkill
- gateway-cache-sync-keeperSkill
- helpers-and-infra-keeperSkill
- interaction-flow-keeperSkill
- legacy-command-client-keeperSkill
- part-model-maintainerSkill
- repository-cache-keeperSkill
- runtime-bootstrap-keeperSkill
- type-map-keeperSkill
- voice-subsystem-keeperSkill
- discord-php-bot-securitySkill
- discord-php-extensionSkill
- discord-php-interactionsSkill
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
56 rules. About 0 are common, 43 are distinctive, and 13 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈43
Distinctive · estimate
This author's own choices. Worth a look.
13
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 5
If a change crosses layers, load multiple skills and use the playbooks in this file to keep boundaries clean.
- Distinctive: line 12
Read the base abstraction for that layer before copying a concrete class.
- Distinctive: line 13
Read one representative concrete implementation from the same family.
- Distinctive: line 16
Update companion surfaces that define the same contract.
- Distinctive: line 27
Builders own outbound payload rules: If a payload has meaningful shape or validation, it usually deserves a builder.
- Distinctive: line 30
Type maps are central dispatch points: If a Discord payload is polymorphic, there is usually one place that decides the concrete subtype.
- Very specific: line 31
Use current library idioms: Prefer Factory::part() / Factory::repository(), prefer $part->save($reason) over repository save($part) in …
- Distinctive: line 78
When editing endpoint bindings, REST routes, cache storage, or voice audio, you are necessarily touching these packages' contracts.
- Distinctive: line 88
If you put transport logic in parts, parts stop being stable domain objects.
- Distinctive: line 89
If you put domain validation in repositories, repository APIs stop being predictable.
- Distinctive: line 90
If you skip builders, payload rules spread across unrelated methods.
- Distinctive: line 91
If gateway handlers do not update caches correctly, every downstream relation becomes stale.
- Distinctive: line 167
update events often return both new and old state
- Distinctive: line 168
delete events often return cached removed state, not only the raw payload id
- Distinctive: line 175
If a part can be saved:
- Distinctive: line 184
If userland needs to construct a non-trivial outbound payload:
- Distinctive: line 186
add or extend a builder
- Distinctive: line 187
keep raw-array construction as an implementation detail only when a builder would be needless overhead
- Distinctive: line 193
If Discord can tell us something through gateway dispatch:
- Distinctive: line 195
prefer updating cache from the event instead of forcing later REST refetches
- Distinctive: line 196
keep parent/child repository relationships coherent at the same time
- Distinctive: line 218
When a Discord discriminator chooses a subtype:
- Distinctive: line 221
update all materialization sites that depend on that family
- Distinctive: line 227
If you touch one of these, inspect the companions too:
- Distinctive: line 248
Add or adjust mutators for typed nested data, computed properties, or normalization.
- Very specific: line 250
Update $repositories if a new child repository is exposed.
- Very specific: line 251
Update getRepositoryAttributes() if child route vars changed.
- Very specific: line 252
Update getCreatableAttributes() / getUpdatableAttributes() if persistence shape changed.
- Very specific: line 253
Update getRepository() or save() if ownership or permission rules changed.
- Distinctive: line 254
Check gateway events and repositories that hydrate or cache the part.
- Distinctive: line 255
Add or update tests.
- Distinctive: line 261
Keep REST writes and cache writes in sync.
- Very specific: line 263
Check owning parts for getRepository() and getRepositoryAttributes() assumptions.
- Distinctive: line 264
Update tests and docs if public behavior changed.
- Distinctive: line 268
Put payload validation in setters/adders.
- Distinctive: line 269
Keep fluent chaining style.
- Very specific: line 270
Keep jsonSerialize() aligned with Discord docs and existing payload semantics.
- Very specific: line 271
Keep fromPart() edit symmetry in mind.
- Distinctive: line 272
Update tests for validation limits and payload shape.
- Distinctive: line 273
Update docs/examples if the preferred public construction path changed.
- Very specific: line 277
Add or update the event constant in Event.php if needed.
- Very specific: line 278
Add or update the handler registration in Handlers.php.
- Distinctive: line 280
Update every affected repository and relation cache.
- Distinctive: line 287
Keep application-command and prefix-command layers separate.
- Distinctive: line 288
Keep interaction typing and resolved-data hydration intact.
- Distinctive: line 289
Keep builders as the outbound authoring path for commands, components, and modals.
- Distinctive: line 290
Avoid slow interaction-time work that can delay a response.
- Distinctive: line 291
Update both inbound event handling and outbound builder/docs surfaces if public behavior shifts.
- Distinctive: line 295
If you see one of these, slow down:
- Distinctive: line 311
When you need an example worth imitating, start here:
- Very specific: line 332
All unit-test classes extend DiscordTestCase.
- Very specific: line 333
Use $this->getMockBuilder() for isolated class dependencies and getMockDiscord() when a lightweight real Discord client is needed.
- Very specific: line 334
Use and return wait() from tests/functions.php whenever a test performs asynchronous work so PHPUnit tracks it.
- Distinctive: line 335
Keep semantic tests focused on behavior, not on incidental implementation details.
- Distinctive: line 344
Keep docs in sync when preferred usage or public contracts change.
- Distinctive: line 364
When unsure where code belongs, choose the layer that already owns the same kind of knowledge elsewhere in the repo.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
DiscordPHP Agent Guide
This file is the repo operating manual for AI agents. It describes how to work inside this repository without breaking its design. Specialist skills in .agents/skills/ provide deeper playbooks for specific layers.
If a change crosses layers, load multiple skills and use the playbooks in this file to keep boundaries clean.
Start here
Before changing anything:
- Identify the layer you are touching.
- Read the base abstraction for that layer before copying a concrete class.
- Read one representative concrete implementation from the same family.
- Trace how that layer connects to adjacent layers.
- Make the change in the narrowest layer that can own it.
- Update companion surfaces that define the same contract.
In this repo, companion surfaces usually matter as much as the line you edit.
Non-negotiable truths
- CLI-only runtime. DiscordPHP is a long-running process built on ReactPHP. Do not design around web requests, controllers, middleware stacks, or per-request state.
- Async first. Production I/O is Promise-based. Blocking helpers belong in tests only.
- Parts are canonical domain objects. They model Discord resources and expose typed magic properties.
- Repositories are persistence and cache boundaries. They are not generic service classes.
- Gateway handlers keep caches coherent. They do more than relay notifications.
- Builders own outbound payload rules. If a payload has meaningful shape or validation, it usually deserves a builder.
- Docblocks are runtime-adjacent documentation. They are not optional decoration.
- Traits are preferred over deep inheritance or broad interface hierarchies for shared behavior.
- Type maps are central dispatch points. If a Discord payload is polymorphic, there is usually one place that decides the concrete subtype.
- Use current library idioms. Prefer
Factory::part()/Factory::repository(), prefer$part->save($reason)over repositorysave($part)in user-facing paths, and prefer builder->create($repository)helpers where they exist.
Skill map
For Compatibility Watch findings, Discord API/schema drift, or an issue labelled openapi, read the compatibility handoff. Reuse the existing OpenAPI check and issue; retain source/spec revisions and prove affected runtime behavior separately from schema coverage. Do not accept a baseline merely to silence a new finding.
Each skill lives in .agents/skills/<name>/SKILL.md and is loaded automatically when relevant. When a task crosses layers, load multiple skills.
| If task touches... | Skill to load |
Discord.php, startup, intents, cache, loop, gateway connection | runtime-bootstrap-keeper |
Parts/*, domain modeling, mutators, typed nested data | part-model-maintainer |
Repository/*, endpoint vars, cache, CRUD, fetch/save/delete | repository-cache-keeper |
WebSockets/Handlers.php, WebSockets/Event.php, WebSockets/Events/* | gateway-cache-sync-keeper |
Builders/*, Builders/Components/*, outbound payload rules | builder-payload-smith |
subtype maps like Channel::TYPES or Interaction::TYPES | type-map-keeper |
| interactions, slash commands, resolved data, autocomplete, modals | interaction-flow-keeper |
DiscordCommandClient or prefix-command behavior | legacy-command-client-keeper |
| tests, guides, docblocks, generated reference expectations | async-test-and-doc-sync |
Voice/*, audio streaming, encryption, voice gateway protocol | voice-subsystem-keeper |
Helpers/*, Exceptions/*, cache wrappers, Endpoint::bind(), Collection | helpers-and-infra-keeper |
Architecture map
| Layer | Owns | Primary files | What to preserve |
| Runtime | process lifecycle, options, loop, gateway, HTTP, root repos | src/Discord/Discord.php | __construct() wires dependencies and connects; run() only starts loop |
| Factory | part/repository instantiation | src/Discord/Factory/Factory.php | callers should not construct repo families ad hoc |
| Parts | domain objects, mutators, typed nested data, high-level operations | src/Discord/Parts/Part.php, src/Discord/Parts/PartTrait.php, src/Discord/Parts/**/* | $fillable, mutators, PHPDoc, save() semantics, created lifecycle |
| Repositories | typed collections, cache, REST endpoints, CRUD | src/Discord/Repository/AbstractRepository.php, src/Discord/Repository/**/* | $class, $endpoints, $vars, cache writes, Promise-based API |
| Builders | outbound payload construction and validation | src/Discord/Builders/**/* | fluent setters, validation, jsonSerialize(), fromPart() symmetry |
| Gateway events | payload hydration, cache mutation, emitted return shapes | src/Discord/WebSockets/Handlers.php, src/Discord/WebSockets/Event.php, src/Discord/WebSockets/Events/* | typed part creation, related cache updates, event contract shape |
| Helpers | cross-cutting utilities: cache wrappers, BigInt math, multipart uploads, property mutator trait, domain exceptions | src/Discord/Helpers/*, src/Discord/Exceptions/* | no domain logic here; utilities only |
| Voice | internal voice protocol types and encryption; runtime integration in Discord.php; audio client in external discord-php-helpers/voice package | src/Discord/Voice/* | Old* files are legacy — do not extend them; keep protocol and crypto layers separate |
| Optional command layer | message-prefix command UX | src/Discord/DiscordCommandClient.php, src/Discord/CommandClient/Command.php | keep it layered on top of core client, not inside it |
| Tests and docs | behavioral contract | tests/*, guide/*, README.md, docs/* | async testing patterns, public guidance, docblock reference surface |
External packages
Three external packages are tightly coupled to core runtime behavior. Treat them as first-class parts of the architecture:
| Package | Why it matters | Local touchpoints |
discord-php/http | HTTP client and Endpoint URL template binding used in every repository; handles rate-limiting via Bucket | $endpoints arrays in all repositories; use Discord\Http\Endpoint imports |
discord-php-helpers/collection | Collection class is the base for every AbstractRepository; discriminator-keyed typed collections | AbstractRepository extends Collection; $discrim property |
discord-php-helpers/voice | Manager and VoiceClient implement the actual audio client; src/Discord/Voice/* contains only internal protocol types and encryption | Discord::joinVoiceChannel(), voice event handlers in Discord.php |
When editing endpoint bindings, REST routes, cache storage, or voice audio, you are necessarily touching these packages' contracts.
Repo worldview
Runtime to domain flow
Discord owns bootstrapping. Gateway dispatch goes through Handlers into a dedicated event class. Event classes translate payloads into typed parts and update repositories. Repositories provide cache and REST persistence. Parts expose that state through magic properties and typed helpers. Builders assemble outbound payloads for operations that would otherwise become fragile arrays.
Why the split matters
- If you put transport logic in parts, parts stop being stable domain objects.
- If you put domain validation in repositories, repository APIs stop being predictable.
- If you skip builders, payload rules spread across unrelated methods.
- If gateway handlers do not update caches correctly, every downstream relation becomes stale.
Common class patterns
Parts
Expect these elements on real resource models:
- large class-level
@propertyand@property-readdocblocks protected $fillable = [...]- optional
protected $repositories = [...] - constants mirroring Discord enums or flags
getXAttribute()andsetXAttribute()mutators- overrides for
getCreatableAttributes(),getUpdatableAttributes(),getRepository(),save(),fetch(), orgetRepositoryAttributes()when the part is persistable
Semantic rules:
- raw attribute names stay snake_case to match Discord payloads
- convenience relations (
guild,channel,owner,member) are usually computed, not directly stored - nested typed data should become a
Part, typed collection, orCarbonvalue through helper methods - permission checks for high-level mutations belong on the part before repository delegation
createdtells you whether the object already exists remotely- a payload that is an existing part's object plus a few fields gets a subclass of that part, which merges only its extra keys into the inherited
$fillable - a subclass's override declares the narrowest return type it can truly return (PHP return types are covariant), and every path must honour it. For example,
GameDirectMessage::getChannelAttribute()returnsChannelwhereMessage's returnsPart, because a game DM's channel can never be aThread - inherited methods that can never work in a subclass's context return a rejected promise (
\BadMethodCallException) instead of sending a request Discord will refuse. Seepart-model-maintainerfor all three - parts live under the namespace of the API route they come from: a message, its attachments, reactions and embeds are under
Parts\Channel\Messagebecause they come from/channels/{channel.id}/messages. A part that moves keeps its old name working through aclass_alias()at the end ofsrc/Discord/functions.php, which Composer loads eagerly, soinstanceof, type hints and cached (serialised) data with the old name still work
Repositories
Expect these elements:
extends AbstractRepositoryprotected $class = SomePart::classprotected $endpoints = [...]- optional constructor normalization for route vars
- occasional domain-specific convenience methods
Semantic rules:
- repositories are typed collections plus REST/cache wrappers
create()builds a local part;save()persists it$varscarries parent route context likeguild_idorchannel_id- cache writes must stay aligned with REST writes and gateway updates
- special methods should still return typed parts or repositories, not loose payloads
Builders
Expect these elements:
extends Builderimplements JsonSerializable- fluent
setX()/getX()methods - eager validation in setters or adders
new()factory method on most builderscreate($repository)helper on newer builders
Semantic rules:
- builders are not parts and should not own persistence or cache logic
- validation belongs here when it describes outgoing payload shape
jsonSerialize()should omit unset optionals when Discord distinguishes missing from explicit nullfromPart()should make edit flows symmetrical
Gateway events
Expect these elements:
- one class per event type under
src/Discord/WebSockets/Events - matching constant in
src/Discord/WebSockets/Event.php - matching registration in
src/Discord/WebSockets/Handlers.php handle($data)method returning typed semantic values
Semantic rules:
- event handlers should hydrate the right subtype on first read
- event handlers are responsible for repository/cache coherence
- update events often return both new and old state
- delete events often return cached removed state, not only the raw payload id
- related user/member caches usually need updating too
Cross-layer rules
1. Parts delegate persistence; repositories own REST
If a part can be saved:
- the part decides whether the action is allowed and which repository owns it
- the repository decides how to call Discord and update cache
Smell: a part method manually building endpoints and PATCH payloads when a repository already exists for that family.
2. Parts own semantics; builders own payload ergonomics
If userland needs to construct a non-trivial outbound payload:
- add or extend a builder
- keep raw-array construction as an implementation detail only when a builder would be needless overhead
Smell: multiple methods assembling the same nested array by hand.
3. Gateway events own reactive cache updates
If Discord can tell us something through gateway dispatch:
- prefer updating cache from the event instead of forcing later REST refetches
- keep parent/child repository relationships coherent at the same time
Smell: event handler creates a part but does not update the repository that should own it.
4. Traits carry horizontal behavior
The codebase prefers traits for shared capabilities across sibling models.
Examples:
PartTraitfor universal part mechanicsChannelTraitfor channel/thread behaviorGuildTraitfor guild asset and feature helpersDynamicPropertyMutatorTraitfor builder/property mutators (src/Discord/Helpers/DynamicPropertyMutatorTrait.php)AbstractRepositoryTraitfor repository collection mechanicsComponentsTraitfor shared component helpers across builders (src/Discord/Builders/ComponentsTrait.php)VoiceGroupCryptoTraitfor voice channel encryption/decryption
Smell: new abstract intermediate base class that only exists to share a few methods between peers that already have a common root.
5. Type maps beat repeated branching
When a Discord discriminator chooses a subtype:
- extend the relevant
TYPESmap - update all materialization sites that depend on that family
Smell: one event handler special-cases a new subtype but the central type map still does not know it.
Common companion surfaces
If you touch one of these, inspect the companions too:
| Touching | Also inspect |
src/Discord/Parts/Part.php or PartTrait.php | representative parts, PartInterface, generated docblocks |
a concrete Part | owning repository, related trait, event handlers, tests, docs |
| a nested repository relationship | parent part $repositories, getRepositoryAttributes(), repository constructor vars |
src/Discord/Repository/AbstractRepository* | representative repos, cache wrapper behavior, part save/fetch overrides |
| a gateway event class | Event.php, Handlers.php, related part/repository, tests |
| a builder | matching part, callers, tests, docs/examples |
Channel::TYPES, Interaction::TYPES, component/ embed maps | event handlers, typed collection helpers, builder mirrors |
DiscordCommandClient | CommandClient/Command.php, examples/docs |
| public magic properties | class PHPDoc, guide docs if user-facing behavior changed |
Helpers/CacheWrapper.php or cache config | AbstractRepository cache behavior, Discord.php options['cache'] wiring |
Voice/* or voice event in Discord.php | VoiceGroupCrypto, VoicePacket, external voice package entry points, voice opcodes |
Change playbooks
Playbook: editing a Part
- Update
$fillable. - Add or adjust mutators for typed nested data, computed properties, or normalization.
- Update class docblocks.
- Update
$repositoriesif a new child repository is exposed. - Update
getRepositoryAttributes()if child route vars changed. - Update
getCreatableAttributes()/getUpdatableAttributes()if persistence shape changed. - Update
getRepository()orsave()if ownership or permission rules changed. - Check gateway events and repositories that hydrate or cache the part.
- Add or update tests.
Playbook: editing a Repository
- Confirm
$class,$discrim, and$endpointsstill describe the family correctly. - Confirm parent route vars are complete and in the correct shape.
- Keep REST writes and cache writes in sync.
- Return typed values.
- Check owning parts for
getRepository()andgetRepositoryAttributes()assumptions. - Update tests and docs if public behavior changed.
Playbook: editing a Builder
- Put payload validation in setters/adders.
- Keep fluent chaining style.
- Keep
jsonSerialize()aligned with Discord docs and existing payload semantics. - Keep
fromPart()edit symmetry in mind. - Update tests for validation limits and payload shape.
- Update docs/examples if the preferred public construction path changed.
Playbook: editing a gateway event
- Add or update the event constant in
Event.phpif needed. - Add or update the handler registration in
Handlers.php. - Hydrate the correct subtype.
- Update every affected repository and relation cache.
- Preserve event return shape expected by userland listeners.
- Cache related users/members if the payload supplies them.
- Re-check any intent-gated or partial-data behavior.
Playbook: editing interactions or commands
- Keep application-command and prefix-command layers separate.
- Keep interaction typing and resolved-data hydration intact.
- Keep builders as the outbound authoring path for commands, components, and modals.
- Avoid slow interaction-time work that can delay a response.
- Update both inbound event handling and outbound builder/docs surfaces if public behavior shifts.
Design tripwires
If you see one of these, slow down:
- a new raw nested array where a typed part already exists
- a repository method returning raw decoded payload instead of a part
- a part saving itself with hand-built endpoints even though an owning repository exists
- a new subtype without a
TYPESmap entry - a magic property added in code but not in docblocks
- a gateway handler that updates one cache but leaves related repositories stale
- a synchronous wait or loop-stop trick outside tests
- a new abstraction layer that duplicates what parts, repositories, events, or builders already do
- web-framework terminology creeping into core runtime code
- extending
OldVoiceClientor anyOld*class insrc/Discord/Voice/unless deliberately fixing legacy behavior - bypassing
Endpoint::bind()with hand-assembled raw URL strings
Preferred reference files
When you need an example worth imitating, start here:
- Runtime orchestration:
src/Discord/Discord.php - Base part mechanics:
src/Discord/Parts/Part.php,src/Discord/Parts/PartTrait.php - Rich part model:
src/Discord/Parts/Guild/Guild.php - Channel/resource semantics:
src/Discord/Parts/Channel/Channel.php - Message semantics and repository binding:
src/Discord/Parts/Channel/Message/Message.php - Repository baseline:
src/Discord/Repository/AbstractRepository.php,src/Discord/Repository/AbstractRepositoryTrait.php - Simple repo specialization:
src/Discord/Repository/Channel/MessageRepository.php - Rich repo specialization:
src/Discord/Repository/GuildRepository.php - Outbound builder style:
src/Discord/Builders/MessageBuilder.php - Interaction typing:
src/Discord/Parts/Interactions/Interaction.php - Gateway cache mutation:
src/Discord/WebSockets/Events/MessageCreate.php,src/Discord/WebSockets/Events/GuildCreate.php - Optional command layer:
src/Discord/DiscordCommandClient.php,src/Discord/CommandClient/Command.php - Cache infrastructure:
src/Discord/Helpers/CacheWrapper.php,src/Discord/Helpers/CacheConfig.php - Voice protocol and encryption:
src/Discord/Voice/VoiceGroupCrypto.php,src/Discord/Voice/VoicePacket.php
Testing and docs workflow
Tests
- All unit-test classes extend
DiscordTestCase. - Use
$this->getMockBuilder()for isolated class dependencies andgetMockDiscord()when a lightweight realDiscordclient is needed. - Use and return
wait()fromtests/functions.phpwhenever a test performs asynchronous work so PHPUnit tracks it. - Keep semantic tests focused on behavior, not on incidental implementation details.
- When a test requires a live
DiscordorMessageCommandClientinstance (for example, to exercise registration, event handling, or runtime integration), obtain it via thewait(function (Discord $discord, $resolve) { ... })pattern and return thatwait()call. Use$this->getMockBuilder()for isolated class dependencies andgetMockDiscord()when a lightweight real client is needed without network access.
Docs
- Public magic properties, repositories, and helpers should be reflected in PHPDoc.
- Long-form guides live in
guide/. - Gatsby docs live in
docs/. - Keep docs in sync when preferred usage or public contracts change.
Useful commands
| Purpose | Command |
| main PHPUnit suite | composer unit |
| static analysis | composer run-script mago-lint |
| formatter contributors run | composer run-script cs |
| non-mutating Pint check | ./vendor/bin/pint --test --config ./pint.json ./src |
| Pint formatter (auto-fix) | composer pint |
| test coverage report | composer coverage |
| compare with Discord's live OpenAPI description | composer openapi (-- --all lists the known gaps too) |
| accept the live spec and today's gaps as the baseline | composer openapi:update |
| docs build | cd docs && yarn install && yarn build |
Integration tests expect .env values for DISCORD_TOKEN, TEST_CHANNEL, and TEST_CHANNEL_NAME. Without DISCORD_TOKEN the suite runs offline: tests that answer requests with getMockHttpDriver() run, and only those that call $this->channel() skip. .github/workflows/tests.yml runs it that way on every push and pull request; unit.yml runs it live, by hand.
Final rule
When unsure where code belongs, choose the layer that already owns the same kind of knowledge elsewhere in the repo. Matching the existing ownership model matters more than shaving a few lines off one class.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

