league/commonmark
Extensible PHP Markdown parser supporting the full CommonMark spec and GitHub-Flavored Markdown. Works with PHP 7.4+ (mbstring) and provides simple converters to turn Markdown into HTML with configurable safety options.
Installation:
composer require league/commonmark
Ensure mbstring PHP extension is enabled.
Basic Conversion:
use League\CommonMark\CommonMarkConverter;
$converter = new CommonMarkConverter();
$html = $converter->convert('# Hello World!');
// Outputs: <h1>Hello World!</h1>
GitHub-Flavored Markdown (GFM):
use League\CommonMark\GithubFlavoredMarkdownConverter;
$gfmConverter = new GithubFlavoredMarkdownConverter();
$html = $gfmConverter->convert('| Tables | Are | Cool |');
$converter = new CommonMarkConverter([
'html_input' => 'strip', // Sanitize HTML input
'allow_unsafe_links' => false, // Block unsafe links
]);
$markdown = "**User's** *content* with [links](https://example.com)";
echo $converter->convert($markdown);
Key Docs:
// Initialize with default config
$converter = new CommonMarkConverter();
// Convert Markdown to HTML
$html = $converter->convert($markdown);
// Convert HTML back to Markdown (if needed)
$converter->getEnvironment()->addExtension(new \League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension());
$markdown = $converter->convert($html);
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\GithubFlavored\GithubFlavoredMarkdownExtension;
$environment = new \League\CommonMark\Environment\Environment();
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new GithubFlavoredMarkdownExtension());
$converter = new CommonMarkConverter(['environment' => $environment]);
// config/services.php
'commonmark' => [
'html_input' => 'strip',
'allow_unsafe_links' => false,
'extensions' => [
\League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension::class,
\League\CommonMark\Extension\GithubFlavored\GithubFlavoredMarkdownExtension::class,
],
],
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton(\League\CommonMark\CommonMarkConverter::class, function ($app) {
$config = $app['config']['services.commonmark'];
$environment = new \League\CommonMark\Environment\Environment();
foreach ($config['extensions'] as $extension) {
$environment->addExtension(new $extension());
}
return new \League\CommonMark\CommonMarkConverter($config + ['environment' => $environment]);
});
}
// app/Providers/BladeServiceProvider.php
Blade::directive('markdown', function ($expression) {
return "<?php echo app('\\League\\CommonMark\\CommonMarkConverter')->convert(trim($expression)); ?>";
});
// Usage in Blade:
@markdown($post->content)
use League\CommonMark\Event\DocumentParsed;
use League\CommonMark\Extension\ExtensionInterface;
class CustomExtension implements ExtensionInterface
{
public function register(EnvironmentInterface $environment)
{
$environment->addEventListener(DocumentParsed::class, function (DocumentParsed $event) {
// Modify AST after parsing
$event->getDocument()->firstChild()->setAttribute('data-custom', 'processed');
});
}
}
$cacheKey = 'markdown_'.$hash($markdown);
$html = cache()->remember($cacheKey, now()->addHours(1), function () use ($converter, $markdown) {
return $converter->convert($markdown);
});
// Stream processing for large files
$converter = new CommonMarkConverter();
$handle = fopen('large_file.md', 'r');
while (!feof($handle)) {
$chunk = fread($handle, 8192);
echo $converter->convert($chunk);
}
fclose($handle);
use League\CommonMark\CommonMarkConverter;
use PHPUnit\Framework\TestCase;
class MarkdownTest extends TestCase
{
public function testConversion()
{
$converter = new CommonMarkConverter();
$html = $converter->convert('# Heading');
$this->assertEquals('<h1>Heading</h1>', $html);
}
}
allow_unsafe_links => false for user-generated content.
$converter = new CommonMarkConverter(['allow_unsafe_links' => false]);
html_input => 'strip' to prevent XSS.
$converter = new CommonMarkConverter(['html_input' => 'strip']);
GithubFlavoredMarkdownConverter for GFM features like tables).AutolinkExtension) can slow down parsing. Benchmark before production use.$markdown = mb_convert_encoding($input, 'UTF-8', 'auto');
@internal methods (e.g., CommonMarkConverter::getEnvironment() directly). Use the public API.$converter = new CommonMarkConverter();
$document = $converter->getEnvironment()->createParser()->parse($markdown);
$ast = $document->dump(); // Debug the Abstract Syntax Tree
$environment = new \League\CommonMark\Environment\Environment();
$environment->addEventListener(\League\CommonMark\Event\DocumentParsed::class, function ($event) {
\Log::debug('Document parsed', ['ast' => $event->getDocument()->dump()]);
});
$converter = new CommonMarkConverter(['environment' => $environment]);
// Check if a GFM feature is enabled
$environment = $converter->getEnvironment();
$hasTables = $environment->getExtension('tables') !== null;
| Error | Cause | Solution |
|---|---|---|
Invalid UTF-8 sequence |
Non-UTF-8 input | Use mb_convert_encoding() |
Unsupported extension |
Missing dependency | Install via Composer (composer require) |
HTML not stripped |
html_input misconfigured |
Set 'html_input' => 'strip' |
SSRF/XSS vulnerabilities |
Unsafe links/HTML | Enable allow_unsafe_links => false |
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Environment\EnvironmentInterface;
class CustomInlineParserExtension implements ExtensionInterface
{
public function register(EnvironmentInterface $environment)
{
$environment->addInlineParser(
new CustomInlineParser(),
150 // Priority (higher = earlier parsing)
);
}
}
class CustomInlineParser extends \League\CommonMark\InlineParser\InlineParserInterface
{
public function parse($offset, $length, $inlineParser, $context)
{
// Implement custom parsing logic
}
}
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Node\Element\AbstractBlock;
class CustomHeadingRenderer implements NodeRendererInterface
{
public function render(AbstractBlock $block, NodeRendererContext $context)
{
return '<custom-heading
How can I help you explore Laravel packages today?