tobento / app-http
应用 http 支持。
Requires
- php: >=8.0
- laminas/laminas-httphandlerrunner: ^1.4
- nyholm/psr7: ^1.4
- nyholm/psr7-server: ^1.0
- psr/container: ^2.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- tobento/app: ^1.0
- tobento/app-migration: ^1.0
- tobento/service-config: ^1.0.3
- tobento/service-cookie: ^1.0.1
- tobento/service-error-handler: ^1.0
- tobento/service-middleware: ^1.0.2
- tobento/service-migration: ^1.0
- tobento/service-requester: ^1.0.2
- tobento/service-responser: ^1.0
- tobento/service-routing: ^1.0.5
- tobento/service-session: ^1.0.1
- tobento/service-translation: ^1.0.3
- tobento/service-uri: ^1.0.1
Requires (Dev)
- mockery/mockery: ^1.6
- phpunit/phpunit: ^9.5
- tobento/app-console: ^1.0.2
- tobento/app-encryption: ^1.0
- tobento/app-translation: ^1.0.1
- tobento/app-view: ^1.0.5
- tobento/service-collection: ^1.0
- tobento/service-form: ^1.0
- tobento/service-view: ^1.0
- vimeo/psalm: ^4.0
README
应用 http,路由,中间件和会话支持。
目录
入门
使用此命令添加运行此应用 http 项目的最新版本。
composer require tobento/app-http
要求
- PHP 8.0 或更高版本
文档
应用
如果您正在使用骨架,请查看 App 骨架。
您还可以查看 App 以了解更多关于应用的一般信息。
Http 引导
http 引导执行以下操作
- PSR-7 实现
- PSR-17 实现
- 安装和加载 http 配置文件
- 基本和当前 URI 实现
- http 错误处理实现
- 发出响应
use Tobento\App\AppFactory; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\Http\Boot\Http::class); // Run the app $app->run();
Http 配置
查看 app/config/http.php
以更改所需值。
请求和响应
您可以通过应用访问 PSR-7 和 PSR-17 接口
use Tobento\App\AppFactory; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UriFactoryInterface; use Psr\Http\Message\UriInterface; use Tobento\Service\Uri\BaseUriInterface; use Tobento\Service\Uri\CurrentUriInterface; use Tobento\Service\Uri\PreviousUriInterface; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\Http\Boot\Http::class); $app->booting(); // PSR-7 $request = $app->get(ServerRequestInterface::class); $response = $app->get(ResponseInterface::class); // returns UriInterface $baseUri = $app->get(BaseUriInterface::class); $currentUri = $app->get(CurrentUriInterface::class); $previousUri = $app->get(PreviousUriInterface::class); // Session Boot is needed, otherwise it is always same as base uri. // PSR-17 $responseFactory = $app->get(ResponseFactoryInterface::class); $streamFactory = $app->get(StreamFactoryInterface::class); $uploadedFileFactory = $app->get(UploadedFileFactoryInterface::class); $uriFactory = $app->get(UriFactoryInterface::class); // Run the app $app->run();
查看 Uri 服务 了解更多关于基本和当前 URI 的信息。
交换 PSR-7 和 PSR-17 实现
您可以将 PSR-7 和 PSR-17 实现交换为任何替代方案。
查看 App - 定制 了解更多信息。
请求者和响应者引导
请求者和响应者引导执行以下操作
- RequesterInterface 实现
- ResponserInterface 实现并添加其中间件
use Tobento\App\AppFactory; use Tobento\Service\Requester\RequesterInterface; use Tobento\Service\Responser\ResponserInterface; // Create the app $app = (new AppFactory())->createApp(); // You may add the session boot to enable // flash messages and flash input data. $app->boot(\Tobento\App\Http\Boot\Session::class); $app->boot(\Tobento\App\Http\Boot\RequesterResponser::class); $app->booting(); $requester = $app->get(RequesterInterface::class); $responser = $app->get(ResponserInterface); // Run the app $app->run();
查看 Requester 服务 了解更多信息。
查看 Responser 服务 了解更多信息。
响应者消息
消息 将在您安装了 App 消息 - 翻译消息 时被翻译。
添加的中间件
中间件引导
中间件引导执行以下操作
- PSR-15 HTTP 处理器(中间件)实现
- 分配中间件
use Tobento\App\AppFactory; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\Http\Boot\Middleware::class); $app->booting(); // add middleware aliases using app macro: $app->middlewareAliases([ 'alias' => FooMiddleware::class, ]); // add middleware group using app macro: $app->middlewareGroup(name: 'api', middlewares: [ Middleware::class, ]); // add middleware using app macro: $app->middleware(BarMiddleware::class); // Run the app $app->run();
查看 中间件服务 了解更多关于中间件实现的信息。
通过配置添加中间件
您可以在配置文件 app/config/middleware.php
中配置中间件,这些中间件将应用于所有路由和请求
return [ // ... 'middlewares' => [ // priority => middleware // via fully qualified class name: 8000 => \Tobento\App\Http\Middleware\SecurePolicyHeaders::class, // with build-in parameters: 7900 => [AnotherMiddleware::class, 'name' => 'Sam'], // by alias: 7800 => 'aliasedMiddleware', // by group name: 7800 => 'groupedMiddlewares', // by class instance: 7700 => new SomeMiddleware(), ], ];
通过引导添加中间件
您可能创建一个引导来添加中间件
use Tobento\App\Boot; use Tobento\App\Http\Boot\Middleware; class MyMiddlewareBoot extends Boot { public const BOOT = [ // you may ensure the middleware boot. Middleware::class, ]; public function boot(Middleware $middleware) { $middleware->add(MyMiddleware::class); } }
中间件别名
use Tobento\App\Boot; use Tobento\App\Http\Boot\Middleware; class MyMiddlewareBoot extends Boot { public const BOOT = [ // you may ensure the middleware boot. Middleware::class, ]; public function boot(Middleware $middleware) { $middleware->addAliases([ 'alias' => MyMiddleware::class, ]); // add by alias: $middleware->add('alias'); } }
通过配置添加别名
您可以在配置文件 app/config/middleware.php
中配置中间件别名。
return [ // ... 'aliases' => [ 'alias' => MyMiddleware::class, ], ];
中间件组
use Tobento\App\Boot; use Tobento\App\Http\Boot\Middleware; class MyMiddlewareBoot extends Boot { public const BOOT = [ // you may ensure the middleware boot. Middleware::class, ]; public function boot(Middleware $middleware) { $middleware->addGroup(name: 'api', middlewares: [ Middleware::class, // with build-in parameters: [AnotherMiddleware::class, 'name' => 'Sam'], // by alias: 'aliasedMiddleware', // by class instance: new SomeMiddleware(), ]); // add by group: $middleware->add('api'); } }
通过配置添加分组
您可以在配置文件 app/config/middleware.php
中配置中间件分组。
return [ // ... 'groups' => [ 'name' => [ SomeMiddleware::class, ], ], ];
可用中间件
前一个 URI 会话中间件
Tobento\App\Http\Middleware\PreviousUriSession::class
中间件由 会话启动 自动添加,用于在会话中存储uri历史记录。
获取上一个Uri
$previousUri = $app->get(PreviousUriInterface::class);
从上一个Uri历史记录中排除
您可以通过在响应中添加 X-Exclude-Previous-Uri
标头来从历史记录中排除某个uri。
$response = $response->withHeader('X-Exclude-Prev-Url', '1');
安全策略头中间件
此中间件将向响应添加以下安全策略标头
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Content-Security-Policy: base-uri 'none'; default-src 'self'; script-src 'nonce-***' 'self'; object-src 'none'; style-src 'nonce-***' 'self';
在 app/config/middleware.php
文件中
'middlewares' => [ 8000 => \Tobento\App\Http\Middleware\SecurePolicyHeaders::class, ],
使用内联脚本和样式
中间件将为 script-src
和 style-src
添加带有nonce的 Content-Security-Policy
标头。因此,要允许内联脚本或样式,您必须在html中添加nonce。
如果您使用的是 App View 包,您可以从视图中检索nonce。
<style nonce="<?= $view->esc($view->get('cspNonce', '')) ?>"> ... </style> <script nonce="<?= $view->esc($view->get('cspNonce', '')) ?>"> ... </script>
路由引导
路由启动执行以下操作
- 启动http和中间件启动
- RouterInterface 实现
- 添加路由宏
- 为路由异常添加http错误处理器
use Tobento\App\AppFactory; use Tobento\Service\Routing\RouterInterface; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\Http\Boot\Routing::class); $app->booting(); // using interface: $router = $app->get(RouterInterface::class); $router->get('blog', function() { return ['page' => 'blog']; }); // using macros: $app->route('GET', 'foo', function() { return ['page' => 'foo']; }); // Run the app $app->run();
查看 路由服务 了解更多关于路由的信息。
通过引导进行路由
您可能创建一个用于定义路由的启动器
use Tobento\App\Boot; use Tobento\Service\Routing\RouterInterface; use Tobento\Service\Routing\RouteGroupInterface; class RoutesBoot extends Boot { public const BOOT = [ // you may ensure the routing boot. \Tobento\App\Http\Boot\Routing::class, ]; public function boot(RouterInterface $router) { // Add routes on the router $router->get('blog', [Controller::class, 'method']); // Add routes with the provided app macros $this->app->route('GET', 'blog', [Controller::class, 'method']); $this->app->routeGroup('admin', function(RouteGroupInterface $group) { $group->get('blog/{id}', function($id) { // do something }); }); $this->app->routeResource('products', ProductsController::class); $this->app->routeMatched('blog.edit', function() { // do something after the route has been matched. }); $url = $this->app->routeUrl('blog.edit', ['id' => 5])->get(); } }
然后,将您的路由启动器添加到应用程序中
use Tobento\App\AppFactory; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(RoutesBoot::class); // Run the app $app->run();
域名路由
您可以在 app/config/http.php
文件中指定路由的域名。
查看 路由服务 - 域名路由 部分以了解更多关于域名路由的信息。
路由处理器
您可以为路由处理添加路由处理程序。
以下是添加处理程序的一些好处
- 您可以对响应进行缓存
- 在特定请求中,您可能抛出异常,稍后由错误处理器捕获(例如验证)
- 等等
首先,创建一个路由处理程序
use Tobento\App\AppInterface; use Tobento\Service\Routing\RouteInterface; use Tobento\Service\Routing\RouteHandlerInterface; use Tobento\App\Http\Routing\DeclaredHandlerParameters; use Tobento\App\Http\Routing\ArgumentsHandlerParameters; use Psr\Http\Message\ServerRequestInterface; final class CustomRouteHandler implements RouteHandlerInterface { public function __construct( private AppInterface $app ) {} /** * Handles the route. * * @param RouteInterface $route * @param null|ServerRequestInterface $request * @return mixed The return value of the handler called. */ public function handle(RouteInterface $route, null|ServerRequestInterface $request = null): mixed { // You may interfere with the arguments for the route handler to be called: $arguments = $route->getParameter('_arguments'); var_dump($arguments instanceof ArgumentsHandlerParameters); // bool(true) // You may use the declared route handler parameters: $declared = $route->getParameter('_declared'); var_dump($declared instanceof DeclaredHandlerParameters); // bool(true) // Let further handlers handle it: return [$route, $request]; // Or prevent further handlers from being called // and returning the route handler result: $result = $this->app->call($route->getHandler(), $arguments->getParameters()); return [$route, $request, $result]; } }
最后,添加路由处理程序
use Tobento\App\Http\Routing\RouteHandlerInterface; // Create the app $app = (new AppFactory())->createApp(); // Adding boots: $app->boot(\Tobento\App\Http\Boot\Routing::class); // Add route handler using the app on method: $app->on(RouteHandlerInterface::class, function(RouteHandlerInterface $handler) { $handler->addHandler(CustomRouteHandler::class); })->priority(1500); // Run the app $app->run();
唯一添加的处理程序是具有 1000
优先级的 Tobento\App\Http\Routing\RequestRouteHandler::class
。
路由列表命令
如果您已安装 App Console,您可以通过提供应用程序定义的所有路由的概览来运行 route:list 命令。
php ap route:list
仅显示具有附加信息的特定名称的特定路由
php ap route:list --name=blog.show
会话引导
会话启动执行以下操作
- SessionInterface 实现
- 添加会话中间件
use Tobento\App\AppFactory; use Tobento\Service\Session\SessionInterface; use Psr\Http\Message\ServerRequestInterface; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\Http\Boot\Middleware::class); $app->boot(\Tobento\App\Http\Boot\Routing::class); $app->boot(\Tobento\App\Http\Boot\Session::class); $app->booting(); $app->route('GET', 'foo', function(SessionInterface $session) { $session->set('key', 'value'); return ['page' => 'foo']; }); // or you may get the session from the request attributes: $app->route('GET', 'bar', function(ServerRequestInterface $request) { $session = $request->getAttribute(SessionInterface::class); $session->set('key', 'value'); return ['page' => 'bar']; }); // Run the app $app->run();
查看 会话服务 了解更多关于会话的一般信息。
会话配置
查看 app/config/session.php
修改所需的值。
会话生命周期
会话由会话中间件启动和保存,通过会话中间件与会话数据的交互之后可用。
会话错误处理
您可以为会话中间件引起的异常添加错误处理器。
use Tobento\App\Boot; use Tobento\App\Http\HttpErrorHandlersInterface; use Tobento\Service\Session\SessionStartException; use Tobento\Service\Session\SessionExpiredException; use Tobento\Service\Session\SessionValidationException; use Tobento\Service\Session\SessionSaveException; use Throwable; class HttpErrorHandlerBoot extends Boot { public const BOOT = [ // you may ensure the http boot. \Tobento\App\Http\Boot\Http::class, ]; public function boot() { $this->app->on(HttpErrorHandlersInterface::class, function(HttpErrorHandlersInterface $handlers) { $handlers->add(function(Throwable $t) { if ($t instanceof SessionStartException) { // You may do something if starting session fails. } elseif ($t instanceof SessionExpiredException) { // This is already handled by the session middleware, // so you might check it out. } elseif ($t instanceof SessionValidationException) { // You may do something if session validation fails. } elseif ($t instanceof SessionSaveException) { // You may do something if saving session fails. } return $t; })->priority(2000); // you might add a priority. }); } }
您可以使用 错误处理器 - 处理其他异常 替代处理这些异常。
查看 可抛出处理器 了解更多关于处理器的一般信息。
Cookie 引导
cookies启动执行以下操作
- 基于cookies配置文件实现 cookie接口
- 根据cookies配置文件添加中间件
use Tobento\App\AppFactory; use Tobento\Service\Cookie\CookiesFactoryInterface; use Tobento\Service\Cookie\CookieFactoryInterface; use Tobento\Service\Cookie\CookieValuesFactoryInterface; use Tobento\Service\Cookie\CookiesProcessorInterface; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\Http\Boot\Cookies::class); $app->booting(); // The following interfaces are available after booting: $cookiesFactory = $app->get(CookiesFactoryInterface::class); $cookieFactory = $app->get(CookieFactoryInterface::class); $cookieValuesFactory = $app->get(CookieValuesFactoryInterface::class); $cookiesProcessor = $app->get(CookiesProcessorInterface::class); // Run the app $app->run();
查看Cookie 服务以了解更多信息。
Cookie 配置
查看app/config/cookies.php
以更改所需的值。
Cookie 使用
读取和写入 cookies
use Tobento\App\AppFactory; use Tobento\Service\Cookie\CookieValuesInterface; use Tobento\Service\Cookie\CookiesInterface; use Psr\Http\Message\ServerRequestInterface; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\Http\Boot\Routing::class); $app->boot(\Tobento\App\Http\Boot\Cookies::class); $app->booting(); $app->route('GET', 'bar', function(ServerRequestInterface $request) { // read cookies: $cookieValues = $request->getAttribute(CookieValuesInterface::class); $value = $cookieValues->get('foo'); // or var_dump($request->getCookieParams()); // write cookies: $cookies = $request->getAttribute(CookiesInterface::class); $cookies->add('name', 'value'); return ['page' => 'bar']; }); // Run the app $app->run();
查看Cookie 值以了解更多信息。
查看Cookies以了解更多信息。
Cookie 加密
首先安装 app-encryption 包
composer require tobento/app-encryption
然后,如果您想加密和解密所有 cookies 值,只需启动 Encryption::class
。这就完成了。
// ... $app->boot(\Tobento\App\Encryption\Boot\Encryption::class); $app->boot(\Tobento\App\Http\Boot\Cookies::class); // ...
白名单 cookie
要在启动后启用白名单(禁用加密)cookie,请使用 CookiesProcessorInterface::class
use Tobento\Service\Cookie\CookiesProcessorInterface; $cookiesProcessor = $app->get(CookiesProcessorInterface::class); $cookiesProcessor->whitelistCookie(name: 'name'); // or $cookiesProcessor->whitelistCookie(name: 'name[foo]'); $cookiesProcessor->whitelistCookie(name: 'name[bar]');
配置
加密和解密是通过在 app/config/cookies.php
文件中指定的中间件处理实现的 CookiesProcessor::class
来完成的。
use Tobento\Service\Cookie; use Tobento\Service\Encryption; use Psr\Container\ContainerInterface; return [ 'middlewares' => [ Cookie\Middleware\Cookies::class, ], 'interfaces' => [ //... Cookie\CookiesProcessorInterface::class => Cookie\CookiesProcessor::class, // or you may use a specified encrypter only for cookies: Cookie\CookiesProcessorInterface::class => static function(ContainerInterface $c): Cookie\CookiesProcessorInterface { $encrypter = null; if ( $c->has(Encryption\EncryptersInterface::class) && $c->get(Encryption\EncryptersInterface::class)->has('cookies') ) { $encrypter = $c->get(Encryption\EncryptersInterface::class)->get('cookies'); } return new Cookie\CookiesProcessor( encrypter: $encrypter, whitelistedCookies: [], ); }, ], //... ];
您可以查看App 加密以了解更多信息。
错误处理器引导
默认情况下,错误处理器将以 json 或纯文本格式渲染异常。如果您想渲染支持 html 和 xml 格式的异常视图,请查看渲染异常视图部分。
// ... $app->boot(\Tobento\App\Http\Boot\ErrorHandler::class); // ...
它处理以下异常以及Http 异常
Http 异常
您可以从控制器和中间件抛出几个 HTTP 异常,这些异常将由默认错误处理器处理。
示例
use Tobento\App\Http\Exception\HttpException; use Tobento\App\Http\Exception\NotFoundException; class SomeController { public function index() { throw new HttpException(statusCode: 404); // or: throw new NotFoundException(); } }
渲染异常视图
为了以 html 或 xml 格式渲染异常,必须在应用中提供 ViewInterface::class
。您可以安装App View 包,或者仅实现 ViewInterface::class
composer require tobento/service-view
use Tobento\Service\View; use Tobento\Service\Dir\Dirs; use Tobento\Service\Dir\Dir; // ... $app->set(View\ViewInterface::class, function() { return new View\View( new View\PhpRenderer( new Dirs( new Dir('home/private/views/'), ) ), new View\Data(), new View\Assets('home/public/src/', 'https://www.example.com/src/') ); }); // ...
如果存在,它将渲染以下视图
处理其他异常
您可以通过扩展错误处理器来处理其他异常
如果您已安装App Translation并使用*
作为资源名称,则消息将被翻译。查看翻译资源和翻译文件资源以了解更多信息。
use Tobento\App\Http\Boot\ErrorHandler; use Tobento\Service\Requester\RequesterInterface; use Tobento\Service\Responser\ResponserInterface; use Psr\Http\Message\ResponseInterface; use Throwable; class CustomErrorHandler extends ErrorHandler { public function handleThrowable(Throwable $t): Throwable|ResponseInterface { $requester = $this->app->get(RequesterInterface::class); if ($t instanceof SomeException) { return $requester->wantsJson() ? $this->renderJson(code: 404) : $this->renderView(code: 404); // or with custom message: return $requester->wantsJson() ? $this->renderJson(code: 404, message: 'Custom') : $this->renderView(code: 404, message: 'Custom'); // or with custom message and message parameters: return $requester->wantsJson() ? $this->renderJson(code: 404, message: 'Custom :value', parameters: [':value' => 'foo']) : $this->renderView(code: 404, message: 'Custom :value', parameters: [':value' => 'foo']); } // using the responser: if ($t instanceof SomeOtherException) { $responser = $this->app->get(ResponserInterface::class); return $responser->json( data: ['key' => 'value'], code: 200, ); } return parent::handleThrowable($t); } }
然后启动您自定义的错误处理器而不是默认的错误处理器
// ... $app->boot(CustomErrorHandler::class); // ...
优先错误处理器
您可以创建一个错误处理器并使用 HANDLER_PRIORITY
常量来定义优先级。
默认优先级为 1500
,优先级越高,处理越早。
use Tobento\App\Http\Boot\ErrorHandler; use Tobento\Service\Requester\RequesterInterface; use Tobento\Service\Responser\ResponserInterface; use Psr\Http\Message\ResponseInterface; use Throwable; class PrioritizedErrorHandler extends ErrorHandler { protected const HANDLER_PRIORITY = 5000; public function handleThrowable(Throwable $t): Throwable|ResponseInterface { $requester = $this->app->get(RequesterInterface::class); if ($t instanceof SomeException) { return $requester->wantsJson() ? $this->renderJson(code: 404) : $this->renderView(code: 404); } // using the responser: if ($t instanceof SomeOtherException) { $responser = $this->app->get(ResponserInterface::class); return $responser->json( data: ['key' => 'value'], code: 200, ); } // return throwable to let other handler handle it: return $t; } }
然后启动您错误处理器
// ... $app->boot(PrioritizedErrorHandler::class); $app->boot(DefaultErrorHandler::class); // you could boot it after the default, // it gets called first if priority is higher as default: $app->boot(PrioritizedErrorHandler::class); // ...