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',
    );
  }
}
Output
| 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