DEV Community

Cover image for Repository Pattern in Laravel
David
David

Posted on • Updated on • Originally published at bluecollardev.io

Repository Pattern in Laravel

In most web applications accessing a database makes up a substantial portion of the code base. To avoid sprinkling plain SQL queries all over our application logic, we rely on abstractions, which hide the mechanics of the data access behind PHP methods.

There are several patterns to structure data access, "Active Record" and "Repository" the two most well-known. In this blog post I'll explain them specifically in the context of the Laravel framework. The discussion of the pros and cons of using the Repository pattern will follow in separate blog posts.

Active Record

By default, Laravel uses the Active Record pattern. Every Laravel programmer intuitively uses it, because it's implemented in with the abstract Model base class and models usually inherit from it. Let's look at an example:

use Illuminate\Database\Eloquent\Model;

/**
 * @property int    $id
 * @property string $first_name
 * @property string $last_name
 */
class Person extends Model {
}

// --- Usage:

$person = new Person();
$person->first_name = 'Jack';
$person->last_name = 'Smith';
$person->save();
Enter fullscreen mode Exit fullscreen mode

Of course you can read and write the properties you created on Person. But to save the model, you can also call methods directly on the model. There is no need for another object -- the model already provides all the methods to access the corresponding database table.

This means, that the domain model combines your custom properties and methods with all the data access methods in the same class. The second part is achieved by inheriting from Model.

Takeaways:

  • πŸ‘‰ Active Record combines the domain model with data access functionality.
  • πŸ‘‰ Laravel uses the Active Record pattern and implements it with the Model class.

Repository

The Repository pattern is an alternative to the Active Record pattern. It also provides an abstraction to handle data access. But more generally, it can be seen as a conceptual repository or collection of domain objects.

In contrast to the Active Record pattern, the Repository pattern separates the data access from the domain model. It provides a high-level interface, where you create, read, update and delete your domain models, without having to think about the actual underlying data store.

The underlying Repository implementation could access a database by building and executing SQL queries, a remote system via a REST API or might just manage an in-memory data structure which contains all the domain models. This can be useful for testing. The key part of the Repository is the high-level interface it provides to the rest of the code.

Takeaways:

  • πŸ‘‰ A Repository represents a conceptual collection of domain objects.
  • πŸ‘‰ It has the single responsibility of encapsulating data access with a high-level interface.
  • πŸ‘‰ Laravel does not provide specific helpers to implement the Repository pattern.

When it comes to implementing the Repository pattern in Laravel, I mainly see two variants.

Variant 1: Specific Methods

In the first variant, the repository methods are focused and specific. The names explain what the caller gets and the options to parameterize the underlying query are limited.

class InvoiceRepository {

    public function findAllOverdue(Carbon $since, int $limit = 10): Collection {
        return Invoice::where('overdue_since', '>=', $since)
            ->limit($limit)
            ->orderBy('overdue_since')
            ->get();
    }

    public function findInvoicedToCompany(string $companyId): Collection {
        return Invoice::where('company_id', $companyId)
            ->orderByDesc('created_at')
            ->get();
    }
}
Enter fullscreen mode Exit fullscreen mode

The advantage of this approach lies in the expressiveness of the methods. When the code is read, it is clear what to expect from the methods and how to call them. This leads to fewer mistakes. The Repository methods are easy to test, because the parameters are narrow.

A disadvantage of this approach is, that you might end up with lots of methods in your Repository. Because the methods can't be reused easily, you have to add additional methods for new use cases.

Takeaways:

  • πŸ‘‰ The Repository pattern can be implemented by a class which provides specific methods.
  • πŸ‘‰ Each method wraps one query, exposing only the necessary parameters.
  • πŸ‘‰ Pros: readability and testability
  • πŸ‘‰ Cons: lack of flexibility and lower reusability

Variant 2: General Methods

The approach on the other side of the spectrum is to provide general methods. This leads to less methods. But the methods have a large API surface, because each method can be called with various combinations of arguments.

The key problem which emerges is the parameter representation. The representation should guide the callers to understand the method signature and avoid invalid inputs. For that, you can introduce a special class, for example with the Query Object pattern.

But what I most often see in practice is a mix of scalar parameters and PHP arrays. The caller can pass completely invalid inputs and the type array alone does not say much about what to pass. But if used carefully you can succeed with this light-weight approach and avoid more cumbersome abstractions.

class InvoiceRepository {

    public function find(array $conditions, string $sortBy = 'id', string $sortOrder = 'asc', int $limit = 10): Collection {
        return Invoice::where($conditions)
            ->orderBy($sortBy, $sortOrder)
            ->limit($limit)
            ->get();
    }
}

// --- Usage:

$repo = new InvoiceRepository();
$repo->find(['overdue_since', '>=', $since], 'overdue_since', 'asc');
$repo->find(['company_id', '=', $companyId], 'created_at', 'asc', 100);
Enter fullscreen mode Exit fullscreen mode

This approach alleviates the problems of the first approach: you get less Repository methods which are more flexible and can be reused more often.

On the negative side, the Repository becomes harder to test because there are more cases to cover. The method signatures are harder to understand and because of that, the caller can make more mistakes. Also, you introduce some kind of query object representation. Whether it is explicit or implicit (like with arrays), your Repository implementation and its callers will get coupled to it.

Takeaways:

  • πŸ‘‰ The Repository pattern can be implemented with a class which provides general methods.
  • πŸ‘‰ The challenge lies in the representation of the method parameters.
  • πŸ‘‰ Pros: greater flexibility and higher reusability
  • πŸ‘‰ Cons: harder to test, less readable, coupling to parameter representation

Of course the two approaches can be combined. Maybe you want some specific methods for complicated queries and a few general methods for simple where queries.

Implementation

Now, let's talk about how to implement the method bodies.

In the examples above, I used the methods from the Model class to get access to an Eloquent query builder. So the Repository implementation actually used the Active Record pattern as an implementation.

You don't have to do that. You could use the DB facade, to get a query builder, while avoiding the Model class. Or you can write SQL queries directly:

class InvoiceRepository {

    public function findAllOverdue(Carbon $since, int $limit = 10): Collection {
        return DB::table('invoices')
            ->where('overdue_since', '>=', $since)
            ->limit($limit)
            ->orderBy('overdue_since')
            ->get();
    }

    public function findInvoicedToCompany(string $companyId): Collection {
        return DB::select('SELECT * FROM invoices
                           WHERE company_id = ?
                           ORDER BY created_at
                           LIMIT 100', [$companyId]);
    }
}
Enter fullscreen mode Exit fullscreen mode

The great thing about the Repository pattern is, that the implementation can be anything, as long as it fulfills the interface. You could also manage objects in-memory or wrap (and cache) an API.

But most often, the underlying data store is a SQL database. And for accessing it, you can choose the best implementation on a per-method basis. For performance critical or complex queries, you might want to use SQL statements directly. Simpler queries can use the Eloquent query builder.

When you don't use the Model class to implement your Repository, you might think about not inheriting from it in your models. But this works against a lot of built-in Laravel magic and is in my opinion not a pragmatic approach.

Takeaways:

  • πŸ‘‰ The Repository pattern is flexible and allows for various implementation techniques.
  • πŸ‘‰ In Laravel, the Eloquent query builder is a pragmatic choice when accessing databases.

Interfaces

Another option you have is, whether to introduce an interface or not. The example above can be separated in an interface and one or more implementations:

// --- Interface:

public interface InvoiceRepositoryInterface {

    public function findAllOverdue(Carbon $since, int $limit = 10): Collection;

    public function findInvoicedToCompany(string $companyId): Collection;
}

// --- Concrete class, implementing the interface:

class InvoiceRepository implements InvoiceRepositoryInterface {

    public function findAllOverdue(Carbon $since, int $limit = 10): Collection {
        // implementation
    }

    public function findInvoicedToCompany(string $companyId): Collection {
        // implementation
    }
}
Enter fullscreen mode Exit fullscreen mode

Adding an interface is an additional indirection and is not necessarily good. If your application is the only user of the Repository and you don't expect to have more than one implementation of it, I don't see the point of introducing an interface. For testing, the repository can be mocked with PHPUnit, as long as it's not marked as final.

If you know you are going to have multiple implementation you should definitively use an interface. Different implementations may occur if you are writing a package which is going to be used in multiple projects or if you want a special Repository implementation for testing.

In order to benefit from Laravel's dependency injection, you have to bind the concrete implementation to the interface. This has to be done in the register method of a Service Provider.

use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider {

    public function register(): void {
        $this->app->bind(InvoiceRepositoryInterface::class, InvoiceRepository::class);
    }
}
Enter fullscreen mode Exit fullscreen mode

Takeaways:

  • πŸ‘‰ An interface can further decouple the Repository from the rest of the code.
  • πŸ‘‰ Use Repository interfaces, when you expect to have more than one concrete class implementing it.
  • πŸ‘‰ In Laravel, bind the concrete class to the interface in a Service Provider.

πŸ‘‰ Let me know how you would implement the Repository pattern in Laravel.

πŸ‘‰ In the next post, I'll explore the advantages of using the Repository pattern.

Discussion (10)

Collapse
shokme profile image
Jordan Massart

Hello David,

Thank you for this well presented article :)
I'm not a fan of repository pattern, but I recently do a kind of it by splitting Eloquent and Collection from the model.

I don't know if you are aware of it so I'm sharing it to you:

Models/User.php

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

public function newCollection(array $models = [])
{
    return new UserCollection($models);
}
Enter fullscreen mode Exit fullscreen mode

UserEloquentBuilder.php

<?php

namespace App\Models\EloquentBuilders;

use Illuminate\Database\Eloquent\Builder;

class UserEloquentBuilder extends Builder
{
    public function member()
    {
        $this->where('type', 'member');

        return $this;
    }
}
Enter fullscreen mode Exit fullscreen mode

UserCollection.php

<?php

namespace App\Models\Collections;

use Illuminate\Database\Eloquent\Collection;

class UserCollection extends Collection
{
    public function active()
    {
        return $this->where('status', 'active');
    }
}

Enter fullscreen mode Exit fullscreen mode
User::member()->get()->active();
Enter fullscreen mode Exit fullscreen mode

I discovered this possibilty by reading this article

Collapse
davidrjenni profile image
David Author

I appreciate your feedback very much!

Thank you for this well presented article :)
I'm not a fan of repository pattern, but I recently do a kind of it by splitting Eloquent and Collection from the model.

I don't know if you are aware of it so I'm sharing it to you:

No, I wasn't aware about this, thanks for sharing the link. It looks neat and I hope to try it out soon.

I certainly see why it might be more popular in the Laravel community. In the end, the design decisions depend a lot on personal taste, the specific project and other factors. But whatever helps you structure your project is great!

Collapse
bdelespierre profile image
Benjamin Delespierre

Thanks for this article @davidrjenni , very informative and well-written.

One thing I believe is missing is the whole point of having repositories; to abstract entity management from storage layer. One is able to substitute the storage layer at will. This is immensely helpful when you sync data accross several API, here's an example:

<?php

interface OrderEntity
{
    public function getUuid(): ?string;

    public function getAmount(): int;
}

class Order extends Model implements OrderEntity
{
    // ...
}

interface OrderRepository
{
    public function has(OrderEntity $order): bool;

    public function find(string $uuid): OrderEntity;

    public function save(OrderEntity $order): void;
}

class OrderDatabaseRepository implements OrderRepository
{
    // Saves the order on the database using Eloquent
}

class OrderStripeRepository implements OrderRepository
{
    // Sends the order to Stipe using its API
}

class OrderHubspotRepository implements OrderRepository
{
    // Stores a copy of the order on Hubspot for the sales dept
}
Enter fullscreen mode Exit fullscreen mode

Moving data around becomes as easy as:

$stripeRepository->save($databaseRepository->find($uid));
Enter fullscreen mode Exit fullscreen mode

If the API you're talking to is REST compliant, implementing a repository that talks to it is a breeze (no state to manage.)

There are many other reasons why you would want to use repositories with Laravel. Especially if you're designing with DDD. You can read more about that on my dev.to page πŸ˜‰

Collapse
davidrjenni profile image
David Author

Thank you, @bdelespierre for your feedback, much appreciated πŸ™.

One thing I believe is missing is the whole point of having repositories; to abstract entity management from storage layer.

I'll expand on the reasons I think the Repository pattern is helpful in the next blog post. I wanted to keep this post focused on the general concept and how to implement it in Laravel.

You can read more about that on my dev.to page πŸ˜‰

I'll definitively check them out πŸ‘. Thanks for the pointer.

Collapse
xorock profile image
xorock

O think repositories are so useless with laravel...

Collapse
davidrjenni profile image
David Author

It definitively seems to be a controversial topic in the Laravel community.

In this post, I wanted to be as neutral as possible and just describe what it is and how to use it. I will explore the pros and cons in other posts.

Collapse
bdelespierre profile image
Benjamin Delespierre

Can be helpful when you use different model entities across several boundaries.

Collapse
llbbl profile image
Logan Land

Hey David, Would you consider posting to my Medium publication focused on DevOps and PHP?

Collapse
davidrjenni profile image
David Author

Hi Logan, thanks for you feedback.
Sure, why not. Let me know how to move forward.

Collapse
llbbl profile image
Logan Land

contact me: logan[at]llbbl.com