Skip to content

Instantly share code, notes, and snippets.

@Flushot
Last active August 29, 2015 14:17
Show Gist options
  • Select an option

  • Save Flushot/7ef7f93a51713052bba9 to your computer and use it in GitHub Desktop.

Select an option

Save Flushot/7ef7f93a51713052bba9 to your computer and use it in GitHub Desktop.
HTML builder for PHP
<?php
/**
* HTML builder
* @author Chris Lyon <flushot@gmail.com>
*
* This functional HTML builder was inspired by JSX and eliminates the need to use the unsightly
* and error-prone string concatenation method of building HTML markup. You can now cleanly use
* nested functions as children and associative arrays as tag attributes.
*
* Example usage:
*
* echo tag('h1', function() {
* return [
* tag('a', [
* 'href' => 'http://www.google.com',
* 'class' => ['foo', 'bar'],
* 'style' => [
* 'text-decoration' => 'underline',
* 'font-style' => 'italic'
* ]
* ], 'hello'),
* tag('br'),
* 'world'
* ];
* });
*
* Outputs:
*
* <h1><a href="http://www.google.com" class="foo bar" style="text-decoration: underline; font-style: italic">hello</a><br/>world</h1>
*
* @param $tagName name of the tag.
* @param $attrsOrChild associative array of attributes or a child (see $child).
* @param $child child element(s) to render under this tag. can be a string, array of strings, or callable.
* @return HTML markup as a string.
*/
function tag($tagName, $attrsOrChild=null, $child=null) {
if (!is_string($tagName))
throw new InvalidArgumentException('$tagName must be a string');
$realChild = null;
// Handle polymorphic args
if (is_null($attrsOrChild)) {
$attrs = array();
$realChild = $child;
}
else {
if (is_array($attrsOrChild)) {
// Assume array is associative if keys aren't numeric
$isAssociative = (bool)count(array_filter(array_keys($attrsOrChild), 'is_string'));
// Handle as attributes
if ($isAssociative) {
$attrs = $attrsOrChild;
$realChild = $child;
}
// Handle as array of children
else {
$attrs = array();
$realChild = $attrsOrChild;
}
}
// Handle as child
elseif (is_string($attrsOrChild) || is_callable($attrsOrChild)) {
if (!is_null($child))
throw new InvalidArgumentException('Ambiguous children: $attrsOrChild and $child');
$attrs = array();
$realChild = $attrsOrChild;
}
}
// Evaluate child
if (!is_null($realChild)) {
if (is_callable($realChild))
$realChild = $realChild();
if (is_array($realChild))
$realChild = implode("\n", $realChild);
if (!is_string($realChild))
throw new InvalidArgumentException('Child must be a string or a callable');
}
// Doing this because unclear on how array_reduce() callback can handle associative arrays
$reducedAttrs = '';
foreach ($attrs as $k => $v) {
if (is_array($v)) {
switch ($k) {
case 'class':
// val1 valN ...
$value = implode(' ', $v);
break;
case 'style':
// key1: val1; keyN: valN; ...
$value = implode('; ', array_map(
function($styleKey) use ($v) {
return "$styleKey: " .
htmlspecialchars($v[$styleKey], ENT_QUOTES, 'UTF-8');
}, array_keys($v))); // array_map() doesn't iterate keys+vals
break;
default:
throw new InvalidArgumentException('array attributes are only valid for class or style');
}
}
else {
$value = htmlspecialchars($v, ENT_QUOTES, 'UTF-8');
}
$reducedAttrs .= " $k=\"$value\"";
}
// These tags allow the short closing "/>" syntax.
// See EMPTY elements defined in: http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_XHTML-1.0-Strict
$emptyTags = array('base', 'meta', 'link', 'hr', 'br', 'param', 'img', 'area', 'input', 'col');
$markup = '<' . $tagName . $reducedAttrs;
if (is_null($realChild) && in_array(strtolower($tagName), $emptyTags)) {
// Short close tag
$markup .= '/>';
}
else {
// Render children
$markup .= '>' . $realChild . '</' . $tagName . '>';
}
return $markup;
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment