If you are building a scalable, mid/long-term Laravel application you should use interface contracts to apply SOLID principles to your project.
What is SOLID?
SOLID is an acronym that stands for a set of principles for good software design practices:
Single responsibility principle A class should only have one reason to change, which means it should only have one responsibility.
Open/closed principle Objects or entities should be open for extension, but closed for modification.
Liskov substitution principle Each class that inherits from another can be used as its parent without having to know the differences between them.
Interface segregation principle A client should only know the methods they are going to use, and not those they are not going to use.
Dependency inversion principle A role interface is defined by looking at a specific interaction between suppliers and consumers. A supplier component will usually implement several role interfaces, one for each of these patterns of interaction.
A practical example in Laravel to apply the SOLID technique is using Services. Suppose we want to build a simple CRM that stores and retrieves contacts from a "source". The source could be a MySQL database, and the classic way to manage it could be this:
// app/Http/Controllers/ContactsController.php
public function store(Request $request)
{
$request->validate([
'name' => 'string|required',
'email' => 'email|required',
'phone' => 'string',
]);
$contact = new Contact();
$contact->name = $request->name;
$contact->email = $request->email;
$contact->phone = $request->phone;
$contact->save();
}
public function findByEmail(Request $request)
{
$request->validate([
'email' => 'email|required',
]);
return Contact::where('email', $request->email)->first();
}
This logic is strongly bound to the framework and to the model's database driver.
Now we can move that logic into a service that implements a contract:
// app/Contracts/ContactServiceInterface.php
namespace App\Contracts;
interface ContactServiceInterface
{
public function store(string $name, string $email, string $phone): bool;
public function findByEmail(string $email): ?array;
}
// app/Services/LocalDatabaseContactService.php
namespace App\Services;
class LocalDatabaseContactService implements ContactServiceInterface
{
public function store(string $name, string $email, string $phone): bool
{
$contact = new Contact();
$contact->name = $name;
$contact->email = $email;
$contact->phone = $phone;
return $contact->save();
}
public function findByEmail(string $email): ?array
{
$contact = Contact::where('email', $email)->first();
if (! $contact) {
return null;
}
return [
'name' => $contact->name,
'email' => $contact->email,
'phone' => $contact->phone,
];
}
}
Now we bind the ContactServiceInterface to the concrete class we want to use. For example:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->bind(ContactServiceInterface::class, LocalDatabaseContactService::class);
}
This way, every time we use ContactServiceInterface in our codebase Laravel resolves the bound concrete class — so we can add other "providers" and swap them in without modifying the rest of our code.
For example:
// app/Services/ExternalCrmContactService.php
namespace App\Services;
class ExternalCrmContactService implements ContactServiceInterface
{
public function store(string $name, string $email, string $phone): bool
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'api.externalcms.com/store');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'name' => $name,
'email' => $email,
'phone' => $phone,
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
return curl_exec($ch);
}
public function findByEmail(string $email): ?array
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'api.externalcms.com/user/?email=' . $email);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
return curl_exec($ch);
}
}
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->bind(ContactServiceInterface::class, ExternalCrmContactService::class);
}
Make your next contract, now!