Paulund
Subscribe

Contextual Attributes in Laravel

16 March 2026, 2 min read

From the archive. Versions and APIs may have moved on since I wrote this.

On this page
  1. The Problem They Solve
  2. Using Contextual Attributes
  3. Registering Custom Attributes
  4. When to Use Contextual Attributes
  5. When Not to Use Them
  6. Comparison

Since Laravel 11, you can use contextual attributes 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, and the approach has become a standard part of the Laravel toolkit.

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.

PHP
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:

PHP
use App\Services\Contracts\Logger;
use App\Services\FileLogger;
use App\Services\RemoteLogger;

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:

PHP
#[\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:

PHP
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

PHP
// Attribute-based contextual binding
class ReportService
{
    public function __construct(
        #[\Illuminate\Container\Attributes\Inject(CsvExporter::class)]
        private Exporter $exporter,
    ) {}
}

// Legacy provider-based approach
// In AppServiceProvider:
$this->app->when(ReportService::class)
    ->needs(Exporter::class)
    ->give(CsvExporter::class);

Both approaches produce identical behaviour. The attribute-based approach moves the declaration closer to where it matters, which makes it easier to understand a class's dependencies at a glance without hunting through service providers.

Next in Laravel

16 March 2026Laravel Pint: configuration, rules and CI checks

Tags

  • Laravel
  • Dependency injection
  • Best practices