kafkiansky/symfony-middleware

symfony的PSR-15中间件。

1.0.0 2023-12-13 11:32 UTC

This package is auto-updated.

Last update: 2024-09-13 13:10:58 UTC


README

test Codecov Software License Total Downloads Quality Score

内容

安装

composer require kafkiansky/symfony-middleware

配置

确保您已在 config/bundles.php 中添加了包。

Kafkiansky\SymfonyMiddleware\SymiddlewareBundle::class => ['all' => true],

packages/symfony-middleware.yaml 中创建配置文件。

symiddleware:
  global:
    ##

使用

每个中间件必须实现 Psr\Http\Server\MiddlewareInterface 接口。感谢symfony自动配置,现在中间件注册表知道您的中间件。

为了让中间件开始执行,必须在控制器类和/或方法上定义它们。

use Kafkiansky\SymfonyMiddleware\Attribute\Middleware;

#[Middleware([ValidatesQueryParams::class])]
final class SomeController
{
    #[Middleware([ConvertStringsToNull::class])]
    public function index(): void
    {
        
    }
}

如果控制器是可调用的,中间件只需在控制器类上定义即可

use Kafkiansky\SymfonyMiddleware\Attribute\Middleware;

#[Middleware([ValidatesQueryParams::class, ConvertStringsToNull::class])]
final class SomeController
{
    public function __invoke(): void
    {
    }
}

分组

如果您想使用中间件列表,可以在 symfony_middleware.yaml 配置文件中定义中间件组。

symiddleware:
  groups:
    debug:
      if: '%env(RUN_DEBUG_MIDDLEWARE)%'
      middlewares:
        - 'App\Middleware\TrackRequestTime'
        - 'App\Middleware\EnableSqlLogger'

现在在控制器类或方法上定义此中间件。

use Kafkiansky\SymfonyMiddleware\Attribute\Middleware;

#[Middleware(['debug'])]
final class SomeController
{
    public function __invoke(): void
    {
    }
}

请注意配置文件中的 if 参数。此参数告诉中间件运行者何时可以运行中间件组。如果为假,则此中间件将不会执行。

全局

如果您想在每个请求上运行中间件列表,则需要全局中间件部分。此关键字是保留的,不支持 if 参数。

symiddleware:
  global:
      - App\Controller\SetCorsHeaders
  groups:
    web:
      middlewares:
        - 'App\Middleware\ModifyRequestMiddleware'

现在 App\Controller\SetCorsHeaders 中间件将在每个请求上执行。

示例

  1. 简单的修改请求的中间件
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;

final class ModifyRequestMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        return $handler->handle($request->withAttribute(__CLASS__, 'handled'))
    }
}
  1. 修改响应的中间件
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;

final class ModifyResponseMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $response = $handler->handle($request)

        return $response->withHeader('x-developer', 'kafkiansky');
    }
}
  1. 停止执行的中间件
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Nyholm\Psr7\Response;

final class StopExecution implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $response = new Response(200, [], json_encode(['success' => false]));

        return $response;
    }
}

在此示例中,控制器将不会执行。

  1. 使用symfony响应停止执行
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Nyholm\Psr7\Response;
use Kafkiansky\SymfonyMiddleware\Psr\PsrResponseTransformer;
use Symfony\Component\HttpFoundation\JsonResponse;

final class StopExecution implements MiddlewareInterface
{
    private PsrResponseTransformer $psrResponseTransformer;

    public function __construct(PsrResponseTransformer $psrResponseTransformer)
    {
        $this->psrResponseTransformer = $psrResponseTransformer;        
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        return $this->psrResponseTransformer->toPsrResponse(new JsonResponse(['success' => false]));
    }
}

您可以使用单个中间件、中间件列表的 Middleware 属性等方式来组合中间件组。以下所有示例都将正常工作

use Kafkiansky\SymfonyMiddleware\Attribute\Middleware;

#[Middleware(['debug', 'api', SomeMiddleware::class])]
#[Middleware([SomeAnotherMiddleware::class])]
final class SomeController
{
    public function __invoke(): void
    {
    }
}
use Kafkiansky\SymfonyMiddleware\Attribute\Middleware;

#[Middleware(['debug', 'api', SomeMiddleware::class])]
final class SomeController
{
    #[Middleware([SomeAnotherMiddleware::class, 'web'])]
    #[Middleware(['tracking'])]
    public function index(): void
    {
    }
}

此外,您还可以使用嵌套组

symiddleware:
  global:
      - App\Controller\SetCorsHeaders
      - web
  groups:
    web:
      middlewares:
        - 'App\Middleware\ModifyRequestMiddleware'
        - debug
    debug:
      if: false
      middlewares:
        - 'App\Middleware\LogSqlQuery'

重复的中间件将被删除。

自定义

PSR中间件和Symfony具有不同的不兼容的请求对象。如果您的中间件将要更改请求对象,则只有 attributes查询参数头部解析后的主体 将从psr请求复制到symfony请求。如果您希望更改此行为,您可以更改 Kafkiansky\SymfonyMiddleware\Psr\PsrRequestCloner 接口的绑定,以实现您的实现。

缓存

该包在生产环境中使用缓存以防止反射的使用。首先,包将搜索 app.cache_middleware 参数。如果包找不到它,它将使用 kernel.environment 定义,并在设置为 prod 时缓存属性。

即使找不到其属性,包也将缓存所有控制器。这种方法将允许记住所有控制器,而不再使用反射。

真实世界示例

假设您有一些需要通过基本认证授权访问的端点。编写中间件

# services.yaml

services:
    _defaults:
        autowire: true
        autoconfigure: true

        bind:
            $basicUser: 'root'
            $basicPassword: 'secret'
// Authorization Middleware

use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Nyholm\Psr7\Response;

final class AuthorizeRequests implements MiddlewareInterface
{
    private string $basicUser;
    private string $basicPassword;

    public function __construct(string $basicUser, string $basicPassword)
    {
        $this->basicUser = $basicUser;
        $this->basicPassword = $basicPassword;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $user = $request->getServerParams()['PHP_AUTH_USER'] ?? null;
        $passwd = $request->getServerParams()['PHP_AUTH_PW'] ?? null;

        if ($user === $this->basicUser && $passwd === $this->basicPassword) {
            return $handler->handle($request);
        }

        return new Response(401, [
            'WWW-Authenticate' => 'Basic realm="Backend"'
        ]);
    }
}
# example configuration
symiddleware:
  groups:
    basic:
      middlewares:
        - App\Middleware\AuthorizeRequests
// Some controller

use Symfony\Component\HttpFoundation\JsonResponse;
use Kafkiansky\SymfonyMiddleware\Attribute\Middleware;

final class SomeController
{
    #[Middleware(['basic'])] // via middleware group
    public function writeArticle(): JsonResponse
    {
    }

    #[Middleware([App\Middleware\AuthorizeRequests::class])] // via concrete class
    public function deleteArticle(): JsonResponse
    {
    }
}

中间件

处理HTTP基本认证PSR-15中间件为Symfony

测试

$ composer test

许可证

MIT许可证(MIT)。有关更多信息,请参阅许可证文件