A simple HTML renderer in PHP
Occasionally, projects are done that do not require usage of feature-rich frameworks or libraries. They usually have a short list of requirements and are built on top of simple infrastructure. Such projects are often prototypes that may become a complex system sometime in the future. But, until that happens, as a first step, a quick demo will be assembled and it needs to be as stable as possible. This post describes one possible way to go.
Let’s start with the top-level rendering function. It will output document doctype information and its root tags. Whole content will be rendered in between by the lambda (or anonymous) function passed in as the only parameter: $contentRenderer. A lambda function can contain other lambda functions and thus form a rendering tree that will result with well-formed HTML markup. Simple as that! 🙂
function renderHtml($contentRenderer = null) { echo "<!DOCTYPE HTML>n"; echo '<html>'; if ($contentRenderer) $contentRenderer(); echo '</html>'; }
HTML tags can have attributes, so it’s important to make place for them as the first few parameters of a rendering function. Of course, only attributes needed in project’s scope should be listed. Therefore, even though the <a> element can have a handful of parameters, only “href” (as mandatory) and “target” (the optional one) have been picked and will be used. Again, element’s body will be rendered by an outside lambda function.
function renderA($href, $target = '', $contentRenderer = null) { echo '<a', prepareAttribute('href', $href), prepareAttribute('target', $target, false), '>' if ($contentRenderer) $contentRenderer(); echo '</a>'; } function prepareAttribute($name, $value = '', $is_mandatory = true) { $attribute = ''; if ($is_mandatory || $value || $value === 0 || $value === '0') { $attribute = ' '.$name.'="'.htmlspecialchars($value).'"'; } return $attribute; }
A new utility function, prepareAttribute(), has been introduced in this example. It takes attribute name and value and concatenates them in the <name>=”<value>” form. If an attribute is marked as optional (that is, the $is_mandatory flag is set to false), its construction will be skipped unless a non-empty value is provided (which includes zero either as a number or a string). This will avoid having optional parameters rendered in the <name>=”” form and hence leave the final markup as clean as possible.
Now that basic concepts have been explained, here’s an example of a complete page rendering tree. It demonstrates how lambda functions are nested in each other and how they can be leveraged to render dynamic data loaded from DB or some other source.
<?php renderHtml(function() { renderHead(function() { renderTitle('Welcome to my new site!'); }); renderBody(function() { renderH1('Under Construction'); renderImg('http://www.mysite.com/images/logo.png', 'My Site Ltd.'); renderP(function() { echo htmlspecialchars('This site is still being constructed...'); renderBr(); renderA('http://www.mysite.com/', '', function() { echo htmlspecialchars('> Visit My Site's official website!'); }); renderA('http://www.some-other-site.com/', '_blank', function() { echo htmlspecialchars('> Get more information here.'); }); }); // load and render some text dynamically $textItems = loadTextItems(); foreach ($textItems as $item) { renderP(function() use ($item) { echo htmlspecialchars($item); }); }); }); }); ?>
Since lambda rendering functions are expected parameterless and as such simply executed, parameter passing needs to be done via closures. Where necessary, a rendering function can be linked to its calling scope by binding to that scope’s variables, thus forming a closure. Such variables can be used inside the function normally.
Naturally, this approach has its limits and it should be deprecated as project grows. It is intended for quick prototyping or for simple projects that don’t require heavy machinery. Once development starts “for real”, the code should switch to one of (many) PHP frameworks that can do a better job of rendering HTML markup.