paulund

Repository Pattern

Repository Pattern

Repositories / Custom Query Objects

When working with databases in an application, you often need to retrieve, store, and manipulate data. Repositories are a design pattern that provides an abstraction layer between your application and the database, allowing you to interact with data without directly accessing the database.

Here’s an example of using the Repository Pattern in a Laravel application.

namespace App\Repositories;

use App\Models\Post;

class PostRepository implements PostRepositoryInterface
{
    public function getAll(): array
    {
        return Post::all()->toArray();
    }

    public function findById(int $id): ?array
    {
        $post = Post::find($id);
        return $post ? $post->toArray() : null;
    }

    public function create(array $data): array
    {
        $post = Post::create($data);
        return $post->toArray();
    }

    public function update(int $id, array $data): bool
    {
        $post = Post::find($id);
        return $post ? $post->update($data) : false;
    }

    public function delete(int $id): bool
    {
        $post = Post::find($id);
        return $post ? $post->delete() : false;
    }
}

Now, rather than calling the Post model directly, your application can use the repository. This pattern allows you to group together all the logic you need when interacting with the database.

In Laravel applications you can encapsulate the same logic by using a custom Eloquent query builder instead.

In the model, define a newEloquentBuilder method that returns your custom query builder:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = ['title', 'content', 'published_at'];

    public function newEloquentBuilder($query)
    {
        return new PostQueryBuilder($query);
    }
}

You can then define the custom query builder:

namespace App\QueryBuilders;

use Illuminate\Database\Eloquent\Builder;

class PostQueryBuilder extends Builder
{
    public function published()
    {
        return $this->whereNotNull('published_at')->where('published_at', '<=', now());
    }

    public function search(string $term)
    {
        return $this->where('title', 'like', "%{$term}%")
                    ->orWhere('content', 'like', "%{$term}%");
    }
}

You can now use these methods like scopes on the model. To get published posts:

$posts = Post::published()->get();

To search for posts:

$posts = Post::search('laravel')->get();

Why Custom Eloquent Query Builders Are Better Than the Repository Pattern

Simpler and Less Boilerplate:

The repository pattern introduces additional layers (interfaces, bindings, and concrete implementations), which can lead to unnecessary complexity, particularly in smaller projects. Custom query builders let you extend functionality directly within Eloquent, avoiding this overhead.

Leverages Laravel's Built-in Features:

Custom query builders integrate naturally with Eloquent's lifecycle, preserving relationships, scopes, and other ORM features. Repositories, on the other hand, require you to manage these features manually.

Follows Laravel's Active Record Pattern: Eloquent uses the active record pattern, where models are responsible for their own queries. Custom query builders align with this philosophy. Repositories deviate from this approach, which can cause friction with the rest of Laravel's design.

Improved Readability: Custom query builders maintain a clean, fluent syntax directly within model queries, which enhances readability. Repositories can obscure the logic by separating queries into yet another layer.

No Dependency on an Interface:

Repositories rely on interfaces for flexibility, but that flexibility is often unnecessary for most applications. Custom query builders provide the same reusability without requiring an interface or service binding.

When to Use Custom Query Builders Over Repositories

Use custom query builders when you want to keep your codebase simple and stay aligned with Laravel's conventions. Consider repositories if you need to fully decouple the data access layer — for example, when your application must support multiple data sources such as APIs and databases side by side.

In most Laravel applications, custom query builders are sufficient and provide a cleaner, more maintainable approach to encapsulating query logic.