Contracts (interfaces as contracts) — Laravel's Contracts\* namespace
Concept
Laravel's Illuminate\Contracts namespace contains a suite of interfaces that define the framework's internal API. They are the concrete expression of the Dependency Inversion Principle in Laravel's architecture — consuming code depends on contracts, not on specific Eloquent, Redis, or Queue implementations.
Why contracts matter: If your application service type-hints \Illuminate\Contracts\Cache\Repository instead of \Illuminate\Cache\RedisStore, it works with any cache driver. Swapping to an array driver for tests requires only a container binding change — no modification to the service class.
Laravel Contracts vs PHP-FIG PSR interfaces: PHP-FIG defines cross-framework interoperability standards (PSR-7, PSR-11, PSR-14). Laravel's contracts are framework-internal and richer — they include Laravel-specific features like cache tags, queue priorities, and broadcast channels that PSR interfaces don't cover. Many Laravel contracts loosely align with PSR interfaces where they overlap.
Discovering contracts: The Illuminate\Contracts namespace mirrors the service container's binding map. app('cache') returns an implementation of \Illuminate\Contracts\Cache\Factory. Type-hint the contract in your class constructor and the container resolves the correct implementation automatically.
Programming to contracts in practice: In a controller, type-hint CacheRepository $cache — Laravel injects the configured cache driver. In tests, bind an ArrayCache to CacheRepository::class and your controller uses memory cache without Redis infrastructure.
Code Example
<?php
declare(strict_types=1);
// Using Laravel contracts in application code
namespace App\Services;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
use Illuminate\Contracts\Mail\Mailer as MailerContract;
use Illuminate\Contracts\Queue\Queue as QueueContract;
use Illuminate\Contracts\Auth\Guard as AuthGuard;
class NotificationService
{
public function __construct(
private readonly CacheRepository $cache,
private readonly MailerContract $mailer,
private readonly QueueContract $queue,
) {}
public function notify(int $userId, string $message): void
{
$key = "notification:sent:{$userId}";
// Skip if already notified in last 10 min (uses contract, not concrete)
if ($this->cache->has($key)) return;
$this->queue->push(new SendNotificationJob($userId, $message));
$this->cache->put($key, true, 600);
}
}
// Testing with a fake implementation
class FakeCache implements CacheRepository
{
private array $store = [];
public function has($key): bool { return isset($this->store[$key]); }
public function get($key, $default = null): mixed { return $this->store[$key] ?? $default; }
public function put($key, $value, $ttl = null): bool
{
$this->store[$key] = $value;
return true;
}
// ... other required methods
}
// In a test
$fakeCache = new FakeCache();
$service = new NotificationService($fakeCache, $fakeMail, $fakeQueue);
$service->notify(1, 'Hello');
assert($fakeCache->has('notification:sent:1'));
// Laravel ServiceProvider binding
// In AppServiceProvider::register():
// $this->app->bind(\Illuminate\Contracts\Cache\Repository::class, \App\Infrastructure\DatabaseCache::class);
// Now anywhere CacheRepository is type-hinted, DatabaseCache is injected