Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
91b5a28
Remove Docker-related files and configurations for PHP versions 8.0 t…
alexandre433 Apr 20, 2026
bdb7a3b
Refactor async function usage in browser example and fix typo in ping…
alexandre433 Apr 20, 2026
fc03e32
Update GitHub Actions workflows: upgrade checkout and deployment acti…
alexandre433 Apr 20, 2026
fcb63b7
Renames file
alexandre433 Apr 20, 2026
f66c96b
Update contribution guidelines and configuration files: replace php-c…
alexandre433 Apr 20, 2026
e916203
Remove unnecessary blank lines in various component and part files
alexandre433 Apr 20, 2026
2a6193e
Refactor getColor, contains, studly, imageToBase64, getSnowflakeTimes…
alexandre433 Apr 20, 2026
8c9b29f
Refactor tests to use Pest syntax and improve readability
alexandre433 Apr 20, 2026
05d5320
Refactor mentioned function to use array_key_exists for role mentions…
alexandre433 Apr 20, 2026
060862a
Reorganize composer.json dependencies for improved clarity and add so…
alexandre433 Apr 20, 2026
cfed936
Enhance composer.json with additional metadata and configuration opti…
alexandre433 Apr 20, 2026
9131792
Add CA certificate handling for Windows users and update documentation
alexandre433 Apr 21, 2026
5f8408d
Enhance environment variable handling and update examples for improve…
alexandre433 Apr 21, 2026
ef82e35
Add public API policy and design heuristics to AGENTS.md for clarity …
alexandre433 Apr 21, 2026
7442cbe
Refactor environment variable handling to use Symfony Dotenv and remo…
alexandre433 Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 0 additions & 38 deletions .dockerignore

This file was deleted.

24 changes: 24 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -------------------------------------------------------------------
# Bot runtime variables
# -------------------------------------------------------------------

# Your Discord bot token. Required to connect.
# https://discord.com/developers/applications → your app → Bot → Token
DISCORD_TOKEN=

# Comma-separated Intents constant names (or an integer bitmask).
# Defaults to Intents::getDefaultIntents() when not set.
# Example: GUILDS,GUILD_MESSAGES,MESSAGE_CONTENT
# DISCORDPHP_INTENTS=

# Optional. Path to a CA certificate bundle (.pem) for TLS verification.
# Only needed on native Windows where PHP cannot read the system cert
# store. Leave blank on Linux/macOS/WSL.
# See scripts/windows-ssl-setup.ps1 to download cacert.pem automatically.
# DISCORDPHP_CAFILE=

# -------------------------------------------------------------------
# Integration test variables (not used at runtime)
# -------------------------------------------------------------------
TEST_CHANNEL=
TEST_CHANNEL_NAME=
4 changes: 2 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
if: github.event_name == 'release' || contains(github.event.head_commit.message, 'build docs')
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
Expand All @@ -28,7 +28,7 @@ jobs:
yarn build
sudo mv public/* ../build
- name: Publish docs
uses: JamesIves/github-pages-deploy-action@3.7.1
uses: JamesIves/github-pages-deploy-action@4.8.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
Expand Down
6 changes: 1 addition & 5 deletions .github/workflows/unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,10 @@ jobs:
with:
php-version: ${{ matrix.php }}
extensions: uv, zlib, mbstring
tools: phpunit, phplint
tools: phpunit
- name: Install dependencies
run: composer install
- name: Redis Server in GitHub Actions
uses: supercharge/redis-github-action@1.4.0
# - name: Memcached Service
# uses: niden/actions-memcached@v7
- name: Run PHPUnit
run: phpunit
- name: Run PHPLint
run: phplint
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ composer.lock

# phpunit
phpunit.log
coverage.xml
/.phpunit*
/coverage
/.tools
103 changes: 0 additions & 103 deletions .php-cs-fixer.dist.php

This file was deleted.

7 changes: 0 additions & 7 deletions .phplint.yml

This file was deleted.

73 changes: 69 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,67 @@ In this repo, companion surfaces usually matter as much as the line you edit.
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.

## Public API policy

DiscordPHP is a **library, not a framework.** User code calls DiscordPHP; DiscordPHP does not call user code beyond the event listeners the user explicitly registers. This posture is deliberate and is the gate against feature creep. If a proposed addition would invert that control flow, it does not belong here — it belongs in a sibling framework package layered on top.

### The dividing rule

A capability belongs **in DiscordPHP (this library)** if **all** of the following are true:

- It lives in exactly one existing layer (Part, Repository, Builder, Event, Voice, or runtime).
- It wraps a primitive nicer without choosing an app shape.
- It holds no state the caller would otherwise control.
- It does not own a lifecycle beyond `__construct` / `run()` / `close()`.
- It does not assume a config file format, DI container, or plugin model.
- Its default behavior is correct for the overwhelming majority of callers, and wrong defaults are still overridable.

A capability belongs **outside this library** if **any** of the following are true:

- It imposes app structure (routing, controllers, plugin registry, modules).
- It owns config resolution beyond `.env` + constructor options.
- It holds cross-event state on the user's behalf (pagination cursors, wizards, cooldowns, session tracking).
- It couples subsystems users should be free to swap (e.g. command routing + persistence + permission system together).
- It wants a DI container, service providers, or its own CLI.

Ambiguous cases default to **outside**. It is far harder to remove a public abstraction than to keep it out.

### Rules (R1–R10)

These are design heuristics, not commandments. They exist to serve the dividing rule above.

- **R1. One obvious entry point per task, progressive disclosure.** `Discord::fromEnv()` is the happy path; the full constructor is still there. Do not ship three ways to do the same thing.
- **R2. Listeners over lifecycle ceremony for the common path.** Thin aliases like `onReady()`, `onMessage()` keep first-bot code short. Reserve `on(Event::X, ...)` for cases where a constant is actually clearer.
- **R3. Builders are the single outbound authoring path.** If a payload has meaningful shape or validation, improve the builder. Do not add convenience methods that bypass it.
- **R4. No raw payloads in userland.** Handlers, events, and repository methods must return typed Parts or typed collections. Array-diving in userland is a library failure.
- **R5. Fail loudly, fail actionably.** Every thrown exception tells the user what to do next (env var, option key, portal toggle). See `Discord::fromEnv()` for the reference tone.
- **R6. No hidden singletons.** `Discord` is an instance. Never add global registry lookups — they break multi-bot processes, tests, and DI.
- **R7. Layered, not monolithic.** Core `Discord` works without `DiscordCommandClient`. Voice works without slash commands. Users opt in.
- **R8. Async visible, taught once.** Promises are part of the contract. Document the one pattern (`->then()` / `->done()`) in the quickstart and reuse it everywhere.
- **R9. Every public addition needs docblock + example + test.** If it is not all three, it is not part of the public surface.
- **R10. Conventions over config, with a real escape hatch.** Good defaults for intents, cache, logger — but every default is overridable via the same options array. No "rebuild to change X" moments.

### Explicit non-goals (belong in a framework package, not here)

Named so that future proposals get a clear, reviewable "out of scope" response. These are not forbidden ideas — they are simply not this library's job:

- **Slash command router.** Attribute- or class-based dispatch to handler methods. Imposes an app shape.
- **CommandRegistrar / diff-and-apply registration.** Holds desired-state across invocations and owns a lifecycle.
- **Interaction router.** Pattern-matching `custom_id` → handler; inherently a routing concern.
- **Menu / pagination / wizard state helpers.** Hold cross-event state on the user's behalf.
- **Permission / cooldown middleware.** Policy over primitives; middleware implies a pipeline the library does not own.

A starter document for such a framework project lives at `todo.txt` at the repo root as a temporary placeholder; it is intended to be moved into its own repository.

### Decision record hook for reviewers

If a proposed PR fails the dividing rule, the reviewer should either:

1. Ask the author to rewrite it as a single-layer primitive that does pass the rule, **or**
2. Close the PR with a note suggesting the idea belongs in an external framework package (link `todo.txt` while it still exists, or the future framework repo).

Do not merge "just this one" exceptions. The rule is only useful if it is consistently applied.

## Architecture map

| Layer | Owns | Primary files | What to preserve |
Expand Down Expand Up @@ -284,9 +345,10 @@ When you need an example worth imitating, start here:

### 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.
- Write all tests in **Pest 4** using `it()` or `test()` functions — no class-based test files.
- Unit tests (no Discord token needed) go in the `unit` testsuite. Integration tests go in the `integration` testsuite; list new files in `phpunit.xml`.
- Use `uses(DiscordTestCase::class)->in(...)` in `tests/Pest.php` only when a test file needs `$this->channel()` or other integration helpers.
- Use `wait()` from `tests/functions.php` to bridge ReactPHP promises into test assertions.
- Keep semantic tests focused on behavior, not on incidental implementation details.

### Docs
Expand All @@ -300,14 +362,17 @@ When you need an example worth imitating, start here:

| Purpose | Command |
| --- | --- |
| main PHPUnit suite | `composer unit` |
| unit test suite (no Discord token needed) | `composer unit` |
| integration test suite (requires `.env`) | `composer integration` |
| 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` |
| docs build | `cd docs && yarn install && yarn build` |

Integration tests expect `.env` values for `DISCORD_TOKEN`, `TEST_CHANNEL`, and `TEST_CHANNEL_NAME`.

Tests are written in **Pest 4** (`it()` / `test()` functions). Use `uses(SomeClass::class)->in(...)` in `tests/Pest.php` to bind a base class to specific test files.

## 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.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
We are open to contributions. However, please make sure you follow our coding standards (PSR-4 autoloading and custom styling). Please run php-cs-fixer before opening a pull request by running ``composer run-script cs.``
We are open to contributions. However, please make sure you follow our coding standards (PSR-4 autoloading and custom styling). Please run Pint before opening a pull request by running ``composer run-script pint.``

Please only use the issue tracker for submitting issues with the code. If you have questions about how to use DiscordPHP, hop over to our discord at [![DiscordPHP Community](https://discord.com/api/guilds/115233111977099271/widget.png?style=banner1)](https://discord.gg/dphp)
Loading