jschreuder / middle
基于其他组件构建的中间件微框架
Requires
- php: >=8.1
- ext-json: *
- psr/http-factory: ^1.0
- psr/http-message: ^1.0
- psr/http-server-middleware: ^1.0
- psr/log: ^2.0 || ^3.0
Requires (Dev)
- friends-of-phpspec/phpspec-code-coverage: ^6.1
- laminas/laminas-session: ^2.12
- phpspec/phpspec: ^7.2
- symfony/routing: ^6.0
- twig/twig: ^3.0
Suggests
- laminas/laminas-session: Required when using the LaminasSession implementation
- symfony/routing: Required when using the SymfonyRouter implementation
- twig/twig: Required when using the TwigRenderer view-engine implementation
README
一个围绕中间件概念的微框架,基本上:中间件一切。这意味着什么?一切都是基于简单的接口,默认实现可以被替换或装饰。实现可以是原子的:只执行一个任务。通过选择要装饰或添加到堆栈中的简单中间件来组合复杂的功能。此外,每个组件都是 NIH;PSR-1、PSR-2、PSR-3、PSR-4、PSR-7、PSR-15 和 PSR-17;自 2.0 版本起针对 PHP 8.1。
查看 Middle 骨架 应用程序以快速运行示例设置。
注意:所有示例都使用 Laminas Diactoros,但任何 PSR-7 兼容库也可以正常工作。
运行 Middle 应用程序
使用 Middle 构建的应用程序的核心是 ApplicationStackInterface
,它仅接受 PSR ServerRequestInterface
并必须返回一个 ResponseInterface
。运行它,在设置完成后,如下所示(使用 Laminas Diactoros 作为 PSR-7 实现)
<?php // Create Request $request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(); // Render the response by processing the request $response = $app->process($request); // And output it (new Laminas\Diactoros\Response\SapiEmitter())->emit($response);
最小默认设置
以下设置应用程序,以路由中间件和控制器运行器为核心。中间件以 LIFO(后进先出)顺序处理。
<?php use jschreuder\Middle; // Let's setup a router which needs the base-URL, and a handler for // generating a response when no route is matched. $router = new Middle\Router\SymfonyRouter('https://'); $fallbackController = Middle\Controller\CallableController::fromCallable( function () { return new Laminas\Diactoros\Response\JsonResponse(['error'], 400); } ); // Setup the application with controller runner and the routing middleware $app = new Middle\ApplicationStack( new Middle\Controller\ControllerRunner(), new Middle\ServerMiddleware\RoutingMiddleware($router, $fallbackController) );
有了这个设置,我们现在可以添加一些路由(使用上面的 $router
)
<?php use jschreuder\Middle; // Using the convenience method for GET request on '/' $router->get('home', '/', Middle\Controller\CallableController::factoryFromCallable(function () { return new Laminas\Diactoros\Response\JsonResponse([ 'message' => 'Welcome to our homepage', ]); }) );
包含的路由依赖于 Symfony 的路由组件。在路径中,您可以使用变量表示法。get()
方法还支持 2 个额外的参数:传递给 Symfony 路由的 $defaults
数组和 $requirements
数组。
向堆栈添加更多中间件
以下示例构建了一个应用程序,它使用 2 个额外的中间件,在先前的示例之上添加了会话和错误处理。
<?php use jschreuder\Middle; // starting with the example above, let's add these before running the app. // Now let's also make sessions available on the request $app = $app->withMiddleware( new Middle\ServerMiddleware\SessionMiddleware( new Middle\Session\LaminasSessionProcessor() ) ); // And finally: make sure any errors are caught $app = $app->withMiddleware( new Middle\ServerMiddleware\ErrorHandlerMiddleware( new Monolog\Logger(...), function (Psr\Http\Message\ServerRequestInterface $request, \Throwable $exception) { return new Laminas\Diactoros\Response\JsonResponse(['error'], 500); } ) );
会话中间件在 ServerRequest 的属性中添加一个 'session'
属性,其中包含 jschreuder\Middle\Session\SessionInterface
的实例。
错误处理器接受一个 PSR-3 LoggerInterface
实例,它将记录任何未捕获的异常作为 alert
。构造函数中的可调用对象将在之后直接调用,并期望返回一个显示错误给用户的 ResponseInterface
。
也支持模板
还有一个内置的通用模板解决方案。要使用它,控制器可以创建一个中间的 ViewInterface
实例,并接受一个 RendererInterface
实例,将其渲染到响应对象中。
以下示例使用内置的 Twig 渲染器
<?php use jschreuder\Middle; // Setup the renderer for Twig with a Twig_Environment instance and a // PSR-17 Response factory for generating the Response object $renderer = new Middle\View\TwigRenderer( new Twig\Environment(...), $responseFactory ); $router->get('home', '/', Middle\Controller\CallableViewController::factoryFromCallable( function (Psr\Http\Message\ServerRequestInterface $request) use ($renderer) { // Should render template.twig and parameters with Twig and return // response with status code 200 return $renderer->render($request, new Middle\View\View('template.twig', [ 'view' => 'parameters', ], 200)); } ); );
RendererInterface
可以被装饰。如果您想使用视图返回重定向,您可以像这样装饰渲染器
<?php use jschreuder\Middle; // Decorate with the RedirectRendererMiddleware which needs a PSR-17 // Response factory for generating the Response object $renderer = new Middle\View\RedirectRendererMiddleware( $renderer, $responseFactory );
完成之后,您可以创建这样的重定向
<?php use jschreuder\Middle; $router->get('redirect.example', '/redirect/to/home', Middle\Controller\CallableViewController::factoryFromCallable( function (Psr\Http\Message\ServerRequestInterface $request) use ($renderer) { // This will redirect to the path '/' with status 302, the status is // optional and will default to 302 when omitted. return $renderer->render($request, new Middle\View\RedirectView('/', 302)); } ); );
中间件和依赖注入容器
以下示例使用 Pimple,但可能在其他容器中也可以使用相同的概念
<?php use jschreuder\Middle; // First create the central app object in the container $container = Pimple\Container(); $container['app'] = function () { return new Middle\ApplicationStack( new Middle\Controller\ControllerRunner() ); }; // Now to add a middleware you can do this $container->extend('app', function (Middle\ApplicationStack $app, Pimple\Container $container) { return $app->withMiddleware( new Middle\ServerMiddleware\RoutingMiddleware( $container['router'], $container['fallbackHandler'] ) ); } );
当在多个地方这样做时,例如通过服务提供者,顺序可能不那么明确,因此请注意添加中间件的顺序。
包含的服务
有一些包含的服务,它们都有自己的默认实现,可以根据需要替换或装饰
SessionProcessorInterface
与其默认选项取决于laminas/laminas-session
。它允许设置和获取值、销毁会话或轮换其 ID。如上所示,可以通过SessionMiddleware
加载LaminasSessionProcessor
。RouterInterface
与 Symfony 路由组件的默认设置相关。它通过上面的RoutingMiddleware
加载。它有添加常用 HTTP 方法、解析请求以及获取 URL 生成器以便反向路由的方法。相关接口包括RouteMatchInterface
、UrlGeneratorInterface
和RoutingProviderInterface
。RendererInterface
与其默认设置相关,依赖于 Twig 来渲染模板,如上面所示。您也可以将其包装在其他 Middlewares 中进行额外的解析或完全替换。相关的ViewInterface
预期会被提供,并且包含渲染模板所需的信息。
问题及答案
- 为什么又是一个微型框架... 我曾使用 Silex 创建应用程序,但它却给我带来了麻烦。我还更倾向于使用 PSR-7 而不是 Symfony 的实现。我开始重构它,并用其路由组件、Twig 以及 Laminas 的 Diactoros 和 Session 库来替换它。过了一段时间后,我意识到我实际上创建了一个微型框架,并将其从我的应用程序中提取出来。
- 为什么所有类都是最终的? 目的是遵循 SOLID 原则中的 开闭原则。这意味着对扩展开放,对修改封闭。每个依赖都通过接口类型提示,而不是针对任何具体实现。所有类都可以通过中间件进行扩展,无论是像 ApplicationStack 那样,还是通过使用 装饰器模式。因此,您可以扩展或替换任何类,但不能修改它们的内部工作方式。因此,只有接口是框架 API 的组成部分。
- 我必须使用 Twig、Symfony 的路由或 Laminas 的 Session 库吗? 不,但只包含了一些基本组件。提供的是使用这些包实现的。您可以通过使用其他库实现 Routing 或 Session 接口来轻松替换它们。