Skill: helpers-and-infra-keeper
Use this skill when work touches src/Discord/Helpers/*, src/Discord/Exceptions/*, Endpoint::bind() patterns, or the external Collection / discord-php/http packages.
These are cross-cutting infrastructure utilities. They have no domain logic, but nearly every other layer depends on them.
Read in this order
src/Discord/Helpers/CacheWrapper.php— PSR-16 + React cache bridgesrc/Discord/Helpers/CacheConfig.php— cache options (compression, sweep, TTL)src/Discord/Helpers/LegacyCacheWrapper.php— fallback for older cache implementationssrc/Discord/Helpers/BigInt.php— 32-bit PHP GMP polyfill for snowflake IDs and permission bitfieldssrc/Discord/Helpers/Multipart.php— multipart/form-data builder for file uploadssrc/Discord/Repository/AbstractRepository.php— how repositories consumeCacheWrapperand$endpointssrc/Discord/Discord.php—options['cache']wiring toCacheWrapper/CacheConfig
Cache infrastructure
Three-class hierarchy
| Class | Role |
|---|---|
CacheConfig |
Value object holding cache options passed in Discord::__construct() options['cache'] |
CacheWrapper |
Main wrapper: bridges React's async cache (PSR-16 + react/cache) to repository storage. Handles compression if configured. |
LegacyCacheWrapper |
Default when no external cache is configured. Wraps an in-memory store. Used by default. |
Rule: repositories do not interact with cache stores directly. They always go through CacheWrapper. Do not bypass it.
When to use CacheConfig
If a feature needs configurable cache behavior (TTL, compression, sweep interval), add it as a property on CacheConfig. Do not scatter cache tuning across individual repositories.
Cache coherence rule
When a REST write succeeds, the cache write must happen in the same promise chain — not after a refetch. This is enforced by convention, not framework. Break the chain and you get stale cache.
Endpoint::bind() — URL template pattern
All REST endpoints are defined as Endpoint constants in the external discord-php/http package:
use Discord\Http\Endpoint;
protected $endpoints = [
'get' => Endpoint::CHANNEL,
'update' => Endpoint::CHANNEL,
'delete' => Endpoint::CHANNEL,
];
Routes with parameters use :param placeholders. Binding happens via Endpoint::bind($endpoint, $params) where $params is a key-value map matching the placeholders.
Rule: never build URL strings by hand. Always use Endpoint::bind() or assign a named Endpoint constant. Raw string concatenation bypasses rate-limit bucket identification.
Finding endpoint constants
Browse vendor/discord-php/http/src/Endpoint.php for available constants. If a Discord API endpoint is missing, add it to the external package — do not define it inline in DiscordPHP source.
Collection base class
All repositories extend Collection from discord-php-helpers/collection. Key behaviors inherited:
- discriminator-keyed storage (
$discrim— defaults to'id') - typed item enforcement (
$class) get($key),has($key),first(),find(callable),filter(callable)- JSON serialization
AbstractRepository adds REST/cache behavior on top. Do not re-implement collection operations in repositories — use the inherited helpers.
BigInt
BigInt provides GMP-based arithmetic for environments where PHP is compiled as 32-bit, where Discord snowflake IDs and 64-bit permission bitfields overflow native integers.
Usage pattern:
use Discord\Helpers\BigInt;
$perms = BigInt::bitAnd($memberPerms, $requiredPerm);
Rule: use BigInt for any permission flag arithmetic or snowflake comparison that could run on 32-bit PHP. Do not assume PHP_INT_SIZE === 8.
Multipart
Multipart builds multipart/form-data request bodies for file attachment uploads. It is used by MessageBuilder and similar builders when attachments are present.
It has no cache or domain logic — treat it as a serialization helper.
Exception hierarchy
src/Discord/Exceptions/ contains domain-specific exceptions. Do not use generic \RuntimeException or \InvalidArgumentException when a typed exception already exists:
| Exception | When to throw |
|---|---|
IntentException |
Invalid or missing gateway intents at startup |
PartRequestFailedException |
A part fetch or REST operation failed |
InvalidOverwriteException |
A permission overwrite is malformed |
AttachmentSizeException |
Attachment exceeds size limit |
FileNotFoundException |
File path not found for upload |
BufferTimedOutException |
Audio buffer read timed out |
LibSodiumNotFoundException |
ext-sodium not available (voice) |
OpusNotFoundException |
Opus codec binary not available |
FFmpegNotFoundException |
FFmpeg binary not found |
DCANotFoundException |
DCA tool not found |
When adding a new typed exception: extend \Exception or the nearest domain parent, keep the name *Exception, and put it in src/Discord/Exceptions/.
Companion surfaces
| Touching | Also inspect |
|---|---|
CacheWrapper or CacheConfig |
Discord.php options['cache'] wiring, AbstractRepository cache usage, LegacyCacheWrapper fallback |
Endpoint constants |
$endpoints arrays in all affected repositories, rate-limit bucket behavior |
BigInt |
Permission parts (Permission, RolePermission, ChannelPermission), snowflake comparison sites |
Multipart |
MessageBuilder attachment handling, HTTP client request formation |
| An exception class | Callers that catch the specific type, docs/guide if it's user-facing |
Design tripwires
- Building REST URL strings by hand instead of using
Endpoint::bind() - Writing to a cache store directly inside a repository, bypassing
CacheWrapper - Re-implementing collection iteration helpers that
Collectionalready provides - Using
PHP_INT_SIZE === 8as a safe assumption for permission arithmetic — useBigInt - Throwing
\RuntimeExceptionwhen a typed domain exception already exists - Adding domain logic (Discord resource rules) to any class in
Helpers/orExceptions/ - Putting new audio stream or voice crypto helpers in
Helpers/— those belong inVoice/*or the external voice package
Reference files
src/Discord/Helpers/CacheWrapper.php— main cache bridgesrc/Discord/Helpers/CacheConfig.php— cache options value objectsrc/Discord/Helpers/LegacyCacheWrapper.php— default in-memory cachesrc/Discord/Helpers/BigInt.php— 32-bit PHP GMP polyfillsrc/Discord/Helpers/Multipart.php— file upload form buildersrc/Discord/Repository/AbstractRepository.php— how CacheWrapper and Endpoint are consumedsrc/Discord/Exceptions/IntentException.php— representative typed exceptionsrc/Discord/Exceptions/PartRequestFailedException.php— REST failure exceptionvendor/discord-php/http/src/Endpoint.php— all REST endpoint constants