Introduction
As PHP applications grow, one of the first problems developers face is messy data access logic. Queries start appearing inside controllers, services, jobs, commands, and sometimes even views. At first, this may feel fast and simple. But over time, it becomes harder to maintain, harder to test, and harder to change.
The Repository Pattern is a common solution to this problem.
The main idea is simple: instead of spreading database queries across your application, you place them inside a dedicated class called a repository. This repository becomes the central place for reading and writing data related to a specific model or domain concept.
For example, instead of writing user queries directly inside a controller, you can create a UserRepository and move those queries there.
What Is the Repository Pattern?
The Repository Pattern is a design pattern that separates the logic of retrieving and storing data from the rest of the application.
In simple words, a repository acts like a collection of objects. Your application asks the repository for data, and the repository decides how to get that data.
The controller or service does not need to know whether the data comes from MySQL, PostgreSQL, an API, Redis, or another source. It only talks to the repository.
Example:
$user = $this->userRepository->findByEmail($email);The calling code does not care how findByEmail() works internally. It only cares that it receives the correct user.
Why Use the Repository Pattern?
The Repository Pattern helps you keep your application clean and organized.
Without a repository, your controller might look like this:
public function show(string $email)
{
$user = User::where('email', $email)
->where('is_active', true)
->firstOrFail();
return view('users.show', compact('user'));
}This works, but the controller now knows too much about how users are queried.
With a repository, the controller becomes cleaner:
public function show(string $email)
{
$user = $this->userRepository->findActiveUserByEmail($email);
return view('users.show', compact('user'));
}Now the query logic is hidden inside the repository.
Example Repository in PHP
A simple repository could look like this:
namespace App\Repositories;
use App\Models\User;
class UserRepository
{
public function findByEmail(string $email): ?User
{
return User::query()
->where('email', $email)
->first();
}
public function findActiveUserByEmail(string $email): User
{
return User::query()
->where('email', $email)
->where('is_active', true)
->firstOrFail();
}
public function create(array $data): User
{
return User::query()->create($data);
}
public function update(User $user, array $data): User
{
$user->update($data);
return $user->refresh();
}
}This class is responsible for user-related database operations.
Instead of repeating the same queries in multiple places, you can reuse these methods throughout your application.
Using the Repository in a Controller
You can inject the repository into your controller:
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UserController
{
public function __construct(
protected UserRepository $userRepository
) {}
public function show(string $email)
{
$user = $this->userRepository->findActiveUserByEmail($email);
return view('users.show', compact('user'));
}
}This keeps the controller focused on handling the request and returning a response.
The controller no longer needs to know the details of the database query.
Repository Pattern with Interfaces
In larger applications, developers often create an interface for the repository.
Example:
namespace App\Contracts\Repositories;
use App\Models\User;
interface UserRepositoryInterface
{
public function findByEmail(string $email): ?User;
public function findActiveUserByEmail(string $email): User;
public function create(array $data): User;
public function update(User $user, array $data): User;
}Then the repository implements the interface:
namespace App\Repositories;
use App\Contracts\Repositories\UserRepositoryInterface;
use App\Models\User;
class UserRepository implements UserRepositoryInterface
{
public function findByEmail(string $email): ?User
{
return User::query()
->where('email', $email)
->first();
}
public function findActiveUserByEmail(string $email): User
{
return User::query()
->where('email', $email)
->where('is_active', true)
->firstOrFail();
}
public function create(array $data): User
{
return User::query()->create($data);
}
public function update(User $user, array $data): User
{
$user->update($data);
return $user->refresh();
}
}In Laravel, you can bind the interface to the implementation inside a service provider:
use App\Contracts\Repositories\UserRepositoryInterface;
use App\Repositories\UserRepository;
public function register(): void
{
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
}Now you can inject the interface instead of the concrete class:
public function __construct(
protected UserRepositoryInterface $userRepository
) {}This makes the code more flexible and easier to replace later.
When Is the Repository Pattern Useful?
The Repository Pattern is useful when your application has complex data access logic.
For example, it can help when:
The same queries are used in multiple places
You want to keep controllers and services clean
You need to test business logic without depending directly on the database
You have complex filters, search logic, or reporting queries
You may change the data source in the future
You want a more structured enterprise-style architecture
In these cases, repositories can make your codebase easier to understand and maintain.
When You Should Avoid It
The Repository Pattern is not always necessary.
In Laravel, Eloquent already provides a powerful Active Record implementation. For simple CRUD applications, adding repositories for every model can create unnecessary complexity.
For example, this may be overengineering:
$postRepository->find($id);If the repository only wraps basic Eloquent methods without adding any value, it may not be needed.
A bad repository is usually just a thin wrapper around Eloquent:
public function all()
{
return User::all();
}
public function find(int $id)
{
return User::find($id);
}This does not improve the application much. It only adds more files and more abstraction.
A good repository should contain meaningful query logic.
Repository Pattern vs Action Pattern
The Repository Pattern and Action Pattern solve different problems.
The Action Pattern is usually used for business operations. For example:
CreateUserAction
CancelSubscriptionAction
PublishArticleActionThe Repository Pattern is used for data access. For example:
UserRepository
ArticleRepository
InvoiceRepositoryA clean application can use both patterns together.
Example:
class CreateUserAction
{
public function __construct(
protected UserRepositoryInterface $userRepository
) {}
public function execute(array $data): User
{
return $this->userRepository->create($data);
}
}In this example, the action handles the business process, while the repository handles the database operation.
Best Practices for Using Repositories in PHP
To use the Repository Pattern correctly, keep these best practices in mind.
First, do not create repositories automatically for every model. Use them when they actually improve the structure of your application.
Second, keep business logic out of repositories. Business rules should usually live in actions, services, or domain classes. The repository should focus mainly on data access.
Third, use clear method names. A method like findActiveUserByEmail() is better than a generic method like getUser().
Fourth, avoid making one giant repository. If a repository becomes too large, it may be a sign that your domain logic needs to be separated better.
Finally, do not hide everything behind abstraction just for the sake of abstraction. The goal is cleaner code, not more complicated code.
Conclusion
The Repository Pattern is one of the most common patterns in PHP and Laravel applications. It helps organize data access logic, keeps controllers and services cleaner, and makes complex queries easier to reuse.
However, it should be used carefully. In simple applications, Laravel’s Eloquent may already be enough. But in larger projects, especially enterprise applications, repositories can bring more structure and maintainability.
A good rule is simple: use the Repository Pattern when it removes duplication, improves readability, or makes your business logic easier to test.
Used correctly, the Repository Pattern can be a powerful tool for writing cleaner and more professional PHP applications.