# l5-repository: Eloquent behind an interface, six years after Laravel 5

> A PHP package that puts a repository layer between your controllers and Eloquent, with criteria objects, caching, presenters and a code generator bolted on. Useful scaffolding, with real design costs.

**andersao/l5-repository** — Laravel 5 - Repositories to abstract the database layer

- Repository: https://github.com/andersao/l5-repository
- Website: http://andersao.github.io/l5-repository
- Stars: 4,194 · Forks: 879
- Language: PHP
- License: MIT
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/andersao-l5-repository

## The name says Laravel 5, the releases say Laravel 13

The package is published to Packagist as `prettus/l5-repository` and installed from a repository that is still called andersao/l5-repository. The name is historical. Version 4.0.0, published on 2026-05-27, added Laravel 13 support, which is the same date as the last push to master. Before that, 3.0.0 on 2025-10-09 was titled Make it compatible with PHP 8.4, and 3.0.1 a day later adjusted the PHP version requirement.

That release history is the honest answer to whether the project still works. The Laravel version in the name stopped meaning anything years ago; the badges on Packagist point at prettus/l5-repository, and the README carries links for older 1.0 and 2.0 branches alongside migration guides for 2.0, 2.1 and 3.0. Four thousand stars and eight hundred forks is a lot of Laravel applications built on it, and 261 open issues is the flip side of that reach.

It is MIT licensed and PHP, with a `src/` directory, a `tests/` directory, a `phpunit.xml`, a `CHANGELOG.md` and a `SECURITY.md`. The presence of the security file and a code of conduct is worth noting, since this is a package whose whole job is to be inserted into every application you build.

## Installing it and publishing the config

Installation is one Composer line.

```bash
composer require prettus/l5-repository
```

On Laravel 5.5 and later the service provider registers itself. On anything older you add it by hand to `config/app.php`, which for most readers today is a historical note rather than an instruction.

```php
'providers' => [
    ...
    Prettus\Repository\Providers\RepositoryServiceProvider::class,
],
```

Lumen users get a separate provider class, `LumenRepositoryServiceProvider`, registered through the application object rather than the config file. Then there is the config publish step, which is the one command you will actually need because the generator and cache behaviour are configurable.

```bash
php artisan vendor:publish --provider "Prettus\Repository\Providers\RepositoryServiceProvider"
```

Publishing the configuration is not optional polish here. The cache section of the README has a config block of its own with a time and a repository implementation, and the generator reads a paths array from the same file. Skipping the publish step means guessing at defaults.

## The repository interface is a thin, wide wrapper

The core of the package is `Prettus\Repository\Contracts\RepositoryInterface`, and the method list reads like an Eloquent cheat sheet: reads, one-record lookups, writes, ordering, eager loading and visibility.

```php
all($columns = array('*'))
first($columns = array('*'))
paginate($limit = null, $columns = ['*'])
find($id, $columns = ['*'])
findByField($field, $value, $columns = ['*'])
findWhere(array $where, $columns = ['*'])
create(array $attributes)
update(array $attributes, $id)
updateOrCreate(array $attributes, array $values = [])
delete($id)
```

The bottom half of the interface is where it gets interesting. Beyond `orderBy`, `with`, `has` and `whereHas` there are `hidden` and `visible` for shaping output, `scopeQuery` for dropping a closure into the query builder, and `skipPresenter`. That last pair is how the package handles the case where you want the repository in one part of the app and raw models in another.

A repository is a class extending `BaseRepository` with one required method, `model()`, returning the Eloquent class name. That is the entire contract.

```php
namespace App;

use Prettus\Repository\Eloquent\BaseRepository;

class PostRepository extends BaseRepository {

    function model()
    {
        return "App\\Post";
    }
}
```

The model itself stays an ordinary Eloquent model. The README only insists that `$fillable` be declared, because mass assignment through `create` and `update` depends on it. Nothing about the model changes, which is what makes this pattern adoptable in an existing codebase rather than a rewrite.

## Criteria objects are the part worth learning from

Beyond the CRUD methods, the package has a second concept that is the actual idea behind it. `RepositoryCriteriaInterface` gives you `pushCriteria`, `popCriteria`, `getCriteria`, `getByCriteria` and `skipCriteria`, and `CriteriaInterface` is a single method: `apply($model, RepositoryInterface $repository)`.

That is a small but genuinely useful design. A criteria class is a composable query fragment that knows how to apply itself to any repository, which means filters can be assembled at runtime instead of being encoded as a chain of conditionals in a controller. The README documents both the manual push and pop style and a `RequestCriteria` class that builds criteria from the HTTP query string, so a filtering endpoint can be a few lines.

Caching is bolted onto the same layer through `CacheableInterface`, which exposes `setCacheRepository`, `getCacheRepository`, `getCacheKey`, `getCacheTime` and `skipCache`. `getCacheKey($method, $args = null)` is the detail to look at before enabling it broadly: the cache identity is the method name plus its arguments, so a method called with a broad argument set caches a broad result. Per-user or per-tenant data in a cached method is the mistake this interface makes easy to make.

## Validators, presenters and a code generator

The package extends past queries into two adjacent concerns. Validators are classes you define, enable on a repository, and which run against input before it reaches the model, with rules either inside the validator or declared on the repository itself.

Presenters are the other half, built on Fractal and the `PresenterInterface` with a single `present($data)` method, plus a `Transformable` contract with `transform()`. The model's `transform()` supplies attributes and the presenter supplies includes and structure, so a JSON API response stops being assembled by hand in the controller. `setPresenter` and `skipPresenter` on the repository let you opt in per call site, and `hidden` and `visible` on the interface do the same job at the attribute level.

The generator writes repositories for you, and it is configured from the published config file.

```php
    'generator'=>[
        'basePath'=>app()->path(),
        'rootNamespace'=>'App\\',
        'paths'=>[
            'models'       => 'Entities',
            'repositories' => 'Repositories',
            'interfaces'   => 'Repositories',
            'transformers' => 'Transformers',
            'presenters'   => 'Presenters',
```

One note on that block: the `paths` array is used as both namespace and file path, and the README says so plainly, with a typo in the sentence about forward and backward slashes being handled. It is a small reminder that this README has been edited many times by many people, which is normal for a project with this many contributors.

## What the pattern buys you and what it costs

The honest case for a repository layer is testability and substitution. When a controller depends on a repository class rather than on `Post::where(...)`, a test can hand it a mock, and moving to a different ORM becomes a project rather than a rewrite. Criteria objects and presenters both fall out of that indirection naturally.

The cost is that you now have two layers to read for every query, and Eloquent's own fluency is hard to beat. When something is awkward through the interface, the standard escape hatch is `scopeQuery`, and once you are passing closures into `scopeQuery` you have kept the indirection and lost the abstraction. The README's method list is wide enough that most common cases fit, but the long tail does not.

There is a second cost worth naming: 261 open issues on a package that most applications never touch. Those issues accumulate because nobody can drop in a newer version, which is the main risk of putting a shared dependency between your controllers and your database.

For an application with twenty queries and three developers, none of this is worth it. For an application with two hundred queries, a defined test boundary and a team that keeps hitting the same controller fat, it earns its keep, and the current release cadence means you are not adopting abandoned code.

## Conclusion

l5-repository solves a real problem for applications that have grown a large number of Eloquent call sites and want them testable without touching the database, and the four current releases show it still tracks modern PHP. What it does not do is make your queries better: it moves them, and every method you call is still a method on Eloquent underneath. If your pain is coupling in the controller, the repository interface and its generator are worth the day it takes to wire up. If your pain is slow queries, read the caching section first, because the default cache key is method and argument based, which means you inherit whatever selectivity your arguments happen to have.

## FAQ

### Is the l5-repository package still compatible with modern Laravel?

Yes. Version 4.0.0, published on 2026-05-27, added Laravel 13 support, and 3.0.0 in October 2025 made the package compatible with PHP 8.4. The Laravel 5 in the package name refers to the original branch it was written for and no longer describes the supported versions.

### What does a criteria class do in l5-repository?

A criteria class is a reusable query fragment implementing `CriteriaInterface`, whose only method is `apply($model, RepositoryInterface $repository)`. You push criteria onto a repository before a call and pop them after, so filters can be composed at runtime instead of being written as conditionals in the controller. A `RequestCriteria` class is included for building them from the HTTP query string.

### Does caching results in a repository risk serving stale or shared data?

It can. The cache key comes from `getCacheKey($method, $args = null)`, so the cache identity is the method name plus its arguments, and any per-user or per-tenant variation has to be part of those arguments or a request. The README documents `skipCache` for call sites where caching should not apply.

### Do I have to register the service provider manually?

Not on Laravel 5.5 or later, where the provider attaches automatically. Older versions need `Prettus\Repository\Providers\RepositoryServiceProvider::class` added to the providers array in `config/app.php`, and Lumen needs `LumenRepositoryServiceProvider` registered on the application object. You still need to publish the config with artisan vendor:publish for the generator and cache settings.

### What are the tradeoffs of adding a repository layer to Laravel?

You gain a boundary you can mock in tests and a place to hang criteria objects, caching and presenters, which is worth it once an application has enough query call sites to make the fat controller a real problem. You pay for it in readability, because every query now crosses two layers, and in flexibility, because anything awkward through the interface falls back to `scopeQuery` closures. There is also a dependency risk: with hundreds of open issues, upgrades are not always trivial.

## Sources

- [andersao/l5-repository on GitHub](https://github.com/andersao/l5-repository)
- [License: MIT](https://github.com/andersao/l5-repository/blob/master/LICENSE)
- [Project website](http://andersao.github.io/l5-repository)
- [README](https://github.com/andersao/l5-repository/blob/master/README.md)
- [Releases](https://github.com/andersao/l5-repository/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/andersao-l5-repository
