with one click
part-model-maintainer
// Maintain Part domain models — fillable attributes, mutators, typed nested data, save/fetch behavior, permission checks, PHPDoc, and repository bindings. Use when adding or modifying any Discord Part class.
// Maintain Part domain models — fillable attributes, mutators, typed nested data, save/fetch behavior, permission checks, PHPDoc, and repository bindings. Use when adding or modifying any Discord Part class.
Maintain test and documentation alignment — PHPUnit tests, async testing patterns, PHPDoc contracts, guide pages, and documentation workflow. Use when adding tests, updating docs, or changing public behavior.
Maintain Builder classes — outbound payload construction, validation, serialization, component handling, and fromPart symmetry. Use when changing Builders or outbound Discord API payloads.
Maintain gateway event handlers — payload hydration, cache mutation, event return shapes, and handler registration. Use when touching WebSockets/Events, Handlers.php, or Event.php.
Work with DiscordPHP's infrastructure utilities — CacheWrapper, CacheConfig, BigInt, Multipart, Endpoint::bind URL templates, Collection base class, and domain Exceptions. Use when changing cache behavior, REST endpoint routing, file uploads, big-integer ID math, or adding/modifying exceptions.
Maintain interaction flow — Interaction typing, resolved data caching, command routing, autocomplete, modal responses, and interaction builders. Use when touching Interactions or slash command handling.
Maintain the optional prefix-command layer — DiscordCommandClient, Command class, parsing, aliases, cooldowns, and help system. Use when touching message-based command handling.
| name | part-model-maintainer |
| description | Maintain Part domain models — fillable attributes, mutators, typed nested data, save/fetch behavior, permission checks, PHPDoc, and repository bindings. Use when adding or modifying any Discord Part class. |
Use this skill when work touches src/Discord/Parts/**/*.
This is not syntax guard. This is domain-model guard. Load it when changing what a Discord resource is, how it is hydrated, how it exposes related objects, or how it saves itself.
Keep Part classes as the canonical in-memory representation of Discord resources:
src/Discord/Parts/Part.phpsrc/Discord/Parts/PartTrait.phpsrc/Discord/Parts/Guild/Guild.phpsrc/Discord/Parts/Channel/Channel.phpsrc/Discord/Parts/Channel/Message.phpsrc/Discord/Parts/User/User.phpsrc/Discord/Parts/User/Member.phpsrc/Discord/Parts/Thread/Thread.phpsrc/Discord/Parts/Interactions/Interaction.phpDo not start from a random leaf class alone. PartTrait defines most real behavior.
Every real part sits on same base contract:
Discord\Parts\PartPartTrait(Discord $discord, array $attributes = [], bool $created = false)$attributes$fillable$repositoriesget{Studly}Attribute()set{Studly}Attribute()getRepository() + save()If change breaks one of those assumptions, stop and re-check layer ownership.
$fillableWhitelist for fill() and dynamic writes. If field is missing here:
$part->field = ... writes do not persist to $attributesWhen adding new API field, first ask: should this be part of canonical stored state? If yes, add it to $fillable.
$attributesRaw storage. Usually snake_case and Discord-shaped. Avoid putting derived state here unless the API itself uses that field.
$repositoriesMap of magic property name to repository class. This is how parts expose child collections like:
$guild->channels$channel->messages$message->reactionsIf child data should behave like a repository, wire it here. Do not fake repo behavior with arrays.
$createdTracks whether part already exists on Discord side.
false means local draft or partial unsaved objecttrue means known remote objectRepository save() uses this to choose POST vs PATCH. Nested createOf() links child created state back to parent. Preserve that when materializing nested parts.
fill(array $attributes)Hydrates only fillable keys. It is not "dump every payload field in object". If new data is not appearing after fetch/event handling, check $fillable first.
getAttribute() / __get()Resolution order matters:
$repositoriesnullThis is why magic properties can expose both repositories and computed values. Preserve this behavior when adding convenience fields.
setAttribute() / __set()Setter mutator first, then raw write only if key is fillable. This protects parts from accidental shape drift. Do not bypass it by writing stray protected properties for true resource state.
getCreatableAttributes() / getUpdatableAttributes()These define what the repository sends to Discord. They are not debug dumps. They should express API intent:
Prefer makeOptionalAttributes() to avoid serializing absent optional fields.
getRepository()Returns the owning repository for this part instance. This is frequently context-sensitive:
Guild returns top-level $discord->guildsChannel chooses guild channels vs private channelsMessage chooses channel messages vs webhook messagesMember depends on guild_idThread depends on parent channelWhen repository ownership depends on parent IDs, also inspect getRepositoryAttributes().
save(?string $reason = null)Part-level save() is where high-level semantic guards live:
Rule: if logic needs knowledge of what operation means, it likely belongs here. If logic only knows how to execute REST+cache, it belongs in repository.
fetch()Only override when part can be refreshed independently from Discord and that contract is meaningful. If part is not fetchable, default runtime exception is fine.
attributeCarbonHelper($key)Use for timestamp-ish fields. Keeps repeated Carbon::parse logic out of concrete parts.
attributePartHelper($key, $class, $extraData = [])Use when field is a nested Discord object and you want lazy typed hydration with caching in $attributes.
Good for:
UserGuildMessageattributeCollectionHelper($key, $class, ?string $discrim = 'id', ?array $extraData = [])Use for homogeneous nested collections of one concrete part type.
attributeTypedCollectionHelper($class, $key)Use when payload element type decides subclass at runtime. This is important for:
TYPES mapscreateOf(string $class, array|object $data)Prefer this over raw factory calls for nested child parts because it preserves created linkage.
Common pair:
guild_id, channel_id, owner_idguild, channel, ownerKeep both if the API provides IDs and the library benefits from relation convenience.
Large resource parts publish Discord enum values and flags as class constants. Keep aliases when the project already supports deprecated constant names for compatibility.
If behavior applies horizontally across siblings, prefer trait:
ChannelTraitGuildTraitDo not introduce a new intermediate abstract class unless there is truly no cleaner trait shape.
Update class docblock whenever you add:
If code and docblock drift, IDE help and generated reference docs drift too.
When making a part persistable or changing save behavior:
getCreatableAttributes()getUpdatableAttributes()getRepository()save()getRepositoryAttributes()Channel::save() handles guild permission checks and group-DM special case before repository delegationMessage::save() handles send/manage permissions and webhook message routingMember::save() handles current-member special route instead of generic repository saveThread::save() blocks unsupported creation path and redirects callers to Channel::startThread()When new Discord payload includes nested object:
Part type for it$fillableextraDataDo not leave meaningful nested objects as raw arrays if equivalent typed part exists.
When child repository routes depend on parent context:
$repositories$attributesgetRepositoryAttributes() when raw $attributes is not enough or not in right shape$vars assumptionsExamples:
guild_id and channel_idchannel_id, maybe guild_id, maybe webhook contextguild_idStop if you see:
$fillablesave() building raw endpoints even though repository exists$attributesgetRepositoryAttributes() supportTYPES map or typed collection helper path$fillable matches intended canonical fields$repositories updated if new child collection exposedgetCreatableAttributes() / getUpdatableAttributes() express API semanticsgetRepository() and getRepositoryAttributes() route correctlysave() enforces semantic permission or special-case behavior when neededPart classes in this repo are not dumb DTOs and not service objects. They are typed, lazily-resolved domain resources with controlled hydration and repository-backed persistence. Keep them centered on that job.