XHP: Introduction

XHP provides a native XML-like representation of your output (usually HTML). This allows your UI code to be typechecked, and automatically avoids several common issues such as cross-site scripting (XSS) and double-escaping. It also applies other validation rules, e.g. <head> must contain <title>.

Using traditional interpolation, a simple page could look like this:

<?hh
$user_name = 'Fred';
echo "<tt>Hello <strong>$user_name</strong></tt>";

However, with XHP, it looks like this:

<?hh
$user_name = 'Fred';
echo <tt>Hello <strong>{$user_name}</strong></tt>;

The first example uses string interpolation to output the HTML, while the second has no quotation marks — meaning that the syntax is fully understood by Hack — but this does not mean that all you need to do is remove quotation marks. Other steps needed include:

  • Use curly braces to include variables - e.g. "<a>$foo</a>" becomes <a>{$foo}</a>.
  • As XHP is XML-like, all elements must be closed - e.g. "<br>" becomes <br />.
  • Make sure your HTML is properly nested.
  • Remove all HTML/attribute escaping - e.g. you don't need to call htmlspecialchars() before including a variable in your XHP output; and if you do, it will be double-escaped.

About Namespaces

XHP currently has several issues with namespaces; we recommend that:

  • XHP classes are not declared in namespaces
  • code that use XHP classes is not namespaced.

We plan to support namespaces in the future.

The XHP-Lib Library

While the XHP syntax is part of Hack, a large part of the implementation is in a normal library called XHP-Lib that needs to be installed via composer:

"require": {
  "facebook/xhp-lib": "~2.2"
}

This includes the base classes and interfaces, and definitions of standard HTML elements.

Why use XHP?

The initial reason for most users is because it is 'safe by default': all variables are automatically escaped in a context-appropriate way (e.g. there are different rules for escaping attribute values vs text nodes). In addition, XHP is understood by the typechecker, making sure that you don't pass attribute values. A common example of this is border="3", but border is an on/off attribute, so a value of 3 doesn't make sense.

For users experienced with XHP, the biggest advantage is that it is easy to add custom 'elements' with your own behavior, which can then be used like plain HTML elements. For example, this site defines an <a:post> tag that has the same interface as a standard <a> tag, but makes a POST request instead of a GET request:

<?hh // strict
/*
 *  Copyright (c) 2004-present, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

require_once(__DIR__."/../../../vendor/hh_autoload.php");

final class :a:post extends :x:element {
  attribute :a;

  use XHPHelpers;

  protected function render(): XHPRoot {
    $id = $this->getID();

    $anchor = <a>{$this->getChildren()}</a>;
    $form = (
      <form
        id={$id}
        method="post"
        action={$this->:href}
        target={$this->:target}
        class="postLink"
      >{$anchor}</form>
    );

    $this->transferAllAttributes($anchor);
    $anchor->setAttribute(
      'onclick',
      'document.getElementById("'.$id.'").submit(); return false;',
    );
    $anchor->setAttribute('href', '#');

    return $form;
  }
}

A little CSS is needed so that the <form> doesn't create a block element:

form.postLink {
  display: inline;
}

At this point, the new element can be used like any built-in element:

<?hh
require __DIR__ . "/../../../../vendor/hh_autoload.php";

function intro_examples_a_a_post() {
  $get_link =
   <a href="http://www.example.com">I'm a normal link</a>;
  $post_link =
    <a:post href="http://www.example.com">I make a POST REQUEST</a:post>;

  echo $get_link;
  echo "\n";
  echo $post_link;
}

intro_examples_a_a_post();
Output
<a href="http://www.example.com">I'm a normal link</a>
<form id="f20194a67b" method="post" action="http://www.example.com" class="postLink"><a href="#" id="f20194a67b" onclick="document.getElementById(&quot;f20194a67b&quot;).submit(); return false;">I make a POST REQUEST</a></form>

Runtime Validation

Since XHP objects are first-class and not just strings, a whole slew of validation can occur to ensure that your UI does not have subtle bugs:

<?hh

function intro_examples_tag_matching_validation_using_string(): void {
  echo '<div class="section-header">';
  echo '<a href="#use">You should have used <span class="xhp">XHP</naps></a>';
  echo '</div>';
}

function intro_examples_tag_matching_validation_using_xhp(): void {
  // Typechecker error
  // Fatal syntax error at runtime
  echo
    <div class="section-header">
      <a href="#use">You should have used <span class="xhp">XHP</naps></a>
    </div>;
}

function intro_examples_tag_matching_validation_run(): void {
  intro_examples_tag_matching_validation_using_string();
  intro_examples_tag_matching_validation_using_xhp();
}

intro_examples_tag_matching_validation_run();
Output
Fatal error: XHP: mismatched tag: 'naps' not the same as 'span' in /data/users/joelm/user-documentation/guides/hack/24-XHP/01-introduction-examples/tag-matching-validation.php.type-errors on line 14

The above code won't typecheck or run because the XHP validator will see that <span> and <naps> tags are mismatched - however the following code will typecheck correctly but fail to run, because while the tags are matched, they are not nested correctly (according to the HTML specification), and nesting verification only happens at runtime:

<?hh

require __DIR__ . "/../../../../vendor/hh_autoload.php";

function intro_examples_allowed_tag_validation_using_string(): void {
  echo '<ul><i>Item 1</i></ul>';
}

function intro_examples_allowed_tag_validation_using_xhp(): void {
  try {
    echo <ul><i>Item 1</i></ul>;
  } catch (\XHPInvalidChildrenException $ex) {
    // We will get here because an <i> cannot be nested directly below a <ul>
    var_dump($ex->getMessage());
  }
}

function intro_examples_allowed_tag_validation_run(): void {
  intro_examples_allowed_tag_validation_using_string();
  echo PHP_EOL . PHP_EOL;
  intro_examples_allowed_tag_validation_using_xhp();
}

intro_examples_allowed_tag_validation_run();
Output
<ul><i>Item 1</i></ul>

string(262) "Element `ul` was rendered with invalid children.

/data/users/joelm/user-documentation/guides/hack/24-XHP/01-introduction-examples/allowed-tag-validation.php:11

Verified 0 children before failing.

Children expected:
(:li)*

Children received:
:i[%flow,%phrase]"

Security

String-based entry and validation are prime candidates for cross-site scripting (XSS). You can get around this by using special functions like htmlspecialchars(), but then you have to actually remember to use those functions. XHP automatically escapes reserved HTML characters to HTML entities before output.

<?hh

require __DIR__ . "/../../../../vendor/hh_autoload.php";

function intro_examples_avoid_xss_using_string(string $could_be_bad): void {
  // Could call htmlspecialchars() here
  echo '<html><head/><body> ' . $could_be_bad . '</body></html>';
}

function intro_examples_avoid_xss_using_xhp(string $could_be_bad): void {
  // The string $could_be_bad will be escaped to HTML entities like:
  // <html><head></head><body>&lt;blink&gt;Ugh&lt;/blink&gt;</body></html>
  echo
    <html>
      <head/>
      <body>{$could_be_bad}</body>
    </html>;
}

function intro_examples_avoid_xss_run(string $could_be_bad): void {
  intro_examples_avoid_xss_using_string($could_be_bad);
  echo PHP_EOL . PHP_EOL;
  intro_examples_avoid_xss_using_xhp($could_be_bad);
}

intro_examples_avoid_xss_run('<blink>Ugh</blink>');
Output
<html><head/><body> <blink>Ugh</blink></body></html>

<html><head></head><body>&lt;blink&gt;Ugh&lt;/blink&gt;</body></html>