A small markdown processor that your app can call to turn Markdown text into HTML. It's a single-purpose converter with a deliberately small feature set — everything it recognizes maps directly to semantic HTML, and nothing more. No framework, no build pipe, no opinions about your markup.
ForgeMarkDown is exposed to your app as the
ForgeMarkDownInterface, resolved
through the container — you ask for it and call
parse():
use Modules\ForgeMarkDown\Contracts\ForgeMarkDownInterface;
final class SomeController
{
public function __construct(
private readonly ForgeMarkDownInterface $markdown,
) {}
public function show(): string
{
$html = $this->markdown->parse('# Hello, world');
// returns "Hello, world
"
return $html;
}
}
parse() runs the text through a
single pipeline, in order:
--- YAML block is detected and
stripped.
The output is trimmed before it's returned, and anything the processor doesn't recognize passes through untouched — so the rendered HTML holds only the structure you asked for.
The inline pass recognizes a focused set of constructs:
| Markdown | Renders |
|---|---|
**bold** / __bold__ |
<strong> |
*em* / _em_ |
<em> |
~~strike~~ |
<del> |
[text](url) |
<a href> |
 |
<img> |
`code` |
<code> |
Link and image destinations and inline code are escaped, so a URL or snippet can't smuggle raw markup out of your rendered content.
# through ######
produce <h1> to
<h6>.
Three or more ---,
***, or ___
become <hr>.
A line starting with >
becomes a
<blockquote>.
Lines starting with -,
*, or + become
a <ul> with
<li> items.
GitHub-style pipe tables
(header + separator +
rows) render to a
<table> with
<thead> and
<tbody>.
Fenced (```, optionally with
a language) and indented blocks become
<pre><code>.
## The pitch
> One line of context.
1. first
2. second
| Name | Role |
|-------|--------|
| Ada | Admin |
| Grace | Editor |
Fenced code blocks carry an optional language,
which is reflected as a class on the
<code> element (so your syntax
highlighter can hook in):
```php
$greeting = 'hello';
```
Rendering to <pre><code
class="language-php"></code></pre>.
Indented code — four leading spaces or a tab — is
also recognized and wrapped in a
<pre><code> pair. Block
contents are escaped, keeping rendered examples
intact rather than executable.
For whole files, parseFile() handles
a leading YAML front-matter block — handy for
content documents that carry metadata alongside
their body:
use Modules\ForgeMarkDown\Contracts\ForgeMarkDownInterface;
$result = $markdown->parseFile('storage/docs/welcome.md');
$content = $result['content']; // rendered HTML
$front_matter = $result['front_matter']; // associative array from YAML
The front-matter array is parsed with the
yaml extension when it's available;
if the YAML can't be parsed, the method returns
that as an error entry rather than throwing.
parse() itself strips any front matter
without attempting to parse it — it's only
parseFile() that yields the metadata
as data.
Resolve the interface and use the two methods:
parse(string $markdown): string
— convert a Markdown string to an HTML
string.
parseFile(string $path): array
— read a file, return
['content' => html,
'front_matter' => array].
Everything is injected, so the converter plugs into controllers, commands, notification bodies, or a static-site generator without ceremony — ask for the interface and it's ready.
It's worth being honest about the scope, because ForgeMarkDown is deliberately small and not a full spec implementation:
<ul>
regardless of the bullet character.
When you need the full Markdown surface area, align a dedicated parser with your content — but for rendering trusted, light Markdown this helper keeps the footprint tiny and predictable.
php forge.php package:install-module --module=ForgeMarkDown
The capability has no commands, no routes, and no
config to set — it registers the
ForgeMarkDownInterface binding with
the container, and you start parsing. (Optional:
the yaml extension adds front-matter
data parsing for parseFile().)