Spiral 的关键特性之一是其对拦截器的支持,拦截器可用于为应用程序添加功能,而无需修改应用程序的核心代码。这有助于保持你的代码库更加模块化和可维护。
使用拦截器的好处:
你可以将拦截器与各种组件一起使用,例如:
拦截器实现 Spiral\Interceptors\InterceptorInterface
接口:
namespace Spiral\Interceptors;
use Spiral\Interceptors\Context\CallContextInterface;
interface InterceptorInterface
{
public function intercept(CallContextInterface $context, HandlerInterface $handler): mixed;
}
该接口提供了一种灵活的方式来拦截对任何目标的调用,无论是方法、函数还是自定义处理程序。
CallContextInterface
包含有关被拦截调用的所有信息:
namespace Spiral\Interceptors\Context;
interface CallContextInterface extends AttributedInterface
{
public function getTarget(): TargetInterface;
public function getArguments(): array;
public function withTarget(TargetInterface $target): static;
public function withArguments(array $arguments): static;
// AttributedInterface 中的方法:
public function getAttributes(): array;
public function getAttribute(string $name, mixed $default = null): mixed;
public function withAttribute(string $name, mixed $value): static;
public function withoutAttribute(string $name): static;
}
注意
CallContextInterface
是不可变的,所以调用withTarget()
和withArguments()
会返回具有更新值的新实例。
TargetInterface
定义了要拦截调用的目标。它可以表示方法、函数、闭包,甚至是 RPC 或消息队列端点的路径字符串。
namespace Spiral\Interceptors\Context;
interface TargetInterface extends \Stringable
{
public function getPath(): array;
public function withPath(array $path, ?string $delimiter = null): static;
public function getReflection(): ?\ReflectionFunctionAbstract;
public function getObject(): ?object;
public function getCallable(): callable|array|null;
}
Target
类中的静态工厂方法使创建不同类型的目标变得容易:
use Spiral\Interceptors\Context\Target;
// 从方法反射创建
$target = Target::fromReflectionMethod(new \ReflectionMethod(UserController::class, 'show'), UserController::class);
// 从函数反射创建
$target = Target::fromReflectionFunction(new \ReflectionFunction('array_map'));
// 从闭包创建
$target = Target::fromClosure(fn() => 'Hello, World!');
// 从路径字符串创建(用于 RPC 端点或消息队列处理程序)
$target = Target::fromPathString('user.show');
// 从控制器-操作对创建
$target = Target::fromPair(UserController::class, 'show');
让我们创建一个简单的拦截器,用于记录调用的执行时间:
namespace App\Interceptor;
use Psr\Log\LoggerInterface;
use Spiral\Interceptors\Context\CallContextInterface;
use Spiral\Interceptors\HandlerInterface;
use Spiral\Interceptors\InterceptorInterface;
class ExecutionTimeInterceptor implements InterceptorInterface
{
public function __construct(
private readonly LoggerInterface $logger
) {
}
public function intercept(CallContextInterface $context, HandlerInterface $handler): mixed
{
$target = $context->getTarget();
$startTime = \microtime(true);
try {
return $handler->handle($context);
} finally {
$executionTime = \microtime(true) - $startTime;
$this->logger->debug(
'Target executed',
[
'target' => (string)$target,
'execution_time' => $executionTime,
]
);
}
}
}
这个拦截器:
要使用拦截器,你需要使用 PipelineBuilderInterface
构建一个拦截器管道:
use Spiral\Interceptors\PipelineBuilder;
use Spiral\Interceptors\Context\CallContext;
use Spiral\Interceptors\Context\Target;
use Spiral\Interceptors\Handler\CallableHandler;
use App\Interceptor\ExecutionTimeInterceptor;
use App\Interceptor\AuthorizationInterceptor;
// 创建拦截器
$interceptors = [
new ExecutionTimeInterceptor($logger),
new AuthorizationInterceptor($auth),
];
// 构建管道
$pipeline = (new PipelineBuilder())
->withInterceptors(...$interceptors)
->build(new CallableHandler());
// 创建调用上下文
$context = new CallContext(
target: Target::fromPair(UserController::class, 'show'),
arguments: ['id' => 42],
attributes: ['request' => $request]
);
// 执行管道
$result = $pipeline->handle($context);
管道应该以执行目标的处理程序结束。Spiral 提供了几个内置的处理程序:
CallableHandler
简单地调用目标,不进行任何额外处理:
use Spiral\Interceptors\Handler\CallableHandler;
$handler = new CallableHandler();
AutowireHandler
使用容器解析缺失的参数:
use Spiral\Interceptors\Handler\AutowireHandler;
$handler = new AutowireHandler($container);
当处理控制器时,这个处理程序很有用,你可以自动注入服务依赖。
以下是使用拦截器的更全面示例:
namespace App\Controller;
use App\Interceptor\AuthorizationInterceptor;
use App\Interceptor\CacheInterceptor;
use App\Interceptor\ExecutionTimeInterceptor;
use App\User\UserService;
use Psr\Container\ContainerInterface;
use Spiral\Core\Attribute\Proxy;
use Spiral\Core\CompatiblePipelineBuilder;
use Spiral\Interceptors\Context\CallContext;
use Spiral\Interceptors\Context\Target;
use Spiral\Interceptors\Handler\AutowireHandler;
class UserController
{
private $pipeline;
public function __construct(
private readonly UserService $userService,
#[Proxy] ContainerInterface $container
) {
// 使用拦截器构建管道
$this->pipeline = (new CompatiblePipelineBuilder())
->withInterceptors(
new ExecutionTimeInterceptor($container->get(LoggerInterface::class)),
new AuthorizationInterceptor($container->get(AuthInterface::class)),
new CacheInterceptor($container->get(CacheInterface::class))
)
->build(new AutowireHandler($container));
}
public function show(int $id)
{
// 为目标方法创建上下文
$context = new CallContext(
target: Target::fromReflectionMethod(
new \ReflectionMethod($this->userService, 'findUser'),
$this->userService
),
arguments: ['id' => $id]
);
// 执行管道
return $this->pipeline->handle($context);
}
}
在这个例子中:
AutowireHandler
从容器中解析缺失的参数UserService
的 findUser
方法注意 要了解容器作用域和代理对象,请参阅文档中的 IoC 作用域 部分。
注意 不再推荐基于
spiral/hmvc
的旧实现拦截器。 你可以在 https://spiral.dev/docs/framework-interceptors/3.13 找到旧文档。
在 Spiral 3.14.0 中,spiral/interceptors
包引入了新的拦截器实现。
以下是新实现与旧实现的区别:
namespace App\Interceptor;
use Psr\SimpleCache\CacheInterface;
use Spiral\Core\CoreInterface;
use Spiral\Core\CoreInterceptorInterface;
class CacheInterceptor implements CoreInterceptorInterface
{
public function __construct(
private readonly CacheInterface $cache,
private readonly int $ttl = 3600,
) {}
public function process(string $controller, string $action, array $parameters, CoreInterface $core): mixed
{
// 第1步:根据控制器、操作和参数生成缓存键
$cacheKey = $this->generateCacheKey($controller, $action, $parameters);
// 第2步:检查结果是否已缓存
if ($this->cache->has($cacheKey)) {
// 如果可用,返回缓存的结果
return $this->cache->get($cacheKey);
}
// 第3步:如果没有缓存的结果,执行控制器操作
$result = $core->callAction($controller, $action, $parameters);
// 第4步:为未来的请求缓存结果
if ($this->isCacheable($result)) {
$this->cache->set($cacheKey, $result, $this->ttl);
}
return $result;
}
private function generateCacheKey(string $controller, string $action, array $parameters): string
{
// 从控制器、操作和参数创建确定性的缓存键
return \md5($controller . '::' . $action . '::' . \serialize($parameters));
}
private function isCacheable(mixed $result): bool
{
// 只缓存可序列化的结果
return !\is_resource($result) && (
\is_scalar($result) ||
\is_array($result) ||
$result instanceof \Serializable ||
$result instanceof \stdClass
);
}
}
旧接口:
interface CoreInterceptorInterface
{
public function process(string $controller, string $action, array $parameters, CoreInterface $core): mixed;
}
新接口:
interface InterceptorInterface
{
public function intercept(CallContextInterface $context, HandlerInterface $handler): mixed;
}
主要区别:
$controller
和 $action
参数已被更灵活的 Target
概念替代$parameters
数组现在是 CallContext
的一部分CoreInterface
,现在有一个提供更多灵活性的 HandlerInterface
在 Spiral 3.x 中,同时支持旧拦截器(CoreInterceptorInterface
)和新拦截器(InterceptorInterface
)。
但是,旧实现已被弃用,将在 Spiral 4.x 中排除。
如果你需要同时使用两种实现,请使用 CompatiblePipelineBuilder
:
use Spiral\Core\CompatiblePipelineBuilder;
use Spiral\Core\CoreInterceptorInterface; // 旧版拦截器
use Spiral\Interceptors\InterceptorInterface; // 新版拦截器
$pipeline = (new CompatiblePipelineBuilder())
->withInterceptors(
new LegacyStyleInterceptor(), // 实现 CoreInterceptorInterface
new NewStyleInterceptor() // 实现 InterceptorInterface
)
->build(new CallableHandler());
从旧实现迁移时:
CoreInterceptorInterface
替换为 InterceptorInterface
Target
替代 $controller
和 $action
参数$parameters
移至 CallContext
$core->callAction()
替换为 $handler->handle()
事件 | 描述 |
---|---|
Spiral\Interceptors\Event\InterceptorCalling | 在调用拦截器之前触发。 |
警告
Spiral\Core\Event\InterceptorCalling
事件仅由旧实现中的已弃用\Spiral\Core\InterceptorPipeline
分发。 框架的新实现不使用此事件。
注意 要了解有关分发事件的更多信息,请参阅文档中的 事件 部分。