Make your life simpler using Contracts

Andrea Pollastri
3 min readSep 22, 2023

--

If you are building a Scalable and Mid/Long Time Term Laravel Application you should use Interfaces Contracts to apply SOLID principles to your project.

What is SOLID?
SOLID is an acronym that stands for a set of principles to have 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
Liskov’s Substitution Principle says that 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 that 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. This contrasts to a HeaderInterface, where the supplier will only have a single interface.

A practical example in Laravel to apply the SOLID technique is using Services, for example, we would like to realize a simple CRM application that stores and retrieves Contacts from a “source”. Basically, 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' => 'sting'
]);

$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 blinded to Framework and Model DB drivers.
Now we could move some logic into a Service that implements a “contract”:

<?php

// 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;
}
<?php

// 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 have to bind the ContactServiceInterface to implement the desired concrete class. For example…

// app/Providers/AppServiceProvider.php

public function register() {
...
$this->app->bind(ContactServiceInterface::class, LocalDatabaseContactService::class);
...
}

In this way, every time we will use ContactServiceInterface in our codebase, Laravel will refer to the binded concrete class so we could add other “providers” and easily use them without modify our code.

For example…

<?php

// 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);
$response = curl_exec($ch);
return $response;
}

public function findByEmail(string $email): ?array
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'api.externalcms.com/user/?email='.$email);
curl_setopt($ch, CURLOPT_PRE,true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
return $response;
}
}
// app/Providers/AppServiceProvider.php

public function register() {
...
$this->app->bind(ContactServiceInterface::class, ExternalCrmContactService::class);
...
}

Make your next Contract, now!

--

--