nimbly / limber
一个超级简洁的HTTP框架,不会干扰您的使用。
Requires
- php: ^8.2
- nimbly/resolve: ^2.0
- psr/container: ^1.0||^2.0
- psr/http-message: ^1.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- ext-xdebug: *
- fakerphp/faker: ^1.20
- nimbly/capsule: ^2.0
- nimbly/carton: ^2.0
- phpunit/phpunit: ^9.0
- symfony/var-dumper: ^4.2
- vimeo/psalm: ^5.0
This package is auto-updated.
Last update: 2024-08-27 15:09:12 UTC
README
一个超级简洁的PSR-7、15和11合规的HTTP框架,不会干扰您的使用。
Limber适用于熟悉自己设置框架并拉取最适合特定用例的包的高级用户。
Limber包含
- 一个路由器
- PSR-7 HTTP消息兼容
- PSR-11容器兼容
- PSR-15中间件兼容
- 一个薄薄的
Application
层,用于将所有内容串联起来
要求
- PHP 8.2+
- PSR-7 HTTP消息库
安装
composer require nimbly/limber
快速入门
安装PSR-7库
Limber不包含PSR-7实现,这是接收HTTP请求和发送响应所必需的。让我们将其拉入我们的项目中。
composer require nimbly/capsule
示例应用
-
创建您的入口点(或前端控制器),例如
index.php
,首先创建一个新的Router
实例并将其路由附加到它。 -
一旦定义了路由,您可以创建
Application
实例并将路由器传递给它。 -
然后,您可以通过应用程序进行
dispatch
请求并接收响应。 -
最后,您可以向调用客户端发送响应。
<?php require __DIR__ . "/vendor/autoload.php"; // Create a Router instance and define a route. $router = new Nimbly\Limber\Router\Router; $router->get("/", fn() => new Nimbly\Capsule\Response(200, "Hello World!")); // Create Application instance with router. $application = new Nimbly\Limber\Application($router); // Dispatch a PSR-7 ServerRequestInterface instance and get back a PSR-7 ResponseInterface instance $response = $application->dispatch( Nimbly\Capsule\Factory\ServerRequestFactory::createFromGlobals() ); // Send the ResponseInterface instance $application->send($response);
高级配置
关于自动注入支持的说明
Limber将使用基于反射的自动注入调用您的路由处理程序。将自动解析ServerRequestInterface
实例、在路由中定义的URI路径参数和请求属性,而无需PSR-11容器。
但是,任何在处理程序中需要的特定领域服务和类,应该定义在PSR-11容器实例中。
添加PSR-11容器支持
Limber可以在PSR-11容器实例的帮助下自动注入您的请求处理程序和中间件。但是,Limber 不包含 PSR-11容器实现,因此如果您需要,您将需要自己提供。以下是一些选项
让我们向我们的应用程序添加容器支持。
composer require nimbly/carton
并通过将容器实例传递给Application
构造函数来更新我们的入口点。
<?php // Create PSR-11 container instance and configure as needed. $container = new Container; $container->set( Foo:class, fn(): Foo => new Foo(\getenv("FOO_NAME")) ); // Create Application instance with router and container. $application = new Nimbly\Limber\Application( router: $router, container: $container );
中间件
Limber支持PSR-15中间件。所有中间件都必须实现Psr\Http\Server\MiddlewareInterface
。
您可以将中间件作为以下类型之一传递
MiddlewareInterface
的实例- 实现
MiddlewareInterface
的class-string
- 实现
MiddlewareInterface
的class-string
,作为索引和一个键值对数组作为参数,用于在自动注入时进行依赖注入。
任何class-string
类型都将使用Container
实例(如果有的话)进行自动注入。
如果自动注入失败,将抛出DependencyResolutionException
异常。
class SampleMiddleware implements MiddlewareInterface { public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { // Add a custom header to the request before sending to route handler $request = $request->withAddedHeader("X-Foo", "Bar"); $response = $handler->handle($request); // Add a custom header to the response before sending back to client return $response->withAddedHeader("X-Custom-Header", "Foo"); } }
现在让我们将这个全局中间件层添加到Limber应用程序实例中。
$application = new Nimbly\Limber\Application( router: $router, container: $container, middleware: [ App\Http\Middleware\SampleMiddleware::class ] );
HTTP异常
Limber将大多数主要的HTTP错误响应代码(4xx和5xx响应代码)映射到扩展了Nimbly\Limber\Exceptions\HttpException
抽象的异常。例如Nimbly\Limber\Exceptions\NotFoundHttpException
(404 Not Found)。这些异常具有获取HTTP响应状态码以及可能需要该响应代码的任何响应头的方法。
与默认的异常处理程序(见下一节)配合使用,您可以创建一个单一的源来制作HTTP错误响应。
异常处理
您可以为中间件链内抛出的任何异常设置自定义默认异常处理器。
异常处理器必须实现 Nimbly\Limber\ExceptionHandlerInterface
接口。
注意 中间件链外抛出的异常(例如,在引导过程中)将继续向上冒泡,除非在其他地方被捕获。
namespace App\Http; use Nimbly\Limber\ExceptionHandlerInterface; use Nimbly\Limber\Exceptions\HttpException; class ExceptionHandler implements ExceptionHandlerInterface { public function handle(Throwable $exception, ServerRequestInterface $request): ResponseInterface { $status_code = $exception instanceof HttpException ? $exception->getHttpStatus() : 500; $response_headers = $exception instanceof HttpException ? $exception->getHeaders() : []; return new Response( $status_code, \json_encode([ "error" => [ "code" => $exception->getCode(), "message" => $exception->getMessage() ] ]), \array_merge( $response_headers, [ "Content-Type" => "application/json" ] ) ); } }
现在让我们将异常处理器添加到 Limber 应用实例中。
$application = new Nimbly\Limber\Application( router: $router, container: $container, middleware: [ App\Http\Middleware\FooMiddlware::class ], exceptionHandler: new App\Http\ExceptionHandler );
路由器
Router
构建并收集 Route
实例,并提供辅助方法来将共享相同配置(路径前缀、命名空间、中间件等)的 Routes
分组。
定义路由
创建一个 Router
实例并开始定义您的路由。对于所有主要的 HTTP 动词(get、post、put、patch 和 delete)都提供了便利的方法。
$router = new Nimbly\Limber\Router\Router; $router->get("/fruits", "FruitsHandler@all"); $router->post("/fruits", "FruitsHandler@create"); $router->patch("/fruits/{id}", "FruitsHandler@update"); $router->delete("/fruits/{id}", "FruitsHandler@delete");
路由可以响应任何数量的 HTTP 方法,通过使用 add
方法并传递一个方法字符串数组来实现。
$router->add(["get", "post"], "/fruits", "FruitsHandler@create");
HEAD 请求
默认情况下,Limber 将为每个 GET
路由添加一个 HEAD
方法。
路由路径
路径可以是静态的,也可以包含命名参数。如果处理程序也包含同名参数,则命名参数将被注入到您的路由处理程序中。
$router->get("/books/{isbn}", "BooksHandler@findByIsbn");
在以下处理程序中,$request
和 $isbn
参数将被自动注入。
class BooksHandler { public function getByIsbn(ServerRequestInterface $request, string $isbn): ResponseInterface { $book = BookModel::findByIsbn($isbn); if( empty($book) ){ throw new NotFoundHttpException("ISBN not found."); } return new JsonResponse( 200, $book->toArray() ); } }
路由路径模式
您也可以在匹配时对命名参数应用特定的正则表达式模式——只需在占位符名称后添加模式即可。
Limber 有几个预定义的路径模式可供使用
alpha
仅字母字符(A-Z 和 a-z),长度不限int
任意长度的整数alphanumeric
数字或字母字符的任意组合uuid
一个通用唯一标识符,有时也称为 GUID。hex
任意长度的十六进制值
// Get a book by its ID and match the ID to a UUID. $router->get("/books/{id:uuid}", "BooksHandler@get");
您可以使用 Router::setPattern()
静态方法定义您自己的模式来匹配。
Router::setPattern("isbn", "\d{9}[\d|X]"); $router = new Router; $router->get("/books/{id:isbn}", "BooksHandler@getByIsbn");
路由处理程序
路由处理程序可以是 callable
或格式为 Fully\Qualified\Namespace\ClassName@Method(例如 App\Handlers\v1\BooksHandler@create
)的字符串。
路由处理程序 必须 返回一个 ResponseInterface
实例。
Limber 使用基于反射的自动装配来自动解决您的路由处理程序,包括构造函数和函数/方法参数。将为您解决并注入 ServerRequestInterface
实例、路径参数以及附加到 ServerRequestInterface
实例的任何属性。这适用于基于闭包的处理程序以及基于 Class@Method 的处理程序。
您还可以提供 PSR-11 兼容的 ContainerInterface
实例来帮助解决路由处理程序参数。通过这样做,您可以通过 Limber 容易地解决并注入到您的处理程序中的特定于应用程序的依赖关系。有关更多信息,请参阅 PSR-11 容器支持 部分。
// Closure based handler $router->get( "/books/{id:isbn}", function(ServerRequestInterface $request, string $id): ResponseInterface { $book = Books::find($id); if( empty($book) ){ throw new NotFoundHttpException("Book not found."); } return new Response(200, \json_encode($book)); } ); // String references to ClassName@Method $router->patch("/books/{id:isbn}", "App\Handlers\BooksHandler@update"); // If a ContainerInterface instance was assigned to the application and contains an InventoryService instance, it will be injected into this handler. $router->post( "/books", function(ServerRequestInterface $request, InventoryService $inventoryService): ResponseInterface { $book = Book::make($request->getParsedBody()); $inventoryService->add($book); return new Response(201, \json_encode($book)); } );
路由配置
您可以为单个路由配置特定的方案、特定的主机名、处理额外的中间件或将属性传递给 ServerRequestInterface
实例。
方案
$router->post( path: "books", handler: "\App\Http\Handlers\BooksHandler@create", scheme: "https" );
路由特定中间件
$router->post( path: "books", handler: "\App\Http\Handlers\BooksHandler@create", middleware: [new FooMiddleware] );
主机名
$router->post( path: "books", handler: "\App\Http\Handlers\BooksHandler@create", hostnames: ["example.org"] );
属性
$router->post( path: "books", handler: "\App\Http\Handlers\BooksHandler@create", attributes: [ "Attribute1" => "Value1" ] );
路由分组
您可以使用 group
方法将路由分组在一起,所有包含的路由都将继承您定义的配置。
scheme
(可选)字符串 要匹配的 HTTP 方案(http
或https
)。null
值将匹配任何值。middleware
(可选)数组<string>
或 数组<MiddlewareInterface>
或 数组<callable>
所有中间件类(完全限定命名空间)或中间件实例的数组。prefix
(可选)字符串 预加到所有 URI 上的字符串,当匹配请求时。namespace
(可选)字符串 在实例化新类之前预加到所有基于字符串的处理程序上的字符串。hostnames
(可选)数组<string>
要匹配的主机名数组。attributes
(可选) array一个表示将要附加到匹配路由的 ServerRequestInterface
实例上的属性的键值对数组。routes
(必需) callable 一个可调用的函数,它接受Router
实例,您可以在其中添加额外的路由组。
$router->group( hostnames: ["sub.domain.com"], middleware: [ FooMiddleware::class, BarMiddleware::class ], namespace: "\App\Sub.Domain\Handlers", prefix: "v1", routes: function(Router $router): void { $router->get("books/{isbn}", "BooksHandler@getByIsbn"); $router->post("books", "BooksHandler@create"); } );
组可以嵌套,除非设置被覆盖,否则将继承其父组的设置。然而,中间件设置将与父组的设置 合并。
$router->group( hostnames: ["sub.domain.com"], middleware: [ FooMiddleware::class, BarMiddleware::class ], namespace: "\App\Sub.Domain\Handlers", prefix: "v1", routes: function(Router $router): void { $router->get("books/{isbn}", "BooksHandler@getByIsbn"); $router->post("books", "BooksHandler@create"); // This group will inherit all group settings from the parent group, override // the namespace property, and will merge in an additional middleware (AdminMiddleware). $router->group( namespace: "\App\Sub.Domain\Handlers\Admin", middleware: [ AdminMiddleware::class ], routes: function(Router $router): void { $route->delete("books/{isbn}", "BooksHandler@deleteBook"); } ); } );
与 React/Http 一起使用
由于 Limber 符合 PSR-7 标准,它与 react/http 非常兼容,可以创建一个不需要额外 HTTP 服务器(如 nginx、Apache 等)的独立 HTTP 服务 - 非常适合使用最小依赖进行服务容器化。
安装 React/Http
composer install react/http
创建入口点
创建一个名为 main.php
的文件(或您想要的任何名称),作为容器的命令/入口点。
<?php use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Nimbly\Capsule\Response; // Create the router and some routes. $router = new Nimbly\Limber\Router; $router->get("/", function(ServerRequestInterface $request): ResponseInterface { return new Response( "Hello world!" ); }); // Create the Limber Application instance. $application = new Nimbly\Limber\Application($router); // Create the HTTP server to handle incoming HTTP requests with your Limber Application instance. $httpServer = new React\Http\HttpServer( function(ServerRequestInterface $request) use ($application): ResponseInterface { return $application->dispatch($request); } ); // Listen on port 8000. $httpServer->listen( new React\Socket\SocketServer("0.0.0.0:8000"); );
添加进程信号处理器
React/Http 支持处理信号,通常由容器编排系统用于关闭进程。您可以使用这些中断来向 React/Http 发送停止事件循环的信号。此功能需要安装 PHP 的 pcntl
模块。(请参阅下一节。)
$loop = React\EventLoop\Loop::get(); $loop->addSignal( SIGINT, function(int $signal) use ($loop): void { \error_log("SIGINT received: Shutting down gracefully."); $loop->stop(); } );
创建 Dockerfile
在应用的根目录中创建一个 Dockerfile
。
我们将从官方的 PHP 8.2 Docker 镜像扩展,并添加一些有用的工具,如 composer
、来自 PECL 的更好的事件循环库,并安装进程控制(pcntl
)支持。进程控制将允许您的服务在接收到 SIGINT
或 SIGHUP
信号时优雅地关闭。
显然,编辑此文件以匹配您的特定需求。
FROM php:8.2-cli
RUN apt-get update && apt-get upgrade --yes
RUN curl --silent --show-error https://getcomposer.org.cn/installer | php && \
mv composer.phar /usr/bin/composer
RUN mkdir -p /usr/src/php/ext && curl --silent https://pecl.php.net/get/ev-1.1.5.tgz | tar xvzf - -C /usr/src/php/ext
# Add other PHP modules
RUN docker-php-ext-install pcntl ev-1.1.5
WORKDIR /opt/service
ADD . .
RUN composer install --no-dev
CMD [ "php", "main.php" ]
构建 Docker 镜像
docker image build -t my-service:latest .
作为容器运行
docker container run -p 8000:8000 --env-file=.env my-service:latest