# Attribute providers



Recorders use **attribute providers** to turn framework-specific objects (a Symfony request, a Console input, a queued job) into the attributes that Flare records on a span. Going through a provider instead of accepting framework objects directly lets `flare-client-php` stay framework-agnostic while still shipping ready-to-use helpers for the common stacks.

You hand a provider to a recorder when you start or end a span, and the recorder calls the provider to get the attributes it needs. For the common cases, recorders expose `recordStartFrom*` and `recordEndFrom*` helpers that build the matching provider for you. When you work with a framework or object that Flare does not ship a provider for, you write your own.

The base contract is small. Every provider implements `AttributesProvider`:

```php
namespace Spatie\FlareClient\Contracts;

interface AttributesProvider
{
    /** @return array<string, mixed> */
    public function toArray(): array;
}
```

Each recorder accepts a more specific provider contract that adds methods for the data the recorder needs.

| Contract | Used by | Methods beyond `toArray()` |
|---|---|---|
| `RequestAttributesProvider` | `RequestRecorder` | `url()`, `path()`, `method()` |
| `ResponseAttributesProvider` | `RequestRecorder` (on `recordEnd`) | `toArray()` only |
| `RouteAttributesProvider` | `RoutingRecorder`, `RequestRecorder` | `route()` |
| `CommandAttributesProvider` | `CommandRecorder` | `command()`, `commandClass()` |
| `JobAttributesProvider` | `JobRecorder`, `QueueRecorder` | `jobName()`, `jobClass()` |
| `UserAttributesProvider` (abstract base class) | `RequestRecorder` (on `recordEnd`) | `id()`, `fullName()`, `email()`, `attributes()` |

## Built-in providers

`flare-client-php` ships with implementations for plain PHP and for Symfony's HttpFoundation / Console components. They live under `Spatie\FlareClient\AttributesProviders\`:

| Provider | Builds attributes from |
|---|---|
| `PhpRequestAttributesProvider` | PHP superglobals (`$_SERVER`, `$_GET`, etc.) |
| `SymfonyRequestAttributesProvider` | A Symfony `HttpFoundation\Request` |
| `PhpResponseAttributesProvider` | A defined status code, body size, and header set |
| `SymfonyResponseAttributesProvider` | A Symfony `HttpFoundation\Response` |
| `PhpRouteAttributesProvider` | A route pattern, method, and handler name |
| `PhpCommandAttributesProvider` | A command name, class, and arguments array |
| `SymfonyInputCommandAttributesProvider` | A Symfony Console `InputInterface` |
| `PhpJobAttributesProvider` | A job name and class |
| `UserAttributesProvider` (abstract) | The user object you pass to its constructor |
| `EmptyUserAttributesProvider` | A no-op for cases where there is no user |
| `GitAttributesProvider` | The git working copy at `applicationPath` |

Each recorder also exposes `recordStartFrom*` and `recordEndFrom*` helpers that build the matching provider from common framework objects. For example:

```php
$flare->request()->recordStartFromSymfonyRequest($request);
$flare->command()->recordStartFromSymfonyInput(
    'my:command', 
    $input, 
    MyCommand::class
);
$flare->job()->recordStartFromJob(
    'SendWelcomeEmail', 
    SendWelcomeEmail::class
);
```

If you do not have one of those framework objects, build a provider yourself and pass it to `recordStart()` or `recordEnd()`.

## Writing a custom attributes provider

Implement the contract that matches the recorder you want to feed. As an example, here is a minimal request provider for a hypothetical framework that exposes the request through a `MyHttpRequest` object:

```php
use Spatie\FlareClient\Contracts\RequestAttributesProvider;

class MyHttpRequestAttributesProvider implements RequestAttributesProvider
{
    public function __construct(protected MyHttpRequest $request)
    {
    }

    public function url(): string
    {
        return $this->request->fullUrl();
    }

    public function path(): ?string
    {
        return $this->request->path();
    }

    public function method(): string
    {
        return $this->request->method();
    }

    public function toArray(): array
    {
        return [
            'url.full' => $this->url(),
            'url.path' => $this->path(),
            'http.request.method' => $this->method(),
            'http.request.headers' => $this->request->headers(),
        ];
    }
}
```

Pass it to the recorder:

```php
$flare->request()->recordStart(new MyHttpRequestAttributesProvider($request));
```

The same approach applies to every recorder. Implement the contract, return the attribute keys documented in the [protocol events](/docs/protocol/general/events), and the recorder will merge them into the span.

## Describing the entry point handler

Some recorders also need to know who the [entry point](/docs/protocol/general/entry-points) handler is (the controller, command class, or job class). When the same provider can describe both the framework object and the handler, implement the optional `EntryPointHandlerProvider` interface alongside the main contract:

```php
use Spatie\FlareClient\Contracts\EntryPointHandlerProvider;
use Spatie\FlareClient\Contracts\RouteAttributesProvider;

class MyRouteAttributesProvider implements RouteAttributesProvider, EntryPointHandlerProvider
{
    public function __construct(protected MyRoute $route)
    {
    }

    public function route(): ?string
    {
        return $this->route->pattern();
    }

    public function toArray(): array
    {
        return ['http.route' => $this->route()];
    }

    public function entryPointHandlerIdentifier(): ?string
    {
        return "{$this->route->method()} {$this->route->pattern()}";
    }

    public function entryPointHandlerName(): ?string
    {
        return $this->route->controller();
    }

    public function entryPointHandlerType(): ?string
    {
        return 'my_framework_controller';
    }
}
```

The recorders detect the interface and call `setHandler()` on the entry point automatically. See [Entry points](/docs/protocol/general/entry-points) for the attribute schema and the recommended `handler.type` values.
