Skip to main content

Custom Context Resolvers

Context resolvers run at ExtensionStage::RESOLVE_CONTEXT (priority 200) and automatically attach namespaced runtime data to an entry's context attribute. Return null to skip silently — the resolver is simply not applied to that entry.

The pattern

Extend AbstractContextResolver and implement contextKey() and resolve():

use Chronicle\Context\AbstractContextResolver;
use Chronicle\Entry\PendingEntry;

class TenantContextResolver extends AbstractContextResolver
{
public function __construct(
private readonly TenantManager $tenants,
) {}

public function contextKey(): string
{
return 'tenant';
}

public function resolve(PendingEntry $entry): ?array
{
$tenant = $this->tenants->current();

if ($tenant === null) {
return null; // skip — no tenant in this context
}

return [
'id' => $tenant->id,
'slug' => $tenant->slug,
];
}
}

AbstractContextResolver handles the rest: it calls resolve(), skips if null is returned, and merges the result under contextKey() without overwriting any existing context keys set by the application.

The resulting context for the above example:

{
"tenant": {
"id": 12,
"slug": "acme"
}
}

Returning null to skip

Return null from resolve() to skip the resolver entirely for that entry. This is the correct pattern when the resolver is not applicable in the current runtime (e.g. a request resolver running inside a queue worker):

public function resolve(PendingEntry $entry): ?array
{
if (app()->runningInConsole()) {
return null;
}

// ...
}

AbstractContextResolver::process() checks for null and skips the merge — it does not write an empty array or a null value under the key.

Redacting sensitive values

When resolving data that may contain sensitive query parameters or headers, strip or redact them explicitly. Follow the pattern used by RequestContextResolver:

private const SENSITIVE_PARAMS = ['password', 'token', 'secret', 'api_key'];

protected function sanitizeParams(array $params): array
{
foreach (self::SENSITIVE_PARAMS as $key) {
if (array_key_exists($key, $params)) {
$params[$key] = '[redacted]';
}
}

return $params;
}

Registration

Add to config/chronicle.php (uncomment the optional resolver block):

'extensions' => [
// built-in validators...
TenantContextResolver::class,
],

Or at runtime:

Chronicle::extendEntry(TenantContextResolver::class);

See also