Skip to main content

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\node and 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 a renderAsync() method that returns another node, 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 a stringifyAsync() method that returns a string and 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',
);
}
}