z1ffy / htmlgen
A lightweight DSL for HTML generation in PHP.
This package is not auto-updated.
Last update: 2025-02-24 15:24:26 UTC
README
A lightweight DSL for HTML generation in PHP
What's new in 2.0 ?
- All the magic is gone
- Version 1 API is completely obsolete
- No more auto-echoing
- No more output buffering
- No more closures for child elements
- No more explicit data stores/fetches
- No more pretty printing (for now, at least)
- Code base is 25% smaller
- Unit tests
- Namespaces
- Safely encodes all text nodes with
htmlentities
- Achieve "templating" via native PHP
require
(see the provided example) - User-defined elements
- Lite support for Zen-like elements
Remember all of the closures ?
// version 1.0 required closures for all children ... h::html(function(){ h::head(function(){ h::meta(array("charset"=>"UTF-8")); h::link(array("rel"=>"stylesheet", "type"=>"text/css", "href"=>"global.css")); }); h::body(function(){ ... // version 2.0 variadic interface allows passing as many children as you want h('html', h('head', h('meta', ['charset'=>'UTF-8']), h('link', ['rel'=>'stylesheet', 'type'=>'text/css', 'href'=>'global.css']) }) h('body', ...
Remember how hard it was to thread data to the children ?
// version 1.0 required `global` unless `use` was specified on every closure h::table(function(){ // icky global state global $table_data; h::tr(array("class"=>"header"), function(){ h::th("key"); h::th("value"); }); foreach($table_data as $k => $v){ // verbose function use($k,$v) ... h::tr(array("class"=>h::cycle(array("odd", "even"))), function() use($k,$v){ h::td($k); h::td($v); }); } }); // version 2.0 has no problem accessing data in child nodes h('table', h('tr', ['class'=>'header'], h('th', 'key'), h('th', 'value') ), map($table_data, function ($v,$k) { return h('tr', h('td', $k), h('td', $v) ) }) );
Remember how specifying id
and class
for everything was so annoying?
h('#sidebar.column', h('.section', h('h2.section-title', 'Cat Links'), h('button.btn.btn-large', 'Adopt a cat now !') ) );
Will now render
<div id="sidebar" class="column"> <div class="section"> <h2 class="section-title">Cat Links</h2> <button class="btn btn-large">Adopt a cat now !</button> </div> </div>
Requirements
Sorry, but it requires PHP >= 7
right now. The only real thing causing this is
the type hinting right now. I'll eventually get around to making a PHP 5.x
version. Eventually. Probably.
API
html ( string
tag [, assoc
attributes], mixed
...children ) : RawString
This is your bread and butter. See the exaples for more help. Reminder:
htmlgen\RawString
is an implementation detail and should be ignored. Never
write tests against this type or check $html instanceof RawString
. Just know
that RawString
can be coerced to a string
so just treat it as such.
map ( array
xs , callable
λ(mixed
v , mixed
k ): string
): array
This function is very helpful for building lists of nodes from existing data.
This exists because array_map
doesn't pass in array keys by default. Also,
note the order of arguments in this function compared to native array_map
.
See the code examples below for more details.
raw ( string
html ): RawString
All child strings passed to html
will automatically have html entities encoded
using htmlentities($str, ENT_HTML5)
. If you would like to bypass encoding, you
can wrap a string using this function.
render ( resource
writableStream , mixed
...children ) : int
Most people will probably just use echo html(...)
which is fine, but render
is a bit more flexible as it allows you to render to any writable stream. That
means render(STDOUT, html(...))
is effectively the same as echo html(...)
.
Use this for writing html to files render($fd, ...)
or to memory
$mem = fopen('php://memory'); render($mem, ...)
, or skip render
altogether
and just $html = html(...); doSomething($html);
It's PHP, you can figure it
out.
capture ( callable
f[, ...xs] ) : RawString
Use this when you have an unmaneuverable, unwieldy, clumsy, impure function such
as 100% of the functions found within WordPress. This will conveniently hijack
any function that otherwise writes to STDOUT
and instead bottles it up in
string
. capture('the_title')
will return a string instead of echoing. If you
need to pass arguments, you can capture('the_title', $postId)
or you can even
use a lambda, capture(function($id) { the_title($id); }, $postId)
or
capture(function() use($postId) { the_title($postId); })
. Remember, how I said
it's just PHP ? It's just PHP. And yes I know get_the_title
exists. This is
an example.
Oh yeah, it should go without saying that capture
is not a magician or a
mind-reader. It wil not automatically encode HTML entities returned from the
callable. It can (and does) promise not to double encode HTML entities, tho.
WARNINGS
- htmlgen displays html entities only. A special string wrapper is required
to output raw HTML strings. Guess what that helper is called? It's called
raw
. I already said that above. C'mon.
Code example
example/index.js
require '../htmlgen.php'; require './htmlgen.extensions.php'; use function htmlgen\html as h; use function htmlgen\render; render(STDOUT, h('doctype'), h('html', ['lang'=>'en'], h('head', h('meta', ['charset'=>'utf-8']), h('meta', ['http-equiv'=>'X-UA-Compatible', 'content'=>'IE=edge']), h('meta', ['name'=>'viewport', 'content'=>'width=device-width, initial-scale=1']), h('link', ['rel'=>'stylesheet', 'type'=>'text/css', 'href'=>'/main.css']), h('condition', 'lt IE 9', h('script', ['src'=>'ie.js']) ) ), h('body', h('header', require './navigation.php' ), h('main', require './body.php' ), h('footer', require './footer.php' ), h('script', ['src'=>'/main.js']) ) ) );
example/navigation.php
use function htmlgen\html as h; use function htmlgen\map as map; $links = [ 'home' => '/', 'cats' => '/cats', 'milk' => '/milk', 'honey' => '/honey', 'donuts' => '/donuts', 'bees' => '/bees' ]; return h('nav', h('ul', map($links, function($href, $text) { return h('li', h('a', ['href'=>$href], $text) ); }) ) );
example/body.php
use function htmlgen\html as h; use function htmlgen\map; use function htmlgen\raw; $beeData = [ 'pop' => 'yup', 'candy' => 'sometimes', 'flowers' => 'so much', 'water' => 'not really', 'sand' => 'indifferent', 'donuts' => 'most definitely' ]; return [ h('h1', 'Hello from HtmlGgen'), h('comment', 'really cool and thought-provoking article'), h('article', h('h2', 'All about honey'), h('img', ['src'=>'/busybeehive.png', 'alt'=>'bees like to keep busy!', 'width'=>300, 'height'=>100]), h('p', 'Did you know that bees are responsible for making honey ?'), h('p', 'It\'s a wonder more people don\'t like bees !'), h('p', 'Bees are > htmlentities'), // if you really must output HTML, you can use the `raw` utility h('p', raw('Raw honey is the <strong>best</strong>')), h('table', h('thead', h('tr', h('td', 'item'), h('td', 'do bees like it?') ) ), h('tbody', map($beeData, function($value, $key) { return h('tr', h('td', $key), h('td', $value) ); }) ) ), h('aside', 'Did you know that queen bees come from larvae that are overfed with royal jelly ?') ), h('comment', 'newsletter signup form'), h('form', ['action'=>'#subscribe'], h('input', ['name'=>'email', 'autofocus']), h('input', ['type'=>'button', 'value'=>'Get Bee News !']) ) ];
example/footer.php
use function htmlgen\html as h; // notice "&" will automatically be converted to "&" // this behavior protects you from malicious user input return h('p', 'Thanks for your interest in cats & donuts and stuff !');
Output
Line wrapping (actual output does not contain line breaks)
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" type="text/css" href="/main.css"><!--[if lt IE 9]><script src="ie.js"></script> <![endif]--></head><body><header><nav><ul><li><a href="/">home</a></li><li> <a href="/cats">cats</a></li><li><a href="/milk">milk</a></li><li> <a href="/honey">honey</a></li><li><a href="/donuts">donuts</a></li><li> <a href="/bees">bees</a></li></ul></nav></header><main><h1>Hello from HtmlGgen </h1><!-- really cool and thought-provoking article --><article><h2> All about honey</h2><img src="/busybeehive.png" alt="bees like to keep busy!" width="300" height="100"><p>Did you know that bees are responsible for making honey ?</p><p>It's a wonder more people don't like bees !</p><p>Bees are > htmlentities</p><p>Raw honey is the <strong>best</strong></p><table> <thead><tr><td>item</td><td>do bees like it?</td></tr></thead><tbody><tr> <td>pop</td><td>yup</td></tr><tr><td>candy</td><td>sometimes</td></tr><tr><td> flowers</td><td>so much</td></tr><tr><td>water</td><td>not really</td></tr><tr> <td>sand</td><td>indifferent</td></tr><tr><td>donuts</td><td>most definitely </td></tr></tbody></table><aside>Did you know that queen bees come from larvae that are overfed with royal jelly ?</aside></article><!-- newsletter signup form --><form action="#subscribe"><input name="email" autofocus><input type="button" value="Get Bee News !"></form></main><footer><p>Thanks for your interest in cats & donuts and stuff !</p></footer><script src="/main.js"></script></body></html>
Try it and see!
$ cd htmlgen/example
$ php index.php