clean/phpdoc-md
clean/phpdoc-md generates clean Markdown documentation from PHP source code and PHPDoc comments. It parses classes, methods, properties, and docblocks, then outputs readable Markdown suitable for READMEs, wikis, or static docs. Easy to run and integrate into build or CI workflows for up-to-date API...
clean/phpdoc-md is a standalone CLI that walks PHP source, parses PHPDoc blocks, and emits a single markdown document. There is no service provider, no config file, and no Artisan command — it is a dev-only binary you invoke from Composer scripts or CI.
Minimal setup:
composer require --dev clean/phpdoc-md
vendor/bin/phpdoc-md --help # confirm the exact signature for the version you pinned
Then generate against one narrow folder:
vendor/bin/phpdoc-md generate app/Actions -o docs/actions.md
(or redirect stdout with > docs/actions.md if your pinned version writes to stdout — check --help once and standardise on whichever it is).
Where to look first:
vendor/bin/phpdoc-md --help — the whole surface area is the CLI flags (source, output path, ignore list). There is nothing else to learn.app/Services, app/Actions, app/Support, a packages/*/src. That is the first useful use case: an onboarding/reference doc for code your team actually reads.app/ as a whole. Controllers and Eloquent models produce noisy output (see Gotchas), and a wall of bad markdown is a bad first impression of the tool.A sane first target is a package you own: packages/billing/src, where the public surface is small and the PHPDoc is written for humans already.
One Composer script per doc target. Keep flags out of muscle memory and in version control:
"scripts": {
"docs:app": "phpdoc-md generate app/Services -o docs/services.md",
"docs:actions": "phpdoc-md generate app/Actions -o docs/actions.md",
"docs": ["@docs:app", "@docs:actions"]
}
Run composer docs locally and in CI. This is the single highest-value pattern — everything else is variation on it.
Document the public surface, not the application. Point the tool at contracts and services (app/Contracts, app/Services, app/Support, src/ of packages) and leave controllers, jobs, and listeners out. Those are wiring, and nobody reads generated docs for wiring.
Per-module docs in a monorepo. For packages/*, loop the generation so each package gets its own file, then stitch them into your docs site nav:
for p in packages/*/src; do
vendor/bin/phpdoc-md generate "$p" -o "docs/$(basename $(dirname $p)).md"
done
CI drift check. Regenerate into a temp dir and diff against the committed copy; fail the build if they diverge. This is the only reliable way to stop generated docs from rotting, and it forces PHPDoc updates into the same PR as the code change:
- run: composer docs
- run: git diff --exit-code docs/
Feed a static docs site. The output is plain markdown, so it drops straight into VitePress, Docusaurus, or MkDocs as a "Reference" section. Keep generated files in a docs/reference/ subfolder and add a banner ("generated — do not edit") so nobody hand-patches them.
Use it as a PHPDoc audit. Before you care about the rendered output, generate it once and read it. Missing @param types, absent @throws, and @return mixed become immediately visible when the tool has nothing to print. This is often more valuable than the doc itself.
Wrap it in an Artisan command if your team lives in artisan. A ten-line command shelling out to the binary keeps php artisan docs in the muscle memory of developers who won't remember a Composer script. Optional, but cheap.
Macroable, __get/__call, Eloquent's dynamic attributes, container bindings. Add explicit @property / @method blocks on models and services, otherwise the generated docs describe a class that doesn't match how anyone uses it — and verify the tool actually renders those tags before you rely on them.@inheritdoc is generally left unresolved (or dropped). If you build a doc on top of inherited interfaces, expect missing descriptions. Write the description on the concrete class too, or accept gaps.composer.json — do not use ^.vendor/, tests/, storage/, bootstrap/cache/ will all be crawled if you point at the project root. Always pass an explicit source directory and an ignore list rather than generate ..phpDocumentor with a markdown template, or Doctum, if a future PHP upgrade outruns it. Extension points are essentially nonexistent — customisation means post-processing the markdown with sed/a small script, or forking. Budget accordingly and don't build a docs pipeline that only this tool can feed.How can I help you explore Laravel packages today?