hershel-theodore-layton / lecof-router
一个无需构建步骤的 Hack 和 HHVM 请求路由器。
Requires
- hhvm: ^4.102
- hershel-theodore-layton/lecof-router-interfaces: ^1.0-RC1
Requires (Dev)
- facebook/fbexpect: ^2.8
- hhvm/hacktest: ^2.3
- hhvm/hhast: ^4.102
- hhvm/hhvm-autoload: ^3.2
This package is auto-updated.
Last update: 2024-09-26 18:47:37 UTC
README
一个无需构建步骤的 Hack 和 HHVM 请求路由器。
L.E.C.O.F. 路由器
- L. 懒加载
- E. 评估
- C. 组合
- O. 的
- F. 过滤器
发音为 "Lack of router"
为什么选择 Lecof 路由器?
这个路由器通过使用懒加载来避免性能原语构建步骤的需要。对于具有许多端点的复杂应用程序,构建完整路由器可能比实际路由本身需要更长的时间。Codegen 通过最小化每个请求需要进行的发现量来解决这个问题。这是以服务器上的内存和 CPU 为代价换取良好的开发者体验。我迫切希望重新获得添加路由而无需延迟或需要记住运行 codegen 的开发者体验。这个小巧的包只包含足够的基本元素,允许您通过嵌套函数调用的树来表达您的路由需求,这些调用在每次请求时执行,同时给您提供使其快速的手段。任何子树都可以通过调用 Lecof\lazy(function_returning_my_subtree<>)
来替换,它将子树的构建推迟到需要的时候。如果它从未被需要,就不会被构建。
使用和扩展 Lecof 路由器的准备
为了开始使用 lecof-router,您需要提供自己的类并实现一个接口。
一个 RequestInfo
实现是绝对必需的。这个类知道如何在您的当前 HHVM 版本中获取请求信息。这个类有意不提供,因为这可能会在不久的将来发生变化。这也是主要的自定义点。如果您需要根据 IP、cookie、URL 参数或其他内容进行路由,您可以将它们添加到您的 RequestInfo
,并通过 ->getByType<T>()
API 访问这些信息。
此点之后的一切都是严格可选的。您的基本使用可跳转到 使用
如果您希望将路由变量注入到您的 RouteResult
中,您必须提供 ParsedVariable
的实现。您可以使用 reify
只使用一个类,或者使用多个类,每个类针对不同的类型进行专门化。
如果您希望从您的 RouteResult
中解析路由变量,您必须创建一个 VariableParser
的实现,该实现知道如何将原始文本转换为您的首选 ParsedVariable
。
最后但同样重要的是,这个库旨在扩展。如果内置的 Filter
类不能满足您的需求,您可以用几行代码编写自己的。也许您有很多端点应该被路由到而没有文件扩展名,如 /about
,但它们需要与扩展一起工作以保持向后兼容性。您可以实现自己的
function literal_with_optional_extension<T as nonnull>( string $literal, string $extension, LecofInterfaces\Filter<T> $next, )[]: LecofInterfaces\Filter<T> { return new LiteralWithOptionalExtension($literal, $extension, $next); } final class LiteralWithOptionalExtension<T as nonnull> implements LecofInterfaces\Filter<T> { public function __construct( private string $literal, private string $extension, private LecofInterfaces\Filter<T> $next, )[] {} public function filter( LecofInterfaces\RequestInfo $request_info, int $index, )[]: ?LecofInterfaces\RouteResult<T> { $segment = $request_info->getPathSegment($index); if ( $segment !== $this->literal && $segment !== $this->literal.$this->extension ) { return null; } return $this->next->filter($request_info, $index + 1); } }
使用
此示例展示了 Lecof 路由器可以做什么。入口点可能是最接近最小启动器的。您可能决定更改 MyEntryPointType
的签名以更好地满足您的需求。在从未路由的 __EntryPoint
应用程序迁移时,使用 (function(): Awaitable<void>)
并将其作为路由目标和 __EntryPoint
目标双重用途可能是有益的。您必须将解析信息放入静态变量中(就像使用 HH\\global_get()
一样)。
type RequestVariables = dict<string, LecofInterfaces\ParsedVariable<mixed>>; type MyEntryPointType = (function(RequestVariables): Awaitable<void>); type MyFilterType = LecofInterfaces\Filter<MyEntryPointType>; <<__EntryPoint>> async function my_web_entry_point_async(): Awaitable<void> { $request_info = get_my_request_info_from_globals(); $router = Lecof\merge( // Index is very likely, so let's match it first. Lecof\done(web_index_async<>), // Only construct the /api subtree if we need it. Lecof\literal('api', Lecof\lazy(api_routes<>)), // Static resource not found, we can short circuit here. Lecof\literal('static', Lecof\ignore_trailing_path(Lecof\done( four_oh_four_async<>, ))), Lecof\lazy(web_routes<>), ); $default = tuple(four_oh_four_async<>, vec[]); list($route, $variables) = $router->filter($request_info, 0) ?? $default; await $route(Dict\from_values($variables, $v ==> $v->getName())); } // Some other file function api_routes()[]: MyFilterType { return Lecof\literals(dict[ 'user' => Lecof\merge( Lecof\literal('me', Lecof\done(api_response_current_user_async<>)), Lecof\parse_variable( new IntegerParser('user_id'), Lecof\done(api_response_user_by_id_async<>), ), ), 'users' => Lecof\merge( Lecof\inject_variable( new GenericParsedVariable<int>('page_number', '1', 1), Lecof\done(api_response_users_on_page_async<>), ), Lecof\parse_variable( new IntegerParser('page_number'), Lecof\done(api_response_users_on_page_async<>), ), ), // ... ]); } function web_routes()[]: MyFilterType { return Lecof\merge( Lecof\slashed_literals(dict[ 'about' => Lecof\done(web_about_async<>), 'legal/privacy' => Lecof\done(web_privacy_async<>), ]), // ... ); } // Another file final class IntegerParser implements LecofInterfaces\VariableParser<int> { public function __construct(private string $name)[] {} public function canParse(string $raw)[]: bool { return Str\to_int($raw) is nonnull; } public function parse(string $raw)[]: LecofInterfaces\ParsedVariable<int> { return new GenericParsedVariable<int>( $this->name, $raw, Str\to_int($raw) as nonnull, ); } } final class GenericParsedVariable<reify T> implements LecofInterfaces\ParsedVariable<T> { public function __construct( private string $name, private string $raw, private T $value, )[] {} public function getName()[]: string { return $this->name; } public function getRawValue()[]: string { return $this->raw; } public function getValue()[]: T { return $this->value; } }