staabm/phpstan-todo-by
PHPStan extension that makes TODO/FIXME comments actionable: enforce issue links or due dates, flag expired TODOs, and catch malformed annotations during static analysis so postponed work doesn’t get forgotten.
staabm/phpstan-todo-by is a PHPStan rule that turns your TODO/FIXME comments into enforceable debt: a TODO with an expiry date fails analysis once that date passes, and a TODO pointing at a GitHub issue fails once the issue is closed.
Minimal setup for a Laravel app:
composer require --dev staabm/phpstan-todo-by
If you already use phpstan/extension-installer (the Laravel/PHPStan default), the extension registers itself. Otherwise add it to phpstan.neon:
includes:
- vendor/staabm/phpstan-todo-by/extension.neon
Then write your first case — the most common use is time-boxed debt:
// TODO: 2025-12-31 remove this once the billing v2 API ships
$total = $this->legacyCalculator->sum($items);
Run vendor/bin/phpstan analyse and the comment is validated. Where to look first: the README's reference section for the supported comment syntax (plain TODO:/FIXME: plus @todo in docblocks), and the parameters: block in vendor/staabm/phpstan-todo-by/extension.neon for the knobs — that file is the authoritative list of options for your installed version. First realistic use case: gate app/ only, so a backlog of legacy TODO dates in tests/ or old modules doesn't block the build on day one.
Date-based debt in application code. Use absolute dates (TODO: 2025-12-31) rather than "in 30 days" style, since the check runs on every analysis and you want the deadline visible in review diffs. Combine with Laravel conventions: a TODO: <date> on a workaround in a service provider, or an @todo docblock on a deprecated helper.
Issue-linked TODOs. Point the comment at a GitHub issue URL and the rule reports it once the issue is closed — this is the pattern that keeps "temporary" hacks from outliving their ticket:
// TODO: https://github.com/acme/app/issues/1234 re-enable queue retries
Scope it per directory. Create a separate phpstan-todo.neon included only in the CI job that should fail on debt, or use paths: to restrict to app/, database/, and routes/. Many teams run the full Larastan analysis on app/ but the TODO check only on app/ while excluding app/Legacy.
CI wiring. Run it as its own step (vendor/bin/phpstan analyse -c phpstan-todo.neon --no-progress) so a red TODO check is distinguishable from a red type check. For the issue-fetch feature, pass a token — unauthenticated GitHub API calls are heavily rate-limited and will fail a build that analyzes many TODOs.
New in 0.3.5 — disabling GitHub issue URL fetching. The rule can be configured to skip fetching GitHub issues entirely, leaving date-based checks intact. This is the pattern to use when analysis runs offline, in a sandboxed/air-gapped CI runner, or when you don't want analysis to depend on GitHub availability or a token:
parameters:
# exact key name: check parameters in extension.neon for 0.3.5
todoBy:
githubIssueUrlFetch: false
Treat this as the default for local dev and pre-commit hooks, and enable fetching only in the scheduled/nightly CI job where a token and network are guaranteed. That keeps phpstan analyse fast and deterministic for the common case.
Pair with a review convention. The rule only helps if dates get set. A lightweight convention — every TODO added in a PR must have either a date ≤ 90 days out or an issue link — is what actually makes the enforcement useful.
GITHUB_TOKEN in the environment.phpstan.neon (%env.GITHUB_TOKEN%) rather than inlining, and keep the TODO config file out of any path that gets published.--error-format=json shows it) in an ignoreErrors entry rather than @phpstan-ignore sprinkled at call sites — the comment itself is the thing being flagged, and inline ignores on the surrounding statement are easy to lose in refactors..env, YAML, and JS assets are invisible to this rule. Don't assume a TODO in resources/views is being tracked.TODO: with no date/issue may be silently ignored rather than reported — if a comment isn't being flagged, verify the exact format against the README before assuming the rule is broken.How can I help you explore Laravel packages today?