paulund

Contextual Attributes

Contextual Attributes

Laravel 11 introduced contextual attributes, a cleaner way to tell the service container which concrete class to inject depending on where it is needed. They replace the verbose AppServiceProvider bindings that were previously required for contextual binding.

The Problem They Solve

Suppose you have a Logger interface with two implementations — one that writes to a file and one that sends logs to a remote service. Different parts of your application need different implementations, but you do not want to wire them up manually every time.

interface Logger
{
    public function log(string $message): void;
}

class FileLogger implements Logger { /* ... */ }
class RemoteLogger implements Logger { /* ... */ }

Using Contextual Attributes

Decorate the constructor parameter with the #[Inject] attribute, specifying which concrete class the container should provide:

use App\Services\Contracts\Logger;
use App\Services\FileLogger;
use App\Services\RemoteLogger;
use Illuminate\Contracts\Foundation\Isolatable;

class UserService
{
    public function __construct(
        #[\Illuminate\Container\Attributes\Inject(FileLogger::class)]
        private Logger $logger,
    ) {}
}

class OrderService
{
    public function __construct(
        #[\Illuminate\Container\Attributes\Inject(RemoteLogger::class)]
        private Logger $logger,
    ) {}
}

No AppServiceProvider bindings are needed. The attribute carries the binding information directly to the point of use.

Registering Custom Attributes

You can create your own attributes that resolve values at injection time. Define an attribute class that implements Illuminate\Contracts\Container\ResolverInterface:

#[\Attribute]
class FromConfig implements \Illuminate\Contracts\Container\ResolverInterface
{
    public function __construct(
        private string $key,
    ) {}

    public function resolve(\Illuminate\Contracts\Container\Container $container): mixed
    {
        return \Illuminate\Support\Facades\Config::get($this->key);
    }
}

Use it anywhere in your application:

class ApiClient
{
    public function __construct(
        #[FromConfig('services.external.base_url')]
        private string $baseUrl,

        #[FromConfig('services.external.timeout')]
        private int $timeout,
    ) {}
}

When to Use Contextual Attributes

  • When different classes need different implementations of the same interface.
  • When you want to inject config values or computed dependencies without a manual binding in a service provider.
  • When you want to keep binding logic co-located with the consuming class rather than in a centralised provider.

When Not to Use Them

  • For application-wide singleton bindings that are the same everywhere — a standard bind() or singleton() call in AppServiceProvider remains the cleaner choice.
  • When the resolved value changes at runtime based on request state. Attributes are resolved once at construction time.

Comparison

// ✅ Laravel 11+ — attribute-based contextual binding
class ReportService
{
    public function __construct(
        #[\Illuminate\Container\Attributes\Inject(CsvExporter::class)]
        private Exporter $exporter,
    ) {}
}

// ❌ Laravel 10 style — verbose provider configuration
// In AppServiceProvider:
$this->app->when(ReportService::class)
    ->needs(Exporter::class)
    ->give(CsvExporter::class);

Both approaches produce identical behaviour. Contextual attributes simply move the declaration closer to where it matters.