A lightweight DSL for HTML generation in PHP
- 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>
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.
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.
- 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.
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 !');
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>
$ cd htmlgen/example
$ php index.php