.claude/skills/ecotone-interceptors/SKILL.md
Implements Ecotone interceptors and middleware: #[Before], #[After], #[Around], #[Presend] attributes with pointcut targeting, precedence ordering, header modification, and MethodInvocation. Use when adding interceptors, middleware, cross-cutting concerns like transactions/ logging/authorization, hooking into handler execution, or modifying messages before/after handling.
npx skillsauth add ecotoneframework/ecotone-dev ecotone-interceptorsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Interceptors are cross-cutting middleware that hook into handler execution. Use them for transactions, authorization, logging, validation, header enrichment, and other concerns that span multiple handlers.
| Attribute | When | Flow Control | changeHeaders |
|-----------|------|-------------|---------------|
| #[Presend] | Before message enters channel | No | Yes |
| #[Before] | Before handler executes | No | Yes |
| #[Around] | Wraps handler execution | MethodInvocation::proceed() | No |
| #[After] | After handler completes | No | Yes |
Execution order: Presend -> Before -> Around -> handler -> Around end -> After
use Ecotone\Messaging\Attribute\Interceptor\Before;
class ValidationInterceptor
{
#[Before(pointcut: CommandHandler::class)]
public function validate(object $command): void
{
// Throw exception to stop execution
}
}
use Ecotone\Messaging\Attribute\Interceptor\After;
class AuditInterceptor
{
#[After(pointcut: CommandHandler::class)]
public function audit(object $command): void
{
// Log after handler completes
}
}
use Ecotone\Messaging\Attribute\Interceptor\Around;
use Ecotone\Messaging\Handler\Processor\MethodInvoker\MethodInvocation;
class TransactionInterceptor
{
#[Around(precedence: Precedence::DATABASE_TRANSACTION_PRECEDENCE)]
public function transactional(MethodInvocation $invocation): mixed
{
$this->connection->beginTransaction();
try {
$result = $invocation->proceed();
$this->connection->commit();
return $result;
} catch (\Throwable $e) {
$this->connection->rollBack();
throw $e;
}
}
}
You must call proceed() or the handler chain stops.
use Ecotone\Messaging\Attribute\Interceptor\Presend;
class AuthorizationInterceptor
{
#[Presend(pointcut: CommandHandler::class)]
public function authorize(object $command, #[Header('userId')] string $userId): void
{
if (! $this->authService->canExecute($userId, $command)) {
throw new UnauthorizedException();
}
}
}
Pointcuts target which handlers an interceptor applies to:
// By attribute
#[Before(pointcut: CommandHandler::class)]
// By class
#[Before(pointcut: OrderService::class)]
// By method
#[Before(pointcut: OrderService::class . '::placeOrder')]
// By namespace
#[Before(pointcut: 'App\Domain\*')]
// AND / OR / NOT
#[Before(pointcut: CommandHandler::class . '||' . EventHandler::class)]
#[Around(pointcut: CommandHandler::class . '&¬(' . WithoutTransaction::class . ')')]
When no explicit pointcut is set, it's inferred from the interceptor method's parameter type-hints:
#[Before]
public function check(RequiresAuth $attribute): void { }
// Auto-targets handlers with #[RequiresAuth]
#[Before(changeHeaders: true, pointcut: CommandHandler::class)]
public function addHeaders(#[Headers] array $headers): array
{
$headers['processedAt'] = time();
return $headers;
}
Only available on #[Before], #[After], #[Presend] (not #[Around]).
proceed() in #[Around] interceptorsPrecedence::DEFAULT_PRECEDENCE for custom interceptorsclassesToResolve for testing#[Before], #[After], #[Around], #[Presend] attributes, MethodInvocation interface, and Precedence constants table. Load when you need exact parameter names, types, defaults, or precedence values.MethodInvocation, and complete pointcut patterns (attribute, class, namespace, method, AND/OR/NOT, bus targeting, custom attributes, dynamic pointcut building). Load when you need complete, copy-paste-ready interceptor implementations or complex pointcut expressions.classesToResolve. Load when writing tests for interceptors.development
Implements workflows in Ecotone: Sagas (stateful process managers), stateless workflows with InternalHandler and outputChannelName chaining, and Orchestrators (Enterprise) with routing slip pattern. Use when building Sagas, process managers, multi-step workflows, long-running processes, handler chaining, or Orchestrators.
development
Writes and debugs tests for Ecotone using EcotoneLite::bootstrapFlowTesting, aggregate testing, async-tested-synchronously patterns, projections, and common failure diagnosis. Use when writing tests, debugging test failures, adding test coverage, or implementing any new feature that needs tests. Should be co-triggered whenever a new handler, aggregate, saga, projection, or interceptor is being implemented.
testing
Sets up Ecotone in a Symfony project: composer installation, bundle registration, YAML configuration, Doctrine ORM integration, SymfonyConnectionReference for DBAL, Symfony Messenger channels, async consumer commands, and ServiceContext. Use when installing Ecotone in Symfony, configuring Symfony-specific connections, or setting up Symfony async consumers.
development
Implements message resiliency in Ecotone: RetryTemplateBuilder for retry strategies, error channels, ErrorHandlerConfiguration, DBAL dead letter queues, outbox pattern for guaranteed delivery, and FinalFailureStrategy for permanent failures. Use when handling failed messages, configuring retries, setting up dead letter queues, implementing outbox pattern, or managing error channels.