Contracts (Interfaces)
Interfaces and Contracts for creating abstraction layer
Contracts in Laravel are PHP Interfaces used to create abstraction layer. This pattern implements one of SOLID principles (Interface Segregation Principle) and allows you to separate implementation from client code.
Benefits of Using Contracts:
- Reduced coupling between classes and increased cohesion
- Increased testability with easy mocking capability
- More flexibility in swapping implementations
- Ability to swap implementation without changing client code
- Improved code maintainability and readability
- Better implementation of Dependency Inversion Principle
Usage:
- To define contract between classes and create abstraction
- For tests (mocking)
- To create plugin architecture
- For separation of concerns
Laravel Contracts Examples:
- <code>Illuminate\Contracts\Cache\Repository</code> - for cache operations
- <code>Illuminate\Contracts\Queue\Queue</code> - for queue operations
- <code>Illuminate\Contracts\Mail\Mailer</code> - for email sending
- <code>Illuminate\Contracts\Filesystem\Filesystem</code> - for file operations
- <code>Illuminate\Contracts\Events\Dispatcher</code> - for event dispatching
Examples
Using Contract
<?php
use Illuminate\Contracts\Cache\Repository as Cache;
class UserService
{
protected $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
public function getUser($id)
{
return $this->cache->remember("user.{$id}", 3600, function () use ($id) {
return User::find($id);
});
}
}
Instead of using Cache facade directly, we use Contract which allows mocking in tests.
Creating Custom Contract
<?php
namespace App\Contracts;
interface PaymentGateway
{
public function charge($amount, $token);
public function refund($transactionId);
}
A custom Contract that can have different implementations.
Implementing Contract
<?php
namespace App\Services;
use App\Contracts\PaymentGateway;
class StripePaymentGateway implements PaymentGateway
{
public function charge($amount, $token)
{
// Stripe implementation
return ['status' => 'success', 'transaction_id' => '...'];
}
public function refund($transactionId)
{
// Stripe refund implementation
return ['status' => 'refunded'];
}
}
An implementation of Contract that can be bound in Service Container.
Binding Contract in Service Provider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;
class PaymentServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(
PaymentGateway::class,
StripePaymentGateway::class
);
}
}
In Service Provider, we bind Contract to implementation.
Using Laravel Contracts
<?php
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Queue\Queue;
class NotificationService
{
public function __construct(
private Mailer $mailer,
private Queue $queue
) {}
public function send($user, $message)
{
$this->queue->push(function () use ($user, $message) {
$this->mailer->send('emails.notification', ['message' => $message], function ($m) use ($user) {
$m->to($user->email)->subject('Notification');
});
});
}
}
Using Laravel Contracts for mail and queue which allows mocking in tests.
Contract with Multiple Implementations
<?php
namespace App\Contracts;
interface StorageInterface
{
public function put($path, $contents);
public function get($path);
public function delete($path);
}
// Implementation 1
class LocalStorage implements StorageInterface { /* ... */ }
// Implementation 2
class S3Storage implements StorageInterface { /* ... */ }
// Implementation 3
class AzureStorage implements StorageInterface { /* ... */ }
A Contract can have multiple implementations that can be conditionally bound.
Contract in Testing
<?php
namespace Tests\Unit;
use App\Services\UserService;
use Illuminate\Contracts\Cache\Repository;
use Mockery;
class UserServiceTest extends TestCase
{
public function test_get_user_caches_result()
{
$cache = Mockery::mock(Repository::class);
$cache->shouldReceive('remember')
->once()
->andReturn(['id' => 1, 'name' => 'John']);
$service = new UserService($cache);
$result = $service->getUser(1);
$this->assertEquals('John', $result['name']);
}
}
In tests you can mock Contract to isolate dependencies.
Contract with Type Hints
<?php
namespace App\Services;
use App\Contracts\PaymentGateway;
class OrderService
{
public function __construct(
private PaymentGateway $gateway
) {}
public function processPayment($amount, $token)
{
return $this->gateway->charge($amount, $token);
}
}
// Laravel automatically resolves PaymentGateway from container
By type-hinting Contract in constructor, Laravel automatically resolves it from container.
Use Cases
- Creating abstraction layer between business logic and infrastructure layer
- Swapping implementations at runtime based on environment or configuration
- Creating plugin architecture that allows adding new implementations
- Test writing with mocking dependencies
- Creating strategy pattern using contracts
- Separation of concerns in architecture
Common Mistakes
- Using concrete classes directly instead of contracts causing tight coupling
- Creating too large contracts violating Interface Segregation Principle
- Not binding contract in Service Provider causing errors
- Using facade instead of contract reducing testability
- Creating contract for every small thing which is over-engineering
- Not documenting contract methods causing confusion
Best Practices
- Always use contracts for dependencies, not concrete classes
- Keep contracts small and focused (Interface Segregation Principle)
- Always bind contract in Service Provider
- Use Laravel Contracts instead of facade in dependency injection
- Document contract methods with PHPDoc
- Use contracts to create plugin architecture
- Use contracts for mocking in tests
Edge Cases
- Contract with circular dependencies requiring lazy loading
- Contract in queue jobs which have different container instance
- Contract with multiple implementations requiring contextual binding
- Contract in console commands which may have different context
- Overriding contract binding at runtime which may cause issues
Performance Notes
- Using contracts has no negative impact on performance - it's just abstraction layer
- Contract resolution with type-hinting is faster than string-based resolution
- Use deferred providers for rarely used contracts
- Laravel contracts caching in production improves performance
Security Notes
- Ensure contract implementations follow security best practices
- Use contracts for separation of concerns in security layers
- Ensure sensitive operations in contracts are properly abstracted
Interview Points
- What is the difference between Contract and Facade?
- Why should we use Contracts instead of concrete classes?
- What is Interface Segregation Principle and how is it related to Contracts?
- How can you mock a Contract in tests?
- What are the benefits of using Laravel Contracts?
- When should you create a Contract and when not?
Version Notes
- Laravel 11.x: Improved performance in contract resolution
- Laravel 11.x: Better support for PHP 8.2+ attributes in contracts
- Laravel 10.x: Added new contracts for HTTP client
- Laravel 9.x: Improved contract caching for better performance