XHP: Interfaces
There are two important interfaces in XHP, XHPRoot
and XHPChild
, that you will need to use these when adding type annotations to functions.
XHPRoot
The XHPRoot
interface is a implemented by all XHP objects; in practice, this means:
:x:element
subclasses -- these are classes that re-use (and combine) existing XHP classes:x:primitive
subclasses -- these define basic elements, such as:x:frag
, and all of the basic HTML elements- implementations of the
XHPUnsafeRenderable
interface described below
XHPChild
XHP presents a tree structure, and this interface defines what can be valid child nodes of the tree; it includes:
- All implementations of
XHPRoot
- 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.
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 XHPUnsafeRenderable
and XHPAlwaysValidChild
interfaces allow bypassing these safety mechanisms.
XHPUnsafeRenderable
If you need to render raw HTML strings, wrap them in a class that implements this interface and provides a toHTMLString: string
method:
require __DIR__."/../../../../vendor/hh_autoload.php";
require_once __DIR__.'/md_render.inc.php';
/* 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 XHPUnsafeRenderable {
public function __construct(private string $html) {
}
public function toHTMLString(): string {
return $this->html;
}
}
<<__EntryPoint>>
function start(): void {
echo (
/* HH_FIXME[4067] implicit __toString() is now deprecated */
<div class="markdown">
{new ExamplePotentialXSSSecurityHole(
HHVM\UserDocumentation\XHP\Examples\md_render('Markdown goes here'),
)}
</div>
).
"\n";
}
We do not provide an implementation of this interface as a generic implementation tends to be overused; instead, consider making more specific implementations:
require __DIR__."/../../../../vendor/hh_autoload.php";
require_once __DIR__.'/md_render.inc.php';
class ExampleMarkdownXHPWrapper implements XHPUnsafeRenderable {
private string $html;
public function __construct(string $markdown_source) {
$this->html = HHVM\UserDocumentation\XHP\Examples\md_render(
$markdown_source,
);
}
public function toHTMLString(): string {
return $this->html;
}
}
<<__EntryPoint>>
function run(): void {
echo (
/* HH_FIXME[4067] implicit __toString() is now deprecated */
<div class="markdown">
{new ExampleMarkdownXHPWrapper('Markdown goes here')}
</div>
).
"\n";
}
XHPAlwaysValidChild
XHP's child validation can be bypassed by implementing this interface. Most classes that implement this interface are also implementations of
XHPUnsafeRenderable
, 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 a child class specification should be replaced with a category. This interface is intentionally breaking part of XHP's safety, so should be used as sparingly as possible.
Example
require __DIR__."/../../../../vendor/hh_autoload.php";
final class XHPUnsafeExample implements XHPUnsafeRenderable {
public function toHTMLString(): string {
return '<script>'.$_GET['I_LOVE_XSS'].'</script>';
}
}
<<__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 \HH\Lib\Str\repeat(' ', $max_label_len + 1)." | XHPRoot | XHPChild\n";
print \HH\Lib\Str\repeat('-', $max_label_len + 1)."-|---------|----------\n";
foreach ($inputs as $label => $input) {
printf(
" %s | %-7s | %s\n",
\HH\Lib\Str\pad_left($label, $max_label_len, ' '),
$input is XHPRoot ? 'yes' : 'no',
$input is XHPChild ? 'yes' : 'no',
);
}
}
| XHPRoot | XHPChild
-----------------------------|---------|----------
<div /> | yes | yes
<x:frag /> | yes | yes
"foo" | no | yes
3 | no | yes
true | no | no
null | no | no
new stdClass() | no | no
vec[<li />, <li />, <li />] | no | yes
XHPUnsafeExample | no | yes