agentleFS
discord-phpdiscord-php/DiscordPHP/.github/skills/discord-php-extension/SKILL.md

discord-php-extension

Skill1.1k starsChanged 35 days agoMITView on GitHub

Scaffold and maintain a DiscordPHP-based bot or an API-library-on-DiscordPHP (the DiscordPHP-MTG / DiscordPHP-NHA / DiscordPHP-Sabacc pattern) — client subclass, third-party HTTP layer, Parts, repositories, JSON state store, the init/application-init boot guard, composer path-repo wiring, and the CRLF/diff gotcha. Use when creating a new extension repo or working across the existing ones.

Trending

More talk lately.about the whole repository

Dec 2025: 0 mentionsJan 2026: 0 mentionsFeb 2026: 2 mentionsMar 2026: 0 mentionsApr 2026: 1 mentionMay 2026: 0 mentionsJun 2026: 0 mentionsJul 2026: 0 mentionsAug 2026: 4 mentionsSep 2026: 13 mentionsOct 2026: 9 mentionsNov 2026: 1 mention
Dec 2025Jun 2026Nov 2026
Mentions a month of the discord-php/DiscordPHP repository that our weekly crawl found. There is no adoption data for this file yet.

Who's talking about discord-php/DiscordPHP

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

45 more on GitHub

The file

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 1 is distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Keep game/business logic in a namespace that imports nothing from Discord\ so it is unit-testable in isolation (see Sabacc\Pazaak).

    line 35
  2. Very specific:

    Default the Http logger arg to new NullLogger(), not null (the trait type-hints LoggerInterface).

    line 58
  3. Distinctive:

    Do not persist live match/session state across restarts unless you need to — keep it in memory keyed by message id.

    line 93
  4. Very specific:

    Run COMPOSER_ROOT_VERSION=dev-main composer install (Composer cannot detect the root version through the path repos otherwise).

    line 111
  5. Very specific:

    Every file starts with declare(strict_types=1).

    line 119
  6. Very specific:

    When a class extends a DiscordPHP part (or any class), narrow each override's return type to the most specific type it can truly return.

    line 136

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill 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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

DiscordPHP Extension / Bot Skill

Distilled from the discord-php/DiscordPHP-{MTG,NHA,Sabacc} extensions, which all follow the same shape: a MessageCommandClient subclass that also speaks to some third-party REST API and/or persists per-user state.

Repository layout

src/<Ns>/
  <Ns>.php              MessageCommandClient subclass; wires the HTTP client,
                        the Client part, and any services (Store, Shop, engine)
  Client.php            (only if you add repositories) extends
                        Discord\Parts\User\Client, adds entries to $repositories
  Http/
    Http.php            implements HttpInterface, `use HttpTrait`; points BASE_URL
                        at the third-party API
    Endpoint.php        implements EndpointInterface, `use EndpointTrait`; the
                        route table as `const`s with `:param` placeholders
    Request.php         extends Discord\Http\Request; overrides getUrl() to
                        prepend Http::BASE_URL
  Repository/           AbstractRepository + one per API resource (optional)
  Parts/               one Part subclass per API object; $fillable + get*Attribute()
  <Ns>Trait.php         pure presentation/helpers (no API, no I/O)
bot.php                boot + slash-command registration + component routing
tests/                 pure-logic tests; no live gateway
composer.json, .php-cs-fixer.dist.php, pint.json, phpunit.xml, env.example

Keep game/business logic in a namespace that imports nothing from Discord\ so it is unit-testable in isolation (see Sabacc\Pazaak).

The client subclass

class Foo extends \Discord\MessageCommandClient
{
    protected Http $foo_http;

    public function __construct(array $options = [])
    {
        parent::__construct($options);
        $this->foo_http = new Http('', $this->loop, $options['logger'] ?? new NullLogger(), new React($this->loop, $options['socket_options'] ?? []));
        // If you added repositories:
        $this->client = $this->factory->part(Client::class, (array) $this->client);
    }
}
  • Never pass the Discord bot token to a third-party API client. MTG passed 'Bot '.$this->token into its Http and leaked the token on every request — pass '' and only add an Authorization header when a value is actually set.
  • Default the Http logger arg to new NullLogger(), not null (the trait type-hints LoggerInterface).
  • $options['socket_options'] → new React($this->loop, ...) lets callers set dns etc.

Boot: the dual init guard

Discord fires both init and application-init; the command tree needs the application to be ready. Guard so your setup runs exactly once, after both:

$ready = $appReady = false;
$boot = function () use (&$ready, &$appReady, $bot, ...) {
    if (! $ready || ! $appReady) return;
    $bot->application->commands->freshen()->then($registerCommands);
    $bot->listenCommand('foo', fn (Interaction $i) => $commands->dispatch($i));
    // component routing, presence, ...
};
$bot->once('init', function () use (&$ready, $boot) { $ready = true; $boot(); });
$bot->once('application-init', function () use (&$appReady, $boot) { $appReady = true; $boot(); });

Common client options: 'intents' => Intents::getDefaultIntents(), 'disableVoiceClient' => true (unless you need voice — it still opens the gateway regardless), 'storeMessages' => true only if you read from the message cache.

Persistence: a tiny JSON state store

For per-user wallets / identities / settings, a flat JSON file is enough (see NHA\StateStore, Sabacc\Economy\Store):

  • Load lazily on first access; keep everything in memory.
  • Atomic writes: file_put_contents($path.'.'.getmypid().'.tmp', json_encode(...)) then @rename($tmp, $path). Sweep stale *.tmp on load.
  • gitignore the state file: /var/* + !/var/.gitkeep.
  • Do not persist live match/session state across restarts unless you need to — keep it in memory keyed by message id.

composer.json

"require": {
    "php": "^8.3",
    "team-reflex/discord-php": "dev-master",
    "discord-php/http": "dev-master as 10.1.7"   // needed: team-reflex/discord-php
},                                               // dev-master requires http ^10.1.7,
"minimum-stability": "dev", "prefer-stable": true,
"repositories": [                                 // local dev against sibling checkouts
    { "type": "path", "url": "../DiscordPHP",      "options": { "symlink": true } },
    { "type": "path", "url": "../DiscordPHP-Http", "options": { "symlink": true } }
]

Run COMPOSER_ROOT_VERSION=dev-main composer install (Composer cannot detect the root version through the path repos otherwise). composer.lock is gitignored in these repos.

Style

.php-cs-fixer.dist.php uses ['@auto' => true, 'phpdoc_align' => [...vertical...], 'phpdoc_indent' => true]. pint.json is {"preset": "psr12", "rules": {"declare_strict_types": true}}. Every file starts with declare(strict_types=1); and the project header comment.

The CRLF / diff gotcha

These repos have core.autocrlf=true and no .gitattributes, so php-cs-fixer writes CRLF on disk and Git normalises to LF on git add. A raw git diff after running composer cs shows every line changed. Always inspect the real change set with git diff --cached (after git add) or git diff --cached --stat, not git diff.

Part conventions (additive PHPDoc)

In the core DiscordPHP repo, PHPDoc changes are additive only — add @property / @param tags, never reword existing prose (it mirrors Discord's official docs). Extension repos are freer, but still lead with a one-paragraph class docblock saying what the class is and what it is NOT responsible for.

When a class extends a DiscordPHP part (or any class), narrow each override's return type to the most specific type it can truly return. PHP return types are covariant, so Part can become Channel when a Thread is impossible. Make every path honour it: no return parent::…() with the broader type, and no hydrating through a TYPES map that holds types outside it. Inherited methods that can never work in the subclass's context should return reject(new \BadMethodCallException(…)) rather than fail at Discord. The core repo's .agents/skills/part-model-maintainer has the full pattern.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.