Interfaces
There are two important XHP types, the \XHPChild interface (HHVM built-in) and
the \Facebook\XHP\Core\node base class (declared in XHP-Lib). You will most
commonly encounter these in functions' return type annotations.
\XHPChild
XHP presents a tree structure, and this interface defines what can be valid child nodes of the tree; it includes:
- all subclasses of
\Facebook\XHP\Core\nodeand the advanced interfaces described below - strings, integers, floats
- arrays of any of the above
Despite strings, integers, floats, and arrays not being objects, both the typechecker and HHVM consider them to implement this interface,
for parameter/return types and for is checks.
\Facebook\XHP\Core\node (x\node)
The \Facebook\XHP\Core\node base class is implemented by all XHP objects, via
one of its two subclasses:
\Facebook\XHP\Core\element(x\element): most common; subclasses implement arenderAsync()method that returns anothernode, and XHP-Lib automatically takes care of recursively rendering nested XHP objects\Facebook\XHP\Core\primitive(x\primitive): for very low-level nodes that need exact control of how the object is rendered to a string, or when the automatic handling of nested XHP objects is insufficient; subclasses implement astringifyAsync()method that returns astringand must manually deal with any children
The \Facebook\XHP\Core namespace is conventionally aliased as x (use Facebook\XHP\Core as x;), so you might encounter these classes as x\node,
x\element and x\primitive, which also mirrors their historical names.
Advanced Interfaces
While XHP's safe-by-default features are usually beneficial, occasionally they need to be bypassed; the most common cases are:
- Needing to embed the output from another template system when migrating to XHP.
- Needing to embed HTML from another source, for example, Markdown or BBCode renderers.
XHP usually gets in the way of this by:
- Escaping all variables, including your HTML code.
- Enforcing child relationships - and XHP objects can not be marked as allowing HTML string children.
The \Facebook\XHP\UnsafeRenderable and \Facebook\XHP\XHPAlwaysValidChild interfaces allow bypassing these safety mechanisms.
\Facebook\XHP\UnsafeRenderable
If you need to render raw HTML strings, wrap them in a class that implements this interface and provides a toHTMLStringAsync() method:
use namespace Facebook\XHP;
/* YOU PROBABLY SHOULDN'T DO THIS
*
* Even with a scary (and accurate) name, it tends to be over-used.
* See below for an alternative.
*/
class ExamplePotentialXSSSecurityHole implements XHP\UnsafeRenderable {
public function __construct(private string $html) {
}
public async function toHTMLStringAsync(): Awaitable<string> {
return $this->html;
}
}
use type Facebook\XHP\HTML\div;
<<__EntryPoint>>
async function start(): Awaitable<void> {
$xhp =
<div class="markdown">
{new ExamplePotentialXSSSecurityHole(
md_render('Markdown goes here'),
)}
</div>;
echo await $xhp->toStringAsync();
}
We do not provide an implementation of this interface as a generic implementation tends to be overused; instead, consider making more specific implementations:
use namespace Facebook\XHP;
final class ExampleMarkdownXHPWrapper implements XHP\UnsafeRenderable {
private string $html;
public function __construct(string $markdown_source) {
$this->html = md_render($markdown_source);
}
public async function toHTMLStringAsync(): Awaitable<string> {
return $this->html;
}
}
use type Facebook\XHP\HTML\div;
<<__EntryPoint>>
async function run(): Awaitable<void> {
$xhp =
<div class="markdown">
{new ExampleMarkdownXHPWrapper('Markdown goes here')}
</div>;
echo await $xhp->toStringAsync();
}
\Facebook\XHP\AlwaysValidChild
XHP's child validation can be bypassed by implementing this interface. Most classes that implement this interface are also implementations of
UnsafeRenderable, as the most common need is when a child is produced by another rendering or template system.
This can also be implemented by XHP objects, but this usually indicates that some class in getChildrenDeclaration() should be replaced with a more generic interface.
AlwaysValidChild is intentionally breaking part of XHP's safety, so should be used as sparingly as possible.
Example
use namespace Facebook\XHP;
final class XHPUnsafeExample implements XHP\UnsafeRenderable {
public async function toHTMLStringAsync(): Awaitable<string> {
/* HH_FIXME[2050] $_GET is not recognized by the typechecker */
return '<script>'.$_GET['I_LOVE_XSS'].'</script>';
}
}
use namespace Facebook\XHP\Core as x;
use type Facebook\XHP\HTML\{div, li};
<<__EntryPoint>>
function all_in_one_xhp_example_main(): void {
$inputs = Map {
'<div />' => <div />,
'<x:frag />' => <x:frag />,
'"foo"' => 'foo',
'3' => 3,
'true' => true,
'null' => null,
'new stdClass()' => new \stdClass(),
'vec[<li />, <li />, <li />]' => vec[<li />, <li />, <li />],
'XHPUnsafeExample' => new XHPUnsafeExample(),
};
$max_label_len = \max($inputs->mapWithKey(($k, $_) ==> \strlen($k)));
print Str\repeat(' ', $max_label_len + 1)." | XHPRoot | XHPChild\n";
print Str\repeat('-', $max_label_len + 1)."-|---------|----------\n";
foreach ($inputs as $label => $input) {
\printf(
" %s | %-7s | %s\n",
Str\pad_left($label, $max_label_len, ' '),
$input is x\node ? 'yes' : 'no',
$input is \XHPChild ? 'yes' : 'no',
);
}
}