league/commonmark
Highly extensible PHP Markdown parser supporting full CommonMark and GitHub-Flavored Markdown. Convert Markdown to HTML with simple converters, customize rendering via extensions, and run safely with options like stripping HTML and blocking unsafe links.
Installation:
composer require league/commonmark
Basic Usage:
use League\CommonMark\CommonMarkConverter;
$converter = new CommonMarkConverter();
$html = $converter->convert('# Hello World!');
First Use Case: Render markdown content from a database or user input into HTML for display in a Laravel blade template:
$markdown = "## Features
- **Fast**: Optimized for performance
- **Extensible**: Supports custom extensions";
$converter = new CommonMarkConverter();
$html = $converter->convert($markdown);
return view('post.show', ['content' => $html]);
// In a Laravel service or controller
public function renderMarkdown(string $markdown): string
{
$converter = new CommonMarkConverter([
'html_input' => 'strip', // Security best practice
]);
return $converter->convert($markdown);
}
use League\CommonMark\GithubFlavoredMarkdownConverter;
$gfmConverter = new GithubFlavoredMarkdownConverter([
'html_input' => 'strip',
'tables' => true, // Enable tables if needed
]);
Create a custom Blade directive in AppServiceProvider@boot():
Blade::directive('markdown', function ($expression) {
$markdown = $expression;
$converter = new CommonMarkConverter();
return "<?php echo \\League\\CommonMark\\CommonMarkConverter::convertToHtml({$markdown}); ?>";
});
Usage in Blade:
@markdown($post->content)
use Illuminate\Support\Facades\Cache;
public function getCachedMarkdown(string $key, string $markdown, int $ttl = 3600)
{
return Cache::remember("markdown.{$key}", $ttl, function () use ($markdown) {
return (new CommonMarkConverter())->convert($markdown);
});
}
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Environment\Environment;
public function getExtendedConverter(): CommonMarkConverter
{
$environment = new Environment();
$environment->addExtension(new CustomExtension());
return new CommonMarkConverter($environment);
}
Service Container Binding:
$this->app->bind(CommonMarkConverter::class, function ($app) {
return new CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
});
Form Request Validation:
public function rules()
{
return [
'content' => 'required|markdown', // Custom rule for markdown validation
];
}
API Responses:
return response()->json([
'content' => (new CommonMarkConverter())->convert($request->markdown),
]);
public function testMarkdownConversion()
{
$converter = new CommonMarkConverter();
$html = $converter->convert('# Test');
$this->assertStringContainsString('<h1>Test</h1>', $html);
}
XSS Vulnerabilities:
'html_input' => 'strip' and 'allow_unsafe_links' => false when parsing user input.// UNSAFE: Allows arbitrary HTML/JS injection
$converter = new CommonMarkConverter(['html_input' => 'allow']);
Encoding Issues:
$markdown = mb_convert_encoding($userInput, 'UTF-8');
Breaking Changes in Minor Versions:
Extension Conflicts:
AST Modifications:
XML Output for Debugging:
Use MarkdownToXmlConverter to inspect the AST:
use League\CommonMark\Xml\MarkdownToXmlConverter;
$xmlConverter = new MarkdownToXmlConverter($environment);
$xml = $xmlConverter->convert($markdown);
Logging Extensions: Create a debug extension to log parsing events:
use League\CommonMark\Event\DocumentParsed;
use League\CommonMark\Extension\ExtensionInterface;
class DebugExtension implements ExtensionInterface
{
public function register(Environment $environment)
{
$environment->addEventListener(DocumentParsed::class, function (DocumentParsed $event) {
\Log::debug('Parsed document:', ['ast' => $event->getDocument()->children()]);
});
}
}
CommonMark Spec Validation: Use the CommonMark Spec Tests to validate edge cases.
Environment vs. Converter Options:
html_input) are applied at runtime.GFM vs. CommonMark:
GithubFlavoredMarkdownConverter includes additional features (tables, task lists) but may have slightly different behavior for overlapping syntax.Lazy Loading Extensions:
Some extensions (e.g., TableExtension) are lazy-loaded and may not be available until first use.
Custom Inline Parsers: Override inline parsing logic (e.g., for Twitter handles or emoticons):
use League\CommonMark\InlineParser\ElementContentParserInterface;
class CustomInlineParser implements ElementContentParserInterface
{
public function parse(InlineParserContext $context)
{
// Custom parsing logic
}
}
Renderer Decorators: Modify HTML output without changing parsing logic:
use League\CommonMark\Renderer\NodeRendererInterface;
class CustomHeadingRenderer implements NodeRendererInterface
{
public function render(Node $node, NodeRendererContext $context)
{
// Custom rendering logic
}
}
Event Listeners:
Hook into parsing events (e.g., DocumentParsed, BlockParsed):
$environment->addEventListener(DocumentParsed::class, function (DocumentParsed $event) {
// Post-processing logic
});
Blade Caching: Ensure markdown content is not cached in Blade if it changes dynamically:
@verbatim
{!! (new League\CommonMark\CommonMarkConverter())->convert($post->content) !!}
@endverbatim
Queue Jobs: Avoid instantiating converters in queue jobs (reuse them via the service container).
File Storage: Cache parsed markdown in files for performance:
$path = storage_path("cache/markdown/{$key}.html");
if (!file_exists($path)) {
file_put_contents($path, $converter->convert($markdown));
}
return file_get_contents($path);
Combine with HTML Purifier: For additional security when allowing raw HTML:
use HTMLPurifier;
$purifier = new HTMLPurifier();
$safeHtml = $purifier->purify($converter->convert($markdown));
Use CommonMarkCoreExtension:
Explicitly include core extensions for clarity:
$environment->addExtension(new CommonMarkCoreExtension());
Benchmark Extensions: Profile extensions to identify performance bottlenecks:
$start = microtime(true);
$converter->convert($markdown
How can I help you explore Laravel packages today?