Implementing Service, Repository, Interface, and Policy Patterns in Laravel

Tuğrul Yıldırım
3 min readFeb 26, 2025

--

Laravel is a powerful and flexible PHP framework that provides various ways to structure your application. One of the most effective architectural patterns for maintaining scalability, testability, and maintainability is the Service-Repository Pattern, combined with Interfaces and Policies.

In this article, we will break down each component, explaining its purpose, advantages, and disadvantages, along with real-world Laravel examples.

  1. Repository Pattern in Laravel

What is the Repository Pattern?

The Repository Pattern is a design pattern used to abstract data access logic from business logic. It serves as an intermediary between the database (via Eloquent models) and the application’s services, ensuring that data access code is clean and reusable.

Advantages of Using Repositories:

  • Separation of concerns: Keeps business logic separate from data access logic.
  • Scalability: Helps scale the application by keeping the codebase modular.
  • Testability: Makes unit testing easier by mocking repository classes.
  • Reusability: The same repository can be reused across different parts of the application.

Disadvantages of Using Repositories

  • Extra Complexity: Requires additional classes, which might feel unnecessary for small projects.
  • Learning Curve: Developers unfamiliar with this pattern might need time to adapt.
  • Overhead: In some cases, using repositories adds extra layers of abstraction without much benefit.

Example: Implementing a Base Repository

namespace App\Repositories;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Collection;
abstract class BaseRepository {
protected Model $model;
public function getAll(): Collection {
return $this->model->all();
}
public function find(int $id): ?Model {
return $this->model->find($id);
}
public function create(array $data): Model {
return $this->model->create($data);
}
public function update(int $id, array $data): Model {
$record = $this->model->findOrFail($id);
$record->update($data);
return $record;
}
public function delete(int $id): bool {
return $this->model->destroy($id);
}
}

Example: Extending Base Repository for Employees

namespace App\Repositories\Users\Employees;
use App\Models\User;
use App\Repositories\BaseRepository;
class EmployeeRepository extends BaseRepository {
public function __construct(User $model) {
$this->model = $model;
}
public function usernameExists(string $username): bool {
return $this->model->where('username', $username)->exists();
}
}

2. Interface Usage in Laravel

Why Use Interfaces?

Interfaces define the contract for a class without enforcing implementation details. In Laravel, interfaces are useful for defining expected behavior while keeping the actual implementation flexible.

Advantages of Interfaces :

  • Flexibility: Allows switching implementations without modifying dependent classes.
  • Testability: Simplifies mocking dependencies for unit testing.
  • Clear Contracts: Helps enforce coding standards across large teams.

Example: Defining an Interface for Employee Service

namespace App\Interfaces\Services\Users\Employees;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
interface EmployeeServiceInterface {
public function getAll(): Collection;
public function findOrFail(int $id): Model;
public function create(array $data): Model;
public function update(int $id, array $data): Model;
public function delete(int $id): bool;
}

Binding Interface to Implementation in Laravel Service Provider :

namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Interfaces\Services\Users\Employees\EmployeeServiceInterface;
use App\Services\Users\Employees\EmployeeService;
class AppServiceProvider extends ServiceProvider {
public function register(): void {
$this->app->bind(EmployeeServiceInterface::class, EmployeeService::class);
}
}

3. Service Layer in Laravel

Why Use a Service Layer?

A Service Layer is responsible for handling business logic while keeping controllers clean and manageable.

Advantages of Using a Service Layer :

  • Separation of Concerns: Keeps controllers lightweight.
  • Code Reusability: Business logic is centralized.
  • Easier Testing: Allows unit testing without involving controllers or repositories.

Example: Employee Service Implementation

namespace App\Services\Users\Employees;
use App\Repositories\Users\Employees\EmployeeRepository;
use App\Interfaces\Services\Users\Employees\EmployeeServiceInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Collection;
class EmployeeService implements EmployeeServiceInterface {
protected EmployeeRepository $employeeRepository;
public function __construct(EmployeeRepository $employeeRepository) {
$this->employeeRepository = $employeeRepository;
}
public function getAll(): Collection {
return $this->employeeRepository->getAll();
}
public function findOrFail(int $id): Model {
return $this->employeeRepository->findOrFail($id);
}
}

4. Policy in Laravel

Why Use Policies?

Laravel Policies handle authorization logic, ensuring that users have the necessary permissions to perform specific actions.

Example: Defining a User Policy

namespace App\Policies;
use App\Models\User;class UserPolicy {
public function update(User $user, User $model): bool {
return $user->id === $model->id || $user->hasRole('admin');
}
public function delete(User $user, User $model): bool {
return $user->hasRole('admin');
}
}
namespace App\Providers;
use App\Interfaces\Services\Users\Employees\EmployeeServiceInterface;
use App\Models\User;
use App\Policies\UserPolicy;
use App\Services\Users\Employees\EmployeeService;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// Binding: Interface → Concrete
$this->app->bind(EmployeeServiceInterface::class, EmployeeService::class);
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
$policies = [
// User Policies
User::class => UserPolicy::class,
];
foreach ($policies as $model => $policy) {
Gate::policy($model, $policy);
}
}
}

Conclusion

By implementing Repositories, Interfaces, Services, and Policies, Laravel applications become more structured and maintainable. While these patterns introduce some overhead, they significantly improve scalability, testability, and modularity, making them ideal for large and complex applications.

Would you implement these patterns in your Laravel project? Let us know in the comments!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Tuğrul Yıldırım
Tuğrul Yıldırım

Written by Tuğrul Yıldırım

Full Stack Laravel & React Developer | Building CRM & ERP solutions | Sharing insights on code, automation, and business processes.

No responses yet

Write a response