Accumulated patterns and anti-patterns from development sessions. Auto-managed by caliber — do not edit manually.
- [gotcha:project] The existing test files (
tests/EventTest.php,tests/IgnoredActionsThrowTest.php,tests/UnknownActionTest.php,tests/IgnoredEventTest.php) all referenceIrcConverterandDiscordConverterclasses that no longer exist insrc/— runningvendor/bin/phpunitproduces 61 errors about missing classes. These tests are stale and need to be rewritten againstGithubWebhookdirectly. - [fix:project]
vendor/bin/phpunitfails with "No such file or directory" ifvendor/is not present — always runcomposer installfirst. The vendor directory is gitignored and absent after a fresh clone. - [gotcha:project]
composer.jsonhas noautoload,autoload-dev, orscriptssections — there is no PSR-4 autoloader and nocomposer testshortcut. Classes (GithubWebhook,IgnoredEventException, etc.) are loaded via manualrequirestatements. Tests must bootstrap these manually or via a PHPUnit bootstrap file. - [pattern:project] Real GitHub webhook payloads live in
log/as JSON files with structure{"repo": "...", "event": "...", "data": {...payload...}}. Thedatakey holds the raw webhook payload — use these as authoritative source material when creating new fixtures undertests/events/. - [gotcha:project] In the
pushevent handler,$Msg['alias']is set from$Message['head_commit']['author']['name'](commit author name), not$Message['pusher']['name']. These differ when CI or bots push commits authored by a human. Code that compares commit authors against the pusher must use$Msg['alias'], not$Message['pusher']['name']. - [fix:project]
phpunit.xmlmust havebootstrap="tests/bootstrap.php"set — without it PHPUnit cannot load source classes since there is no PSR-4 autoloader. The bootstrap file manuallyrequires allsrc/classes. - [gotcha:project]
composer.jsonnow has ascriptssection —composer test,composer analyse,composer cs-fix, andcomposer cs-checkall work. The old stale learning about no scripts is now superseded. - [convention:project] Test fixture directories under
tests/events/{event_name}/now use 3 files:payload.json,type.txt, andexpected_text.txt(plain text, no IRC color codes). The old 4-file format withexpected.binanddiscord.jsonwas for the removedIrcConverter/DiscordConverter— do NOT create those files. - [pattern:project]
GithubMessageBuilder(insrc/GithubMessageBuilder.php) handles all message formatting. Theswitch ($EventType)block inweb/github.phpis now routing-only: call$Builder->build()before the switch, then each case just decides whether to callSendToChator skip it. Do NOT rebuild$Msginside individual cases. - [gotcha:project]
releaseedited events have achangeskey in the payload that is an empty array[]— GitHub does not populate what fields changed. There is nofrom/todata available for release edits; the message can only show the current release state, not what was modified. - [gotcha:project]
statusevents (CI status from AppVeyor, Scrutinizer, Travis, etc.) are NOT ignored in the current codebase — they fall through todefault:which emits a generic "triggered a status event" message. The payload has rich fields:state(success/failure/error/pending),description(CI message),context(CI service name),target_url(link to build), andcommit.commit.message. Add a dedicatedcase 'status':handler inGithubMessageBuilder::buildText()to surface these fields. - [pattern:project] Real GitHub webhook payloads live in
log/as JSON files with structure{"repo": "...", "event": "...", "data": {...payload...}}. Thedatakey holds the raw webhook payload — use these as authoritative source material when creating new fixtures undertests/events/. Why: payload.json files in tests/events use the unwrapped payload, not the wrapped log structure — extractdatawhen copying. How to apply: when adding fixtures or analyzing past events, jq/python-extract thedatakey. - [gotcha:project]
composer.jsonhas ascriptssection —composer test,composer analyse,composer cs-fix, andcomposer cs-checkall work. The old stale learning about no scripts is superseded. - [convention:project] Test fixture directories under
tests/events/{event_name}/use 3 files:payload.json,type.txt, andexpected_text.txt(plain text, no IRC color codes). The old 4-file format withexpected.binanddiscord.jsonwas for the removedIrcConverter/DiscordConverter— do NOT create those files. - [pattern:project]
GithubMessageBuilder(insrc/GithubMessageBuilder.php) handles most message formatting via itsbuildText()switch, but new event types with rich payload fields (e.g.case 'status':for CI state/context/target_url) have been added directly to theswitch ($EventType)block inweb/github.phpinstead of the builder. Why: quick custom handlers can live inweb/github.phpwithout modifying the shared builder. How to apply: check bothGithubMessageBuilder::buildText()AND the switch inweb/github.phpwhen looking for an event handler — adding to either is valid, but prefer the builder for consistency. - [gotcha:project]
statusevents (CI status from AppVeyor, Scrutinizer, Travis, etc.) have a dedicatedcase 'status':handler inweb/github.phpthat surfacesstate,description,context,target_url, andcommit.commit.message— they are NOT ignored despite the README/exception-handling docs claiming so. Both Webhook docstrings and theIgnoredEventExceptiondescription still mention status as ignored, but it actually flows through to a custom handler. - [fix:project]
vendor/bin/phpstan analysefails withConstant GITHUB_WEBHOOKS_SECRET not foundbecausesrc/config.phpis gitignored. Fix: createphpstan-bootstrap.phpthat stubs the constant (if (!defined('GITHUB_WEBHOOKS_SECRET')) { define('GITHUB_WEBHOOKS_SECRET', ''); }) and reference it fromphpstan.neonviabootstrapFiles: - phpstan-bootstrap.php. Without this, every PHPStan run fails on a fresh checkout. - [gotcha:project] The actual class in
src/GithubWebhook.phpis namedGithubWebhook(capital G, lowercase rest until W), butweb/github.phpandweb/github-old.phphistorically callednew GitHubWebHook()(mixed case). PHPStan's class-case check flags this asClass GithubWebhook referenced with incorrect case: GitHubWebHook. Always usenew GithubWebhook()to match the class declaration exactly — PHP itself is case-insensitive for classes but PHPStan is not. - [gotcha:project] The repo's PHPStan is pinned to
phpstan/phpstan: ^0.12.53(very old, last updated Sep 2021). Output spews PHPDeprecated: Return type of ... jsonSerialize() should ...warnings on every run — filter them withvendor/bin/phpstan analyse 2>&1 | grep -v "Deprecated\|deprecated\|ReturnTypeWillChange\|phpstan.phar"to see only real errors. - [gotcha:project]
SendToChat()inweb/github.phpuses an uninitialized$Codevariable when neither$useRCnor$useTeamsare true — PHPStan flagsVariable $Code might not be defined. Initialize$Code = 0;at the top ofSendToChat()before the conditional curl blocks.