block8/php-docblock-checker
A CLI tool that scans PHP files and verifies classes and methods have proper DocBlocks, reporting missing or incomplete comments with file and line details. Ideal for enforcing documentation standards in CI pipelines and code reviews.
composer require --dev block8/php-docblock-checker
vendor/bin/phpdoccheck --directory=app
That's the whole loop: point it at a directory, get a list of classes and methods that have no docblock, and a non-zero exit code if anything is missing. Run vendor/bin/phpdoccheck --help first — the package has had several CLI flag reshuffles across its releases, and since it's archived you'll be reading whatever your pinned version actually ships rather than current docs.
Where to look first:
docker/ or vendor/bin/phpdoccheck for the executable; there's no config file, no .phpdoccheck.yml, nothing to discover. Everything is CLI flags.bin/phpdoccheck file itself if the README is stale) for the exact option names — --directory, --exclude, --skip-classes, --skip-methods, --json, --html, --output are the ones that matter in practice.composer.json scripts block, which is where this tool should end up living.The first useful use case is not "enforce 100%" — it's a baseline audit on a legacy Laravel app. Run it across app/, capture the output, and commit the result as a ticket list. A typical mid-sized Laravel codebase will surface missing docblocks on Jobs, Events, DTOs, and console commands, while controllers and Eloquent models are usually already annotated by convention.
# rough triage: how bad is it?
vendor/bin/phpdoccheck --directory=app --json > docblock-baseline.json
Composer script, not a global install. Keep the tool discoverable and version-pinned:
{
"scripts": {
"docs:check": "phpdoccheck --directory=app --directory=database",
"docs:check-json": "phpdoccheck --directory=app --json --output=build/docblocks.json"
}
}
Then composer docs:check works identically on every developer machine and in CI, with no PATH assumptions.
Scope it by directory, exclude aggressively. The tool walks everything under --directory, so anything that runs from the repo root needs exclusions:
vendor/bin/phpdoccheck \
--directory=app \
--exclude=vendor \
--exclude=storage \
--exclude=app/Console/Kernel.php
A cleaner pattern in Laravel is to never scan the root at all — list only the directories you care about (app, database, routes if you docblock route files, tests if you're aggressive). This avoids churn when someone adds a new generated directory.
CI gate with a soft-start. Don't turn this on as a blocking check the day you install it. Two-stage rollout:
# .github/workflows/docblocks.yml
- name: Docblock check (advisory)
run: composer docs:check
continue-on-error: true # stage 1: report only
Once the baseline is burned down, remove continue-on-error. Because the tool exits non-zero on violations, it slots into a pipeline with zero glue code — no parsing, no if grep wrapper.
Pair it with Pint, don't replace it. Laravel Pint handles formatting and will happily leave an undocumented method alone; phpdoccheck checks presence, Pint checks style. Run pint first, then the checker, so formatting fixes don't invalidate your docblock baseline counts.
Use --skip-methods / --skip-classes for phased adoption. If class-level docblocks are already universal but method-level ones aren't, gate on --skip-methods today to get a green build, then flip it later. It's the cheapest way to make the check meaningful before the codebase is clean.
JSON output for custom reporting. --json gives you machine-readable results you can feed into a small script that posts a PR comment or writes a markdown summary. Useful when the plain-text output is a 400-line wall and reviewers ignore it. --html --output=build/docblocks.html is the same idea for a browsable artifact — good for a one-off "here's our documentation debt" presentation.
Pre-commit, scoped to changed files. If you use Husky or a pre-commit hook, run the checker against the directories containing staged files rather than the whole app, otherwise a one-line fix gets blocked by unrelated debt:
git diff --cached --name-only --diff-filter=ACM \
| grep '^app/' | xargs -r -n1 dirname | sort -u \
| xargs -r -I{} vendor/bin/phpdoccheck --directory={}
It's archived and last released in May 2021. Treat this as a frozen dependency: pin an exact version ("block8/php-docblock-checker": "1.10.0" or whatever your install resolves to), not a caret range, and expect no upstream fixes. Budget a replacement path from day one — PHP_CodeSniffer's Squiz.Commenting.FunctionComment / Squiz.Commenting.ClassComment sniffs, or PHP-CS-Fixer's phpdoc_* rulesets, cover the same ground and are actively maintained.
PHP 8 syntax is the real risk. The tool parses with nikic/php-parser; the 2021-era parser handles enums and attributes reasonably, but newer syntax (readonly classes, #[\Override], typed class constants, property hooks) can cause parse errors or silent skips. If your Laravel app has drifted to PHP 8.3+, spot-check a file containing modern syntax before trusting a green result.
Dependency conflicts with other tooling. It pulls in a specific nikic/php-parser major. If a static analyser or Rector is also in require-dev, Composer may resolve to a version the checker can't use, or refuse to install at all. composer why nikic/php-parser is your first debugging step when install fails or the binary explodes at runtime.
Empty docblocks pass. The check is presence-only. /** */ above a method satisfies it. This is the single biggest reason the tool gives false confidence — anyone gaming the check can add empty blocks in seconds. Pair it with a code review rule or a PHP_CodeSniffer Generic.Commenting.DocComment sniff if you need minimum content.
No configuration file means no per-project defaults. Every invocation needs the full flag set, which is why the Composer script wrapper above is not optional. Teams that call vendor/bin/phpdoccheck directly from CI end up with flag drift between the workflow file and local muscle memory.
Exit codes are the integration surface. Missing docblocks produce a non-zero exit; a malformed directory argument or parse failure may produce a different non-zero code or a PHP fatal. If your CI step is wrapped in a "fail on any non-zero" gate, a typo'd --directory looks identical to real violations. Log stdout separately, or run the check with -v once when wiring it up.
It won't check what you assume. Properties, constants, file-level docblocks, and closure internals are generally outside scope. Don't sell it to your team as "enforces full documentation" — it enforces class and method docblock presence, which is narrower. Say that plainly up front and nobody is surprised at review time.
Watch for generated code. app/Providers from Laravel, IDE helper files, _ide_helper.php, and anything committed from barryvdh/laravel-ide-helper will get flagged. Exclude them explicitly rather than adding docblocks to files that get regenerated.
Extension point is effectively "none". It's a CLI with no plugin API. If you need custom rules (e.g., "@return must match the signature," or "Jobs must document @throws"), you're writing a separate script that consumes --json output — or switching tools. Factor that into the adoption decision rather than assuming you'll customize it later.
How can I help you explore Laravel packages today?