ostico/phpdoc-md
Generate Markdown documentation from PHPDoc comments in your PHP project. phpdoc-md scans classes, methods, and annotations to build readable docs you can publish or include in repos. Useful for keeping API docs in sync with code.
Installation
composer require ostico/phpdoc-md
Add to composer.json if not auto-discovered:
"autoload": {
"psr-4": {
"App\\": "app/",
"Ostico\\PhpdocMd\\": "vendor/ostico/phpdoc-md/src/"
}
}
Run composer dump-autoload.
First Use Case Generate Markdown from a single class file:
use Ostico\PhpdocMd\PhpdocMd;
$phpdocMd = new PhpdocMd();
$markdown = $phpdocMd->generateFromFile(__DIR__ . '/path/to/YourClass.php');
file_put_contents('output.md', $markdown);
Where to Look First
src/PhpdocMd.php: Core class with generateFromFile(), generateFromString(), and generateFromDocBlock() methods.tests/: Example usage and edge cases (e.g., handling @property, @method).README.md (if exists): Basic CLI usage (if provided).Documentation Generation Pipeline
Integrate into Laravel’s post-update-cmd or a custom Artisan command:
// app/Console/Commands/GenerateDocs.php
use Ostico\PhpdocMd\PhpdocMd;
class GenerateDocs extends Command {
protected $signature = 'docs:generate';
protected $description = 'Generate Markdown docs for all classes';
public function handle() {
$phpdocMd = new PhpdocMd();
$classes = $this->getClassesToDocument(); // Implement logic to scan app/
foreach ($classes as $class) {
$markdown = $phpdocMd->generateFromFile($class);
$this->saveMarkdown($markdown, $class);
}
}
}
Dynamic API Documentation Use middleware to inject Markdown docs into API responses:
// app/Http/Middleware/DocumentationMiddleware.php
public function handle($request, Closure $next) {
if ($request->expectsJson() && $request->has('doc')) {
$controller = $request->route()->getController();
$phpdocMd = new PhpdocMd();
$markdown = $phpdocMd->generateFromFile($controller);
return response()->json(['doc' => $markdown]);
}
return $next($request);
}
Laravel IDE Helper Integration
Generate Markdown for IDE helpers (e.g., app/Helpers.php) and embed in phpstorm.meta.php:
$markdown = $phpdocMd->generateFromFile(app_path('Helpers.php'));
// Parse Markdown into PHP array for IDE hints
PhpdocMd to modify output structure (e.g., add Laravel-specific tags like @route):
class LaravelPhpdocMd extends PhpdocMd {
protected function parseTag(string $tag): string {
if ($tag === '@route') return $this->formatRouteTag($tag);
return parent::parseTag($tag);
}
}
$cacheKey = 'docs_' . md5($filePath);
$markdown = Cache::remember($cacheKey, now()->addHours(1), function() use ($phpdocMd, $filePath) {
return $phpdocMd->generateFromFile($filePath);
});
pre-commit for critical files:
# .git/hooks/pre-commit
php artisan docs:generate --files=app/Http/Controllers/
DocBlock Parsing Quirks
@property-read mixed $foo inside @method).
Fix: Validate docblocks with phpDocumentor or roave/security-advisories before processing.@inheritDoc or @mixin may not render as expected.
Workaround: Pre-process docblocks to expand @inheritDoc or map custom tags.Performance
parallel PHP extensions or queue jobs:
foreach ($classes as $class) {
GenerateDocJob::dispatch($class)->onQueue('docs');
}
memory_limit if processing monolithic files (e.g., vendor/autoload.php).Edge Cases
generateFromString() may fail on anonymous classes without proper docblocks.
Tip: Skip or mock docblocks for anonymous classes.[Attribute] syntax. Use @attribute tags manually or pre-process with nikic/php-parser.$phpdocMd = new PhpdocMd(['verbose' => true]);
ReflectionClass to inspect docblocks before processing:
$reflection = new ReflectionClass($class);
var_dump($reflection->getDocComment());
/**
* @property string $name
* @method void setName(string $name)
*/
class Test {}
Custom Tag Handlers
Override parseTag() to handle Laravel-specific annotations:
protected function parseTag(string $tag): string {
if (str_starts_with($tag, '@route')) {
return $this->formatLaravelRouteTag($tag);
}
return parent::parseTag($tag);
}
Output Formatting
Extend render() to add Laravel-specific sections (e.g., @middleware, @route):
protected function render(array $docBlock): string {
$output = parent::render($docBlock);
if (isset($docBlock['@route'])) {
$output .= "\n\n## Routes\n```\n" . $docBlock['@route'] . "\n```";
}
return $output;
}
CLI Integration Add a custom Artisan command for ad-hoc generation:
// app/Console/Commands/GenerateDocCommand.php
protected $signature = 'docs:generate {--file= : Path to file} {--output= : Output path}';
protected function handle() {
$phpdocMd = new PhpdocMd();
$markdown = $phpdocMd->generateFromFile($this->option('file'));
file_put_contents($this->option('output'), $markdown);
}
$phpdocMd = new PhpdocMd([
'strip_comments' => true, // Remove non-docblock comments
'sort_tags' => true, // Alphabetize tags
'template' => 'custom.tpl' // Custom template path
]);
tests/ for examples).How can I help you explore Laravel packages today?