此包已被放弃,不再维护。作者建议使用facebook/xhp-lib包。

XHP类库

安装: 544

依赖者: 0

建议者: 0

安全: 0

星星: 1,233

关注者: 80

分支: 157

语言:Hack

v2.9.1 2019-02-11 19:46 UTC

README

介绍

XHP增强了Hack的语法,使XML文档片段成为有效的
PHP表达式。这允许您将Hack用作更严格的模板引擎,并提供更直接的
可重用组件的实现。

此存储库包含使用XHP 2.0及以上版本所需的类库,需要HHVM,但早期版本
可以在HHVM和PHP5中运行,可从以下链接获取扩展:
https://github.com/facebookarchive/xhp-php5-extension
如果您使用HHVM并希望有一个高级的XHP UI库,您可能想看看以下链接:

https://github.com/hhvm/xhp-bootstrap/

公告和文章发布在
XHP-Lib博客上

安装

Composer是推荐的安装方法。要将XHP添加到您的项目,请将以下内容添加到您的


  "require": {
    "facebook/xhp-lib": "2.x"
  }

composer.json文件中,然后重新运行composer

$ hhvm $(which composer) install;

使用HHVM运行Composer命令如下:

此外,您还需要使用hhvm-autoload作为您的自动加载器。

<?hh
$href = 'https://#';
echo <a href={$href}>Facebook</a>;

简单示例

请注意第3行的语法,这不是一个字符串。这是XHP为Hack引入的主要新语法。

任何在{}中的内容都被解释为完整的PHP表达式。这与双引号字符串中的{}不同;双引号字符串只能包含变量。

您可以在PHP中定义任意元素,这些元素可以实例化。在幕后,您创建的每个元素都是一个类的实例。要定义新元素,只需定义一个新类。XHP附带了一组预定义元素,这些元素为您实现了大多数HTML。

复杂结构

<?hh
$post =
  <div class="post">
    <h2>{$post}</h2>
    <p><span>Hey there.</span></p>
    <a href={$like_link}>Like</a>
  </div>;

请注意,XHP结构可以是任意复杂的。这是一个有效的XHP程序

与字符串构造相比,XHP的一个优点是它在编译时强制执行正确的标记结构。也就是说,表达式$foo = <h1>Header</h2>;不是一个有效的表达式,因为您不能用

/h2>关闭

标签。当构建大量的标记时,完全正确可能很困难。使用XHP时,编译器现在会检查您的工作,并且只有在标记正确的情况下才会运行。

动态结构

<?hh
$list = <ul />;
foreach ($items as $item) {
  $list->appendChild(<li>{$item}</li>);
}

有时可能有用创建一些元素,并将它们动态地添加到元素中作为子元素。所有XHP对象都支持appendChild方法,其行为类似于相同的JavaScript方法。例如

在代码中,<ul />创建了一个没有子元素的ul。然后我们为$items列表中的每个项动态地添加子元素。

XHP的一个有趣特性是自动转义的概念。在纯PHP中,如果你想渲染用户输入的内容,必须手动转义。这种做法容易出错,并且随着时间的推移,已被证明是不可持续的解决方案。它增加了代码复杂性,并且仍然因为编程的疏忽而导致安全漏洞。然而,由于XHP对页面结构有上下文特定的信息,它可以自动转义数据。以下两个示例是相同的,并且都是“安全的”。

<?php
echo '<div>Hello '.htmlspecialchars($_GET['name']).'</div>';
<?hh
echo <div>Hello {$_GET['name']}</div>;

正如你所见,使用XHP将安全性作为默认设置,而不是例外。

定义元素

XHP中的所有元素都是PHP类。甚至像div和span这样的基本HTML元素也是类。你定义一个元素的方式就像定义一个类一样,只是你需要使用一个前导冒号来指定你正在创建一个XHP元素

<?hh
class :fb:thing extends :x:element {
  ...
}

在定义了fb:thing之后,我们可以用表达式<fb:thing />来实例化它。:x:element是定义元素时应该从它继承的核XHP类。它将为你提供所有需要的方法,如appendChild等。作为一个,你必须只定义render()render()应该总是返回更多的XHP元素。重要的是要记住这个规则:即使是你自己定义的元素也会返回XHP元素。唯一允许返回字符串的XHP元素是从:x:primitive派生的元素。唯一应该从:x:primitive派生的元素是构成基本HTML构建块的基础元素。XHP与核心HTML库相结合,可以成为标记字符串的有效替代品。

你还可以使用前导冒号语法在通常使用类名的地方使用XHP元素,例如在引用类常量或类型提示时

<?hh
echo :fb:thing::someConstant;
<?hh
function giveMeAThing(:fb:thing $thing) {
}

定义属性

大多数元素将接受一些属性,这些属性会影响其行为。你可以在元素中使用attribute关键字定义属性。

<?hh
class :fb:thing extends :x:element {
  attribute
    string title = "No Title",
    enum { "cool", "lame" } type;
}

在这里,我们定义了两个属性,titletypetitle的类型是string,默认值是"No Title"。类型是enum,有两个有效值——"cool""lame"。有效的类型是boolintarraystringvar。你还可以指定一个类或元素名作为类型。最后,你可以在名称后加上@required来指定这个属性是渲染元素所必需的。

请注意,当你扩展另一个元素时,你将始终继承它的属性。然而,任何具有相同名称的属性将覆盖你父元素的属性。

你还可以通过在定义中只指定一个标签名来窃取另一个元素的属性。声明attribute :div表示这个元素可以接受任何div元素可以接受的属性。

定义元素结构

所有元素都必须遵循某种结构。例如,在HTML5中,<input>元素不能直接出现在<body>标签内(它必须在表单内)。XHP允许你定义一个内容模型,文档必须遵守。这是通过使用children关键字来完成的,它的语法类似于正则表达式。注意,与attribute不同,children只能在任何类中出现一次。

<?hh
class :fb:thing-container extends :x:element {
  children (:fb:thing1 | :fb:thing2)*;
}

children声明支持以下后缀操作符

  • ?: 零个或一个实例
  • *: 零个或多个实例
  • +: 一个或多个实例

如果没有指定运算符,则声明必须完全匹配一次。您也可以使用 ,| 运算符将多个声明组合成一个。, 运算符指定一个必须按顺序出现的声明列表,而 | 指定一个声明列表,其中至少有一个必须匹配。例如,如果您正在定义页面布局,您的子声明可能如下所示

children (:fb:left-column?, :fb:content, :fb:right-column?);

这指定了一个可选的左侧列,后面是必需的内容和可选的右侧列。

您还可以使用特殊声明 anyempty 来指定元素可以接受任何元素或没有元素。如果您没有指定子声明,则将继承父类的声明。`:x:element` 的子声明是 children any;

注意:检查子声明遵守性的算法是贪婪的,没有回溯。对于大多数子声明,这不会产生影响,但如果您正在定义复杂的子声明,您应该知道它是如何工作的。基本上,这个声明是无法满足的:children (:fb:thing*, :fb:thing);* 后缀运算符是贪婪的,会捕获所有的 fb:thing

元素类别

很多时候,您可能希望接受特定组的所有子元素,但列举该组开始变得不可持续。当这种情况发生时,您可以定义一个元素组,并在子声明中指定您的元素是成员。然后您可以使用 % 前缀在子声明中引用该组。

class :fb:thing1 extends :x:element {
  category %fb:thing;
}
class :fb:thing2 extends :x:element {
  category %fb:thing;
}
class :fb:thing-container extends :x:element {
  children (%fb:thing)*;
}

异步数据获取

XHP 支持Hack的‘async’功能,允许您构建组件,
有效地获取它们所需的数据

class :async-thing extends :x:element {
  use XHPAsync;
  protected async function asyncRender(): Awaitable<XHPRoot> {
    $db = await AsyncMysqlClient::connect(...);
    $result = await $db->queryf(
      'SELECT id2 FROM %T WHERE id1 %=d',
      'friends',
      $this->getContext('viewer')->getUserID(),
    );
    $rows = $result->mapRowsTyped();
    $friend_ids = $rows->map($row ==> $row['id2']);
    $friend_data = await HH\Asio\mm(
      $friend_ids,
      $id ==> User::get($id),
    );
    $out = <ul />;
    foreach ($friend_ids as $id) {
      $out->appendChild(<li>.... </li>);
    }
    return $out;
  }
}

当渲染 XHP 树时,会对所有 XHPAsync 子元素调用 asyncRender(),
并且数据获取是并行的。这允许您的组件的数据依赖性高效地成为实现细节,而不是作为API的一部分,并通过用户(例如在属性中)传递。

空白符

XHP 中,仅包含空白符的文本节点会被删除。表达式 <div> </div><div /> 是相同的。包含非空白符的文本节点会在左右两边被修剪至最多1个空格。这值得注意,因为您可能想要做如下操作

这将导致不可预期的结果,因为 :$title 之间的空格将丢失。为了解决这个问题,请尝试将空格移到 <label /> 标签中。如果您不能这样做,那么请使用 {' '},这将不会被删除。

<?hh
$node = <div><label>Title:</label> <span>{$title}</span></div>;

最佳实践

在使用 XHP 时,您应遵守某些约定。

  • 不要使用无命名空间元素污染全局 XHP 命名空间。您定义的大多数元素都应该使用某个命名空间。不使用命名空间的元素绝对不应该有“魔法”特性。例如,
<?hh
class :fb:thing extends :x:element {
  protected function render() {
    return <div class="thing">thing</div>;
  }
}

此元素会被认为是魔法的,因为当您打印一个 <fb:thing /> 时,它实际上返回一个 div。

外部资源

以下是关于 XHP 的一些外部资源列表

许可证

此软件遵循 [MIT许可证](LICENSE)。