PHPackages                             componenta/interceptor - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. componenta/interceptor

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

componenta/interceptor
======================

Attribute-driven method interception for Componenta

v1.0.0(1mo ago)086MITPHPPHP ^8.4

Since Jun 17Pushed 1mo agoCompare

[ Source](https://github.com/componenta/interceptor)[ Packagist](https://packagist.org/packages/componenta/interceptor)[ RSS](/packages/componenta-interceptor/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (6)Versions (2)Used By (6)

Componenta Interceptor
======================

[](#componenta-interceptor)

[![PHP 8.4+](https://camo.githubusercontent.com/80c4564163cef31b2a66baaeb95a5bf4a418bcb5242a5ae707b94c2f4811e742/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e342532422d626c7565)](https://camo.githubusercontent.com/80c4564163cef31b2a66baaeb95a5bf4a418bcb5242a5ae707b94c2f4811e742/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e342532422d626c7565)[![License MIT](https://camo.githubusercontent.com/5caa455d8debc46fb23abbadb45a733a937f3910a73fc875c2f7820468e1bb54/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e)](https://camo.githubusercontent.com/5caa455d8debc46fb23abbadb45a733a937f3910a73fc875c2f7820468e1bb54/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e)[![Tests](https://camo.githubusercontent.com/59146161eceefc16e92c23002392a74c257150394226ab289a7eaf4ee8a3691f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d35332532307061737365642d627269676874677265656e)](https://camo.githubusercontent.com/59146161eceefc16e92c23002392a74c257150394226ab289a7eaf4ee8a3691f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d35332532307061737365642d627269676874677265656e)[![MSI](https://camo.githubusercontent.com/35d3b1acb39af1f733087395e02752de83ed22b9530a32317bb0191b5adc9088/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d53492d3130302532352d627269676874677265656e)](https://camo.githubusercontent.com/35d3b1acb39af1f733087395e02752de83ed22b9530a32317bb0191b5adc9088/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d53492d3130302532352d627269676874677265656e)

Middleware-style interceptor pipeline for PHP callables. Wrap any function, method or closure with cross-cutting logic (logging, caching, transactions, authorization, serialization) declared either via `pipe()` or method-level attributes.

**[Русская документация](README.ru.md)**

Installation
------------

[](#installation)

```
composer require componenta/interceptor
```

Requirements
------------

[](#requirements)

- PHP 8.4+
- `psr/container`
- `componenta/di` (`CallableExecutorInterface`, `FactoryInterface`, `ParametersResolver`)
- `componenta/reflection` (lazy reflector resolution)
- `componenta/config` (optional `ConfigProvider` integration)

Related Packages
----------------

[](#related-packages)

PackageWhy it matters here`componenta/di`Invokes callables and resolves missing parameters before interceptors run.`componenta/reflection`Reads callable reflection and method attributes lazily.`componenta/config`Registers context factories and attribute interceptors.`componenta/interceptor-app`Compiles interceptor attributes into application cache.`componenta/serialize-interceptor`Ready-made result serialization interceptor backed by Symfony Serializer.`componenta/http-respond-interceptor`Ready-made HTTP interceptor that wraps results into PSR-7 responses.`componenta/http-paginate-interceptor`Ready-made HTTP interceptor for `PaginatorInterface` -&gt; `ResourcePaginator`.`componenta/pipeline`Similar chain idea for PSR-15 HTTP middleware; this package wraps arbitrary PHP callables.Quick Start
-----------

[](#quick-start)

```
use Componenta\DI\CallableExecutorInterface;
use Componenta\Interceptor\CallbackInterceptorFactory;
use Componenta\Interceptor\InterceptingExecutor;

$executor = new InterceptingExecutor(
    $container->get(CallableExecutorInterface::class),
    CallbackInterceptorFactory::around(
        before: fn ($ctx) => $ctx->withAttribute('started', microtime(true)),
        after:  fn ($result, $ctx) => ['result' => $result, 'ms' => (microtime(true) - $ctx->getAttribute('started')) * 1000],
    ),
);

$result = $executor->call([$controller, 'handle'], ['id' => 42]);
```

Core Concepts
-------------

[](#core-concepts)

### Interceptor

[](#interceptor)

A class implementing `InterceptorInterface`. Receives the execution context and a continuation handler; may act before/after, short-circuit, or transform the result.

```
interface InterceptorInterface
{
    public function intercept(
        CallableContextInterface $context,
        ContextHandlerInterface $handler,
    ): mixed;
}
```

### Context

[](#context)

Immutable object carrying the callable, its parameters, arbitrary attributes, and a lazily-resolved reflector. Mutators return new instances:

```
$context = $context
    ->withParameter('userId', 42)
    ->withAttribute('trace.id', $traceId);
```

### Pipeline

[](#pipeline)

`InterceptingExecutor` composes interceptors into a pre-built chain on first use. Execution order is **FIFO** — the first registered interceptor is outermost (runs first in the call direction, last on unwind):

```
$executor = new InterceptingExecutor($base, $auth, $logger, $cache);

// Single invocation, one pass through the chain:
//
//   auth.before → logger.before → cache.before → callable
//                                              → cache.after → logger.after → auth.after
//
// Each interceptor's intercept() is called exactly once. Work placed before
// $handler->handle() runs on the way in; work after it runs on unwind.
```

`pipe()` returns a new immutable pipeline:

```
$withTx = $executor->pipe($transactionInterceptor);
```

### Short-circuit

[](#short-circuit)

An interceptor may return without invoking the handler (auth rejections, cache hits, maintenance screens). The pipeline stops, and the value bubbles back through outer interceptors.

Attributes
----------

[](#attributes)

Declare interceptors on methods via `#[Intercept]`:

```
use Componenta\Interceptor\Attribute\Intercept;

final class UserController
{
    #[Intercept(CacheInterceptor::class, ['ttl' => 300])]
    #[Intercept(AuthInterceptor::class)]
    public function show(int $id): User { /* ... */ }
}
```

Resolution is driven by `AttributeInterceptor` (register it once in your pipeline). The interceptor instance is built via `FactoryInterface` with the declared `params`. Attributes are read as layers wrapped around the method, **from outside in** — the topmost attribute is the outermost layer (enters first, returns last), the bottommost attribute is closest to the method body.

Put entry-side interceptors (authorization, rate limits, caching gates) above result-side ones (response formatting, serialization, pagination). The method's return value flows outward through the inner layers first, so a serializer placed below a response wrapper gets the raw value and passes the serialized string up to the wrapper:

```
#[Respond(200, 'application/json')]      // outermost — wraps the final string in a PSR-7 response
#[Serialize(context: [...])]             // innermost — receives the raw return value first
public function show(int $id): User { /* ... */ }
```

Attribute classes can also implement `InterceptorInterface` directly — they are instantiated through native PHP attribute construction:

```
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final readonly class WrapJson implements InterceptorInterface
{
    public function intercept($ctx, $handler): mixed
    {
        return json_encode($handler->handle($ctx));
    }
}
```

### Scopes

[](#scopes)

Restrict where an interceptor runs by implementing `Componenta\Scope\ScopedInterface` on the attribute or on the interceptor instance:

```
use Componenta\Interceptor\InterceptorInterface;
use Componenta\Interceptor\Scope;
use Componenta\Scope\ScopedInterface;
use Componenta\Scope\Scopes;

final class RespondInterceptor implements InterceptorInterface, ScopedInterface
{
    public Scopes $scopes {
        get => Scopes::of(Scope::HTTP);
    }

    // ...
}
```

The integrator signals the current scope by setting a context attribute before the pipeline runs:

```
use Componenta\Interceptor\CallableContext;
use Componenta\Interceptor\Scope;

$context = $context->withAttribute(CallableContext::SCOPE_ATTRIBUTE, Scope::HTTP);
```

Attribute-level scope takes priority over instance-level scope. Interceptors without either `ScopedInterface` always match.

Built-in scopes: `HTTP`, `CONSOLE`, `GRPC`, `QUEUE`, `WEBSOCKET`. Custom scopes can be represented by `Componenta\Scope\ScopeName` or by a package-specific enum implementing `Componenta\Scope\ScopeInterface`.

Callback Interceptors
---------------------

[](#callback-interceptors)

Build interceptors from closures without dedicated classes:

```
use Componenta\Interceptor\CallbackInterceptorFactory as F;

$logger   = F::before(fn ($ctx) => $log->info('calling ' . $ctx->reflector->name));
$envelope = F::after(fn ($result) => ['data' => $result]);
$recover  = F::catch(fn (\Throwable $e) => ['error' => $e->getMessage()]);
$cleanup  = F::finally(fn () => $this->releaseLock());

$tracer = F::around(
    before: fn ($ctx) => $ctx->withAttribute('t0', microtime(true)),
    after:  function ($result, $ctx) use ($log) {
        $log->info(sprintf('%.2fms', (microtime(true) - $ctx->getAttribute('t0')) * 1000));
        return $result;
    },
);
```

Parameter Resolution
--------------------

[](#parameter-resolution)

Register `ParameterResolvingInterceptor` to enrich the callable's parameters through DI before downstream interceptors see them:

```
new InterceptingExecutor(
    $container->get(CallableExecutorInterface::class),
    new ParameterResolvingInterceptor($parametersResolver), // outermost — runs first
    $container->get(AttributeInterceptor::class),
);
```

This lets attribute interceptors read resolved arguments (e.g., `$ctx->parameters` for cache keys).

Caching
-------

[](#caching)

`AttributeInterceptor` caches attribute resolution on two levels:

1. **Candidates per signature** — `#[Intercept]` instances are created once per method and reused.
2. **Composed chains per terminal** — stored in a `WeakMap` keyed by the terminal handler; innermost link holds the terminal weakly, so GC reclaims entries when the terminal goes out of scope (e.g., when `pipe()` discards an old pipeline).

No configuration required — caching is always on. Closures bypass the cache (no stable signature).

Container Wiring
----------------

[](#container-wiring)

Register the module's `ConfigProvider` in your application:

```
new \Componenta\Interceptor\ConfigProvider();
```

It binds `CallableContextFactory`, `AttributeInterceptor`, and `PipelineInterface`. The `PipelineInterface` service is intended for HTTP route handler execution and is built by `HttpInterceptorPipelineFactory`:

1. `ParameterResolvingInterceptor` is always registered first, so callable parameters are resolved through DI before application interceptors run.
2. Additional interceptors are read from `ConfigKey::HTTP_INTERCEPTORS`.
3. Each configured item may be a container service id or an `InterceptorInterface` instance.

Typical HTTP configuration:

```
use Componenta\Interceptor\AttributeInterceptor;
use Componenta\Interceptor\ConfigKey;

return [
    ConfigKey::HTTP_INTERCEPTORS => [
        AttributeInterceptor::class,
    ],
];
```

`componenta/router-app` consumes `PipelineInterface` when it executes route handlers. Applications usually configure the interceptor list in their `src/ConfigProvider.php`.

License
-------

[](#license)

MIT

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance91

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

43d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/20490712?v=4)[Andrey Shelamkoff](/maintainers/Shelamkoff)[@Shelamkoff](https://github.com/Shelamkoff)

---

Top Contributors

[![Shelamkoff](https://avatars.githubusercontent.com/u/20490712?v=4)](https://github.com/Shelamkoff "Shelamkoff (1 commits)")

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/componenta-interceptor/health.svg)

```
[![Health](https://phpackages.com/badges/componenta-interceptor/health.svg)](https://phpackages.com/packages/componenta-interceptor)
```

###  Alternatives

[symfony/dependency-injection

Allows you to standardize and centralize the way objects are constructed in your application

4.2k455.6M9.9k](/packages/symfony-dependency-injection)[illuminate/contracts

The Illuminate Contracts package.

706130.3M14.2k](/packages/illuminate-contracts)[illuminate/container

The Illuminate Container package.

31082.0M2.4k](/packages/illuminate-container)[ecotone/ecotone

Enterprise architecture layer for Laravel and Symfony — CQRS, Event Sourcing, Durable Workflows (Sagas, Orchestrators), Projections, and Outbox messaging via PHP attributes.

564576.7k54](/packages/ecotone-ecotone)[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

751291.4k47](/packages/civicrm-civicrm-core)[symfony/object-mapper

Provides a way to map an object to another object

361.5M56](/packages/symfony-object-mapper)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
