Repository Pattern in Laravel

Repository Pattern in Laravel

ยท

8 min read

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();

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();
    }
}

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);

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]);
    }
}

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
    }
}

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);
    }
}

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.

ย