包 / 框架
最小化视图框架
Requires
- php: >=7.2
- psr/container: ^1.0 || ^2.0
Requires (Dev)
- ext-pdo: *
- nikic/fast-route: ^1.3
- paket/bero: ^0.3.0
Suggests
- nikic/fast-route: For FastRouter
- paket/bero: For ContainerInterface
README
Fram(前缀为Swedish)是一个PHP视图框架。Fram的目标是适应所有地方,新项目、现有项目,与其他框架并排使用,因此Fram是一个灵活的框架,可以适应许多场景。
用法
final class HelloWorldView implements HtmlView
{
public function render(Route $route)
{
?>
<!DOCTYPE html>
<html>
<head>
<title>Hello, World!</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
<?php
}
}
$container = new BeroContainer(new StrictBero()); // any PSR-11 ContainerInterface
$router = new FastRouter(simpleDispatcher(function (RouteCollector $r) {
$r->addRoute('GET', '/', HelloWorldView::class);
}));
$fram = new Fram($container, $router, HtmlViewHandler::class);
$fram->run(function (Route $route, ?Throwable $throwable) {
if (isset($throwable)) {
return $route->withViewClass(ExceptionView::class, $throwable);
}
if ($route->hasEmptyView()) {
return $route->withViewClass(NotFoundView::class);
}
return $route;
});
安装
composer require paket/fram
需求
需要PHP 7.2或更高版本。
示例
见 /examples
注意:示例仅应作为示例使用,不应作为库代码。
运行示例
php -S localhost:8888 -t examples/www
通用
Fram仅是一个视图框架,基于路由可以渲染视图。像数据库处理、认证、模板渲染或日志记录等必要项目功能不属于Fram的范围。
Fram的设计基于几个核心接口,Fram包含这些接口的几个实现,但用户需要根据项目需求进行自定义。每个核心接口都设计得尽可能小,使实现变得简单,因此扩展Fram以满足项目需求应该在几分钟内完成。
核心部分
视图
在Fram中,视图既是控制器也是视图。要创建视图,您需要实现特定的视图接口,因此一个类只有一个视图。应使用命名空间对相似视图进行分组。
框架中包含的视图接口
默认视图HTML视图JSON视图
所有视图类型都实现了空的视图接口。
通过使用不同的视图类型,框架可以根据每个视图类型调整其执行。项目可以创建任意数量的视图接口以适应不同的场景。
如何渲染视图,方法名称及其参数由每个视图类型决定,因此不同的视图类型可以提供不同的输入,例如请求和响应对象。
空视图
空视图是一个特殊视图,表示null 视图,即当前请求没有匹配的视图。
视图处理器
视图处理器执行视图类型。视图处理器中典型的事情包括设置头部、专门的日志记录、处理缓冲区或执行中间件管道。视图处理器负责向视图提供视图类型强制的输入。通过切换到另一个视图处理器,可以更改视图的行为。
框架中包含的视图处理器
默认视图处理器HTML视图处理器JSON视图处理器
路由
路由通过Fram的每个部分传递。路由知道HTTP方法、URI以及当前的视图类。一个路由是不可变的,更改当前视图类的唯一方法是克隆路由,这通过调用withViewClass()来完成。通过更改视图类,将返回一个新的路由,然后可以将其返回到上游以在Fram内实现内部重定向。Fram将重定向,直到路由不再更改。请注意,路由跟踪当前视图类的名称,而不是类的实例本身。
路由器
路由器将HTTP方法和HTTP URI与一个视图类匹配,并返回一个设置有该视图类的路由。
框架中包含的路由器
SimpleRouter- 简单字符串匹配方法和对URIFastRouter- 使用FastRoute进行路由
容器
要实例化类 Fram,它使用 PSR-11 ContainerInterface。
建议的实现和在所有文档和示例中使用的都是 BeroContainer,BeroContainer 是 Bero 项目的组成部分。
Fram
Fram 是一个基本将 Route 通过框架泵送的动力引擎。
通过在 Fram 上调用 run(),框架开始一个请求。通过提供一个回调,run() 被调用,这个回调使项目能够对每个 Route 变化进行必要的控制,并可以批准或更改 Route。
run() 回调是一个强大的机制,它使 Fram 能够更好地与现有代码或其他框架集成。通过检查每个 Route 变化,回调可以根据需要重定向或中止。典型场景包括调试、根据环境执行不同的路径、回退到旧代码和授权检查。
404 页面的管理是通过 run() 回调来实现的。Fram 没有关于如何处理 EmptyView 的情况的知识,但 run() 回调可以通过内部重定向到正确的 View 来指导 Fram 如何处理。
Fram 还会捕获在 Router 或 ViewHandler 以及 View 中可能发生的异常。异常作为可选的 Throwable 第二个参数传递给 run() 回调,回调可以如果需要的话通过新的 Route 进行重定向。Fram 不会捕获在 run() 回调中发生的异常,这些异常应该在 run() 之外捕获。LogicExcpeption 总是会停止 Fram。
如果 run() 回调返回 null,Fram 的执行将停止。
Fram 的流程
- 使用方法和 uri 执行
Router Router返回一个设置了View类的新Route- 使用
Route和可选的Throwable(如果发生异常)调用run()回调 run()回调返回相同的Route、一个新的Route或null以取消- 将
Route的View类与已注册的视图处理器相匹配 - 通过使用
ContainerInterface从View类名称实例化View - 使用
Route和View执行匹配的ViewHandler - 如果从
ViewHandler返回的Route发生变化或在异常发生时调用 3
错误处理
Fram 不注册任何异常、错误或关闭处理器。这是一些需要在 Fram 之外配置的事情。
自定义
大多数自定义应该通过实现自己的 ViewHandler 来完成。视图处理器是一个进行用户身份验证/授权、缓冲输出、响应头管理、自定义日志记录、PSR-7 请求和响应对象或调用 PSR-15 中间件的好地方。
常见问题解答
- 我的视图类是否应该直接实现
View接口?- 不,顶层的
View接口是一个抽象接口,永远不应该实现。视图接口用作类型来表示所有视图接口。你应该实现一个视图接口,它反过来扩展顶层View接口。
- 不,顶层的
- 视图接口可以扩展另一个视图接口吗?
- 只有当其他接口是抽象接口,例如顶层的
View接口时。假设我们有一个扩展View接口的视图接口A,那么B接口扩展A接口。A和B都有自己的视图处理器,因此A和B的视图实现可以被渲染。然而,B的视图类可以匹配为A和B注册的视图处理器,这可能导致意想不到的结果,并且 Fram 无法处理这种情况。
- 只有当其他接口是抽象接口,例如顶层的
- 如何实现 CSRF 令牌验证?
- 实现一个视图处理器,检查所有传入的表单,并验证表单数据中提供的令牌。查看示例文件夹中的工作示例,特别是
FormBackendHandler类。
- 实现一个视图处理器,检查所有传入的表单,并验证表单数据中提供的令牌。查看示例文件夹中的工作示例,特别是
- 如何实现输出缓冲?
- 实现一个视图处理器,在渲染视图之前开始输出缓冲,然后根据条件(例如,如果我们从视图返回新的路由,我们清理我们的输出缓冲,如果没有,我们刷新它)刷新(发送)或清理(丢弃)输出缓冲。
final class BufferedHtmlHandler implements ViewHandler { public function handle(Route $route, View $view): Route { ob_start(); /** @var $view HtmlView */ $newRoute = $view->render($route); if ($newRoute !== null && $newRoute !== $route) { ob_end_clean(); return $newRoute; } ob_end_flush(); return $route; } public function getViewInterface(): string { return HtmlView::class; } }
- 实现一个视图处理器,在渲染视图之前开始输出缓冲,然后根据条件(例如,如果我们从视图返回新的路由,我们清理我们的输出缓冲,如果没有,我们刷新它)刷新(发送)或清理(丢弃)输出缓冲。
- 如何根据请求头进行路由?
- 基于请求头,例如
Accept或自定义的X-Version,你可以通过实现一个包装器Router来选择不同的路由器,例如。final class RequestHeaderRouter implements Router { /** @var Router */ private $first; /** @var Router */ private $second; public function __construct(Router $first, Router $second) { $this->first = $first; $this->second = $second; } public function route(string $method, string $uri): Route { if (/* check request header for condition to be true */) { return $this->first->route($method, $uri); } return $this->second->route($method, $uri); } }
- 基于请求头,例如
许可证
Fram 在 MIT 许可证下发布。请参阅捆绑文件 LICENSE.txt。