XHP介绍
XHP提供您的输出(通常为HTML)的本机XML样表示。这样可以对您的UI代码进行类型检查,并自动避免多个常见问题,如跨站点脚本(XSS)和双重转义。它还应用其他验证规则,例如<head>必须包含<title>。
使用传统插值,一个简单的页面可能如下所示:
<?hh
$ user_name = 'Fred' ;
echo “<tt> Hello <strong> $ user_name </ strong> </ tt>” ;
然而,使用XHP,它看起来像这样:
<?hh
$ user_name = 'Fred' ;
echo < tt > Hello < strong > { $ user_name } </ strong > </ tt > ;
第一个例子使用字符串插值来输出HTML,而第二个例子没有引号,这意味着Hack完全可以理解语法 - 但这并不意味着你需要做的只是删除引号。所需的其他步骤包括:
- 使用花括号包括变量 - 例如"<a>$foo</a>"变成<a>{$foo}</a>。
- 由于XHP是象XML,所有的元素都必须关闭-例如,"<br>"成为<br />。
- 确保您的HTML已正确嵌套。
- 删除所有的HTML /属性转义 - 例如,您不需要htmlspecialchars()在XHP输出中包含一个变量之前调用; 如果你这样做,它将被双重转义。
关于Namespaces
XHP目前有几个命名空间的问题; 我们建议:
- XHP类没有在命名空间中声明
- 使用XHP类的代码没有命名空间。
我们计划在未来支持namespaces。
XHP-Lib库
虽然XHP语法是Hack的一部分,但很大一部分实现是在一个名为XHP-Lib的普通库中,需要通过编写器来安装:
“require”:{
“facebook / xhp-lib”:“〜2.2”
}
这包括基类和接口以及标准HTML元素的定义。
为什么要使用XHP?
大多数用户的初始原因是因为它是“默认安全”:所有变量都以上下文相适应的方式自动转义(例如,有不同的转义属性值与文本节点的规则)。另外,XHP被类型检查器理解,确保不传递属性值。一个常见的例子是border="3",但是border是一个on / off属性,所以3的值没有意义。
对于XHP经验丰富的用户来说,最大的优点是可以轻松地添加自定义的“元素”,使用自己的行为,然后可以像纯HTML元素一样使用。例如,该网站定义<a:post>了与标准<a>标签具有相同接口的标签,但是使用POST请求而不是GET请求:
<?hh // strict
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;
}
}
需要一点CSS,以便<form>
不创建块元素:
form.postLink {
display:inline;
}
在这一点上,新元素可以像任何内置元素一样使用:
<?hh
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("f20194a67b").submit(); return false;">I make a POST REQUEST</a></form>
运行时验证
由于XHP对象是一流的,而不仅仅是字符串,因此可以进行一整套验证,以确保您的UI没有细微的错误:
<?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
上述代码将不会进行类型检查或运行,因为XHP验证器将会看到<span>
并且<naps>
标签不匹配 - 但是以下代码将正确键入正确但无法运行,因为在标记匹配时,它们不能正确嵌套(根据HTML规范),并且嵌套验证仅在运行时发生:
?hh
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]"
安全
基于字符串的条目和验证是跨站点脚本(XSS)的首选。你可以通过使用特殊功能来解决这个问题htmlspecialchars(),但是你必须真正记住使用这些功能。输出之前,XHP自动将保留的HTML字符转义为HTML实体。
<?hh
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><blink>Ugh</blink></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><blink>Ugh</blink></body></html>