/框架

最小化视图框架

0.8.1 2021-09-13 11:26 UTC

This package is auto-updated.

Last update: 2024-09-09 14:56:08 UTC


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 - 简单字符串匹配方法和对URI
  • FastRouter - 使用FastRoute进行路由

容器

要实例化类 Fram,它使用 PSR-11 ContainerInterface

建议的实现和在所有文档和示例中使用的都是 BeroContainerBeroContainerBero 项目的组成部分。

Fram

Fram 是一个基本将 Route 通过框架泵送的动力引擎。

通过在 Fram 上调用 run(),框架开始一个请求。通过提供一个回调,run() 被调用,这个回调使项目能够对每个 Route 变化进行必要的控制,并可以批准或更改 Route

run() 回调是一个强大的机制,它使 Fram 能够更好地与现有代码或其他框架集成。通过检查每个 Route 变化,回调可以根据需要重定向或中止。典型场景包括调试、根据环境执行不同的路径、回退到旧代码和授权检查。

404 页面的管理是通过 run() 回调来实现的。Fram 没有关于如何处理 EmptyView 的情况的知识,但 run() 回调可以通过内部重定向到正确的 View 来指导 Fram 如何处理。

Fram 还会捕获在 RouterViewHandler 以及 View 中可能发生的异常。异常作为可选的 Throwable 第二个参数传递给 run() 回调,回调可以如果需要的话通过新的 Route 进行重定向。Fram 不会捕获在 run() 回调中发生的异常,这些异常应该在 run() 之外捕获。LogicExcpeption 总是会停止 Fram。

如果 run() 回调返回 null,Fram 的执行将停止。

Fram 的流程
  1. 使用方法和 uri 执行 Router
  2. Router 返回一个设置了 View 类的新 Route
  3. 使用 Route 和可选的 Throwable(如果发生异常)调用 run() 回调
  4. run() 回调返回相同的 Route、一个新的 Routenull 以取消
  5. RouteView 类与已注册的视图处理器相匹配
  6. 通过使用 ContainerInterfaceView 类名称实例化 View
  7. 使用 RouteView 执行匹配的 ViewHandler
  8. 如果从 ViewHandler 返回的 Route 发生变化或在异常发生时调用 3

错误处理

Fram 不注册任何异常、错误或关闭处理器。这是一些需要在 Fram 之外配置的事情。

自定义

大多数自定义应该通过实现自己的 ViewHandler 来完成。视图处理器是一个进行用户身份验证/授权、缓冲输出、响应头管理、自定义日志记录、PSR-7 请求和响应对象或调用 PSR-15 中间件的好地方。

常见问题解答

  • 我的视图类是否应该直接实现 View 接口?
    • 不,顶层的 View 接口是一个抽象接口,永远不应该实现。视图接口用作类型来表示所有视图接口。你应该实现一个视图接口,它反过来扩展顶层 View 接口。
  • 视图接口可以扩展另一个视图接口吗?
    • 只有当其他接口是抽象接口,例如顶层的 View 接口时。假设我们有一个扩展 View 接口的视图接口 A,那么 B 接口扩展 A 接口。AB 都有自己的视图处理器,因此 AB 的视图实现可以被渲染。然而,B 的视图类可以匹配为 AB 注册的视图处理器,这可能导致意想不到的结果,并且 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。