php artisan boost:add-skill team-reflex/discord-php
Save this content to: AGENTS.md
---
package: team-reflex/discord-php
source_path: AGENTS.md
repo: https://github.com/discord-php/DiscordPHP
---
# 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:
1. Identify the layer you are touching.
2. Read the base abstraction for that layer before copying a concrete class.
3. Read one representative concrete implementation from the same family.
4. Trace how that layer connects to adjacent layers.
5. Make the change in the narrowest layer that can own it.
6. 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
1. **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.
2. **Async first.** Production I/O is Promise-based. Blocking helpers belong in tests only.
3. **Parts are canonical domain objects.** They model Discord resources and expose typed magic properties.
4. **Repositories are persistence and cache boundaries.** They are not generic service classes.
5. **Gateway handlers keep caches coherent.** They do more than relay notifications.
6. **Builders own outbound payload rules.** If a payload has meaningful shape or validation, it usually deserves a builder.
7. **Docblocks are runtime-adjacent documentation.** They are not optional decoration.
8. **Traits are preferred over deep inheritance or broad interface hierarchies for shared behavior.**
9. **Type maps are central dispatch points.** If a Discord payload is polymorphic, there is usually one place that decides the concrete subtype.
10. **Use current library idioms.** Prefer `Factory::part()` / `Factory::repository()`, prefer `$part->save($reason)` over repository `save($part)` in user-facing paths, and prefer builder `->create($repository)` helpers where they exist.
## Skill map
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 `@property` and `@property-read` docblocks
- `protected $fillable = [...]`
- optional `protected $repositories = [...]`
- constants mirroring Discord enums or flags
- `getXAttribute()` and `setXAttribute()` mutators
- overrides for `getCreatableAttributes()`, `getUpdatableAttributes()`, `getRepository()`, `save()`, `fetch()`, or `getRepositoryAttributes()` 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, or `Carbon` value through helper methods
- permission checks for high-level mutations belong on the part before repository delegation
- `created` tells you whether the object already exists remotely
### Repositories
Expect these elements:
- `extends AbstractRepository`
- `protected $class = SomePart::class`
- `protected $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
- `$vars` carries parent route context like `guild_id` or `channel_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 Builder`
- `implements JsonSerializable`
- fluent `setX()` / `getX()` methods
- eager validation in setters or adders
- `new()` factory method on most builders
- `create($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 null
- `fromPart()` 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:
- `PartTrait` for universal part mechanics
- `ChannelTrait` for channel/thread behavior
- `GuildTrait` for guild asset and feature helpers
- `DynamicPropertyMutatorTrait` for builder/property mutators (`src/Discord/Helpers/DynamicPropertyMutatorTrait.php`)
- `AbstractRepositoryTrait` for repository collection mechanics
- `ComponentsTrait` for shared component helpers across builders (`src/Discord/Builders/ComponentsTrait.php`)
- `VoiceGroupCryptoTrait` for 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 `TYPES` map
- 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
1. Update `$fillable`.
2. Add or adjust mutators for typed nested data, computed properties, or normalization.
3. Update class docblocks.
4. Update `$repositories` if a new child repository is exposed.
5. Update `getRepositoryAttributes()` if child route vars changed.
6. Update `getCreatableAttributes()` / `getUpdatableAttributes()` if persistence shape changed.
7. Update `getRepository()` or `save()` if ownership or permission rules changed.
8. Check gateway events and repositories that hydrate or cache the part.
9. Add or update tests.
### Playbook: editing a Repository
1. Confirm `$class`, `$discrim`, and `$endpoints` still describe the family correctly.
2. Confirm parent route vars are complete and in the correct shape.
3. Keep REST writes and cache writes in sync.
4. Return typed values.
5. Check owning parts for `getRepository()` and `getRepositoryAttributes()` assumptions.
6. Update tests and docs if public behavior changed.
### Playbook: editing a Builder
1. Put payload validation in setters/adders.
2. Keep fluent chaining style.
3. Keep `jsonSerialize()` aligned with Discord docs and existing payload semantics.
4. Keep `fromPart()` edit symmetry in mind.
5. Update tests for validation limits and payload shape.
6. Update docs/examples if the preferred public construction path changed.
### Playbook: editing a gateway event
1. Add or update the event constant in `Event.php` if needed.
2. Add or update the handler registration in `Handlers.php`.
3. Hydrate the correct subtype.
4. Update every affected repository and relation cache.
5. Preserve event return shape expected by userland listeners.
6. Cache related users/members if the payload supplies them.
7. Re-check any intent-gated or partial-data behavior.
### Playbook: editing interactions or commands
1. Keep application-command and prefix-command layers separate.
2. Keep interaction typing and resolved-data hydration intact.
3. Keep builders as the outbound authoring path for commands, components, and modals.
4. Avoid slow interaction-time work that can delay a response.
5. 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 `TYPES` map 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 `OldVoiceClient` or any `Old*` class in `src/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.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
- Prefer plain `PHPUnit\Framework\TestCase` when logic is isolated.
- Use `DiscordTestCase` only when real Discord integration matters.
- Use `wait()` from `tests/functions.php` to bridge promises into test assertions.
- Keep semantic tests focused on behavior, not on incidental implementation details.
### 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` |
| docs build | `cd docs && yarn install && yarn build` |
Integration tests expect `.env` values for `DISCORD_TOKEN`, `TEST_CHANNEL`, and `TEST_CHANNEL_NAME`.
## 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.
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.
Factory::part() / Factory::repository(), prefer $part->save($reason) over repository save($part) in user-facing paths, and prefer builder ->create($repository) helpers where they exist.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 |
| 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 |
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.
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.
Expect these elements on real resource models:
@property and @property-read docblocksprotected $fillable = [...]protected $repositories = [...]getXAttribute() and setXAttribute() mutatorsgetCreatableAttributes(), getUpdatableAttributes(), getRepository(), save(), fetch(), or getRepositoryAttributes() when the part is persistableSemantic rules:
guild, channel, owner, member) are usually computed, not directly storedPart, typed collection, or Carbon value through helper methodscreated tells you whether the object already exists remotelyExpect these elements:
extends AbstractRepositoryprotected $class = SomePart::classprotected $endpoints = [...]Semantic rules:
create() builds a local part; save() persists it$vars carries parent route context like guild_id or channel_idExpect these elements:
extends Builderimplements JsonSerializablesetX() / getX() methodsnew() factory method on most builderscreate($repository) helper on newer buildersSemantic rules:
jsonSerialize() should omit unset optionals when Discord distinguishes missing from explicit nullfromPart() should make edit flows symmetricalExpect these elements:
src/Discord/WebSockets/Eventssrc/Discord/WebSockets/Event.phpsrc/Discord/WebSockets/Handlers.phphandle($data) method returning typed semantic valuesSemantic rules:
If a part can be saved:
Smell: a part method manually building endpoints and PATCH payloads when a repository already exists for that family.
If userland needs to construct a non-trivial outbound payload:
Smell: multiple methods assembling the same nested array by hand.
If Discord can tell us something through gateway dispatch:
Smell: event handler creates a part but does not update the repository that should own it.
The codebase prefers traits for shared capabilities across sibling models.
Examples:
PartTrait for universal part mechanicsChannelTrait for channel/thread behaviorGuildTrait for guild asset and feature helpersDynamicPropertyMutatorTrait for builder/property mutators (src/Discord/Helpers/DynamicPropertyMutatorTrait.php)AbstractRepositoryTrait for repository collection mechanicsComponentsTrait for shared component helpers across builders (src/Discord/Builders/ComponentsTrait.php)VoiceGroupCryptoTrait for voice channel encryption/decryptionSmell: new abstract intermediate base class that only exists to share a few methods between peers that already have a common root.
When a Discord discriminator chooses a subtype:
TYPES mapSmell: one event handler special-cases a new subtype but the central type map still does not know it.
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 |
$fillable.$repositories if a new child repository is exposed.getRepositoryAttributes() if child route vars changed.getCreatableAttributes() / getUpdatableAttributes() if persistence shape changed.getRepository() or save() if ownership or permission rules changed.$class, $discrim, and $endpoints still describe the family correctly.getRepository() and getRepositoryAttributes() assumptions.jsonSerialize() aligned with Discord docs and existing payload semantics.fromPart() edit symmetry in mind.Event.php if needed.Handlers.php.If you see one of these, slow down:
TYPES map entryOldVoiceClient or any Old* class in src/Discord/Voice/ unless deliberately fixing legacy behaviorEndpoint::bind() with hand-assembled raw URL stringsWhen you need an example worth imitating, start here:
src/Discord/Discord.phpsrc/Discord/Parts/Part.php, src/Discord/Parts/PartTrait.phpsrc/Discord/Parts/Guild/Guild.phpsrc/Discord/Parts/Channel/Channel.phpsrc/Discord/Parts/Channel/Message.phpsrc/Discord/Repository/AbstractRepository.php, src/Discord/Repository/AbstractRepositoryTrait.phpsrc/Discord/Repository/Channel/MessageRepository.phpsrc/Discord/Repository/GuildRepository.phpsrc/Discord/Builders/MessageBuilder.phpsrc/Discord/Parts/Interactions/Interaction.phpsrc/Discord/WebSockets/Events/MessageCreate.php, src/Discord/WebSockets/Events/GuildCreate.phpsrc/Discord/DiscordCommandClient.php, src/Discord/CommandClient/Command.phpsrc/Discord/Helpers/CacheWrapper.php, src/Discord/Helpers/CacheConfig.phpsrc/Discord/Voice/VoiceGroupCrypto.php, src/Discord/Voice/VoicePacket.phpPHPUnit\Framework\TestCase when logic is isolated.DiscordTestCase only when real Discord integration matters.wait() from tests/functions.php to bridge promises into test assertions.guide/.docs/.| 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 |
| docs build | cd docs && yarn install && yarn build |
Integration tests expect .env values for DISCORD_TOKEN, TEST_CHANNEL, and TEST_CHANNEL_NAME.
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.
How can I help you explore Laravel packages today?