Attributes
Sugoi’s attribute system enables:
- Declarative behavior without boilerplate
- Cross-cutting concerns like retry, caching, and telemetry
- Async-safe context propagation
- Dynamic proxy generation
It’s all about developer joy and control without clutter.
“Use attributes to express intent—not implementation.”
Framework Discovery
#[AutoDiscover]
Used internally by the framework to mark classes that should be automatically scanned during startup.
- Enables Sugoi to bootstrap modules, services, pages, hooks, and more.
- Typically applied to classes like ApiController, Page, or MessageSub.
#[AutoDiscover]
class ProductController { ... }
Component & Dependency Injection
#[Component]
Inspired by Spring’s component model, this attribute marks a class as a framework-managed component.
- Optional for injection, but required if using method-level decorators (e.g. #[Retry], #[Cache])
- Enables proxying, lifecycle handling, and enhanced discovery.
#[Component]
class MyService { ... }
Proxy-Generating Attributes
These attributes auto-generate proxy classes to wrap, delegate, or intercept logic. The general idea here is to extend one of the base attributes following the needed pattern, then provide any additional configuration properties you need through the attribute constructor.
This way you can easily do meta programming and abstract away cross-cutting concerns like never before!
#[Delegator]
- Replaces the target implementation with a delegated one.
- Useful for interfaces where a proxy is auto-generated by convention or metadata.
#[Delegator(MyImpl::class)]
interface MyService { ... }
#[Decorator]
- Injects custom logic before, after, or on failure of a method.
- Use onEnter, onExit, and onException hooks for modular logic.
#[Decorator(MyLogger::class)]
public function process() { ... }
#[Interceptor]
- Full control over invocation flow. Can defer, short-circuit, retry, timeout, or completely replace execution.
- Powerful for cross-cutting concerns like rate limiting, circuit breaking, or scheduling.
#[Interceptor(MyInterceptor::class)]
public function handle() { ... }
Implementations
Caching, Retry & Resilience
#[Cache]
Caches the return value of a method using pluggable cache stores (Redis, Memcached, etc.).
- Supports TTL, cache key strategy, and tags.
- Method parameters can be used in the key using the curly brackets syntax, as seen in the controllers (we're consistent here!).
#[Cache(key: 'my.data.{id}', ttl: 60)]
public function expensiveQuery(string $id): array { ... }
#[Failover]
Executes a fallback method if the original method fails (throws an exception).
- Accepts a method name or a callable.
- This was made because we could, not necessarily because it was a good idea.
#[Failover(method: ['self', 'fallbackMethod'])]
public function callApi(): string { ... }
#[Retry]
Retries method execution on exception, with optional delays and backoff.
- Ideal for flaky external services.
#[Retry(attempts: 3, delayMs: 200)]
public function fetchFromApi() { ... }
#[CircuitBreaker]
Prevents execution if failures exceed a threshold.
- Protects upstream systems by “breaking the circuit” temporarily.
#[CircuitBreaker(failureThreshold: 5, cooldownSeconds: 30)]
public function queryRemote() { ... }
Telemetry & Metrics
These attributes emit real-time performance and usage metrics. Perfect for dashboards, alerts, and tuning. The framework handles OpenTelemetry formats internally, so you don't need to spend time on configuring observability.
Attribute | What it Tracks |
---|---|
MeterBenchmark | Total execution time, memory, and CPU usage |
MeterCount | Increments a counter on method call |
MeterCpu | CPU usage delta for method |
MeterExceptions | Exception rate/frequency |
MeterLatency | High-resolution method latency |
MeterMemory | Memory usage delta |
MeterPerformance | Aggregates multiple metrics: CPU, memory, latency |
MeterRate | Requests per second over time |
#[MeterLatency]
#[MeterExceptions]
public function handleRequest() { ... }
Making Your Own
Creating a custom attribute is simple. Just extend Interceptor or Decorator and implement the logic. All framework attributes use the same simple patterns provided by the base attributes internally.
Here’s how #[Failover] works internally:
#[Attribute(Attribute::TARGET_METHOD)]
readonly class Failover extends Interceptor
{
public function __construct(
public array|string $method,
) {
parent::__construct(FailoverHandler::class);
}
}
Plug in your own ExecutionHandler
implementation class, and Sugoi takes care of the rest! Discovery, proxy generation, and invocation.