PHPackages                             mblarsen/laravel-repository - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. mblarsen/laravel-repository

ActiveLibrary

mblarsen/laravel-repository
===========================

Beefed up query-builder and repository to reduce boilerplate and keep your controllers lean.

0.13.1(5y ago)32691[2 issues](https://github.com/mblarsen/laravel-repository/issues)MITPHPPHP ^7.3

Since Apr 26Pushed 5y ago1 watchersCompare

[ Source](https://github.com/mblarsen/laravel-repository)[ Packagist](https://packagist.org/packages/mblarsen/laravel-repository)[ Docs](https://github.com/mblarsen/laravel-repository)[ GitHub Sponsors](https://github.com/mblarsen)[ RSS](/packages/mblarsen-laravel-repository/feed)WikiDiscussions master Synced yesterday

READMEChangelogDependencies (3)Versions (16)Used By (0)

laravel-repository 🐮
====================

[](#laravel-repository-cow)

[![Latest Version on Packagist](https://camo.githubusercontent.com/0310b4561561aeaf39d00911f48002fad5011ad1c441a172f25f1b0a5c12551d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d626c617273656e2f6c61726176656c2d7265706f7369746f72792e737667)](https://packagist.org/packages/mblarsen/laravel-repository)[![Build Status](https://camo.githubusercontent.com/c259dcf5393b775bc0c53814191331dabb039c6f26efe0f99cd20922e59b9600/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f6d626c617273656e2f6c61726176656c2d7265706f7369746f72792f6261646765732f6275696c642e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/mblarsen/laravel-repository/build-status/master)[![Code Coverage](https://camo.githubusercontent.com/a06a1ce8e31a680c6dff68cc9350b6cb06f664168914b52e3c88b711e9a4d140/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f6d626c617273656e2f6c61726176656c2d7265706f7369746f72792f6261646765732f636f7665726167652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/mblarsen/laravel-repository/?branch=master)[![Quality Score](https://camo.githubusercontent.com/6a1d6346a354438483236b1a6ee3d73f48f78e5f55d84b8c492c48f5b385b955/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f672f6d626c617273656e2f6c61726176656c2d7265706f7369746f72792e737667)](https://scrutinizer-ci.com/g/mblarsen/laravel-repository)[![Total Downloads](https://camo.githubusercontent.com/d31cbb1f4451aeee08da9ac922d4fccca8865924f5ff0c3a7773e76f564d2fe6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d626c617273656e2f6c61726176656c2d7265706f7369746f72792e737667)](https://packagist.org/packages/mblarsen/laravel-repository)

> Beefed up query-builder and repository to reduce boilerplate and keep your controllers lean

Features:

- Get started with zero config. [See basic examples](#basic-examples)
- The usual suspects: `all`, `find`, `create`, `update`, `destroy`.
- Plus `allResources`, `findResource`, and so on to wrap in resources.
- `list()` function useful for `` and autocompletes.
- Front-end and user driven. The request is the context for what gets included:
    - filter query on model and relations
    - include relations (all blocked by default)
    - deal with paging transparently
    - order models by their props or their relations props (with no custom SQL)
- 100% test coverage

The goal of this package:

1. Avoid boilerplate for paged, filtered, and sorted resources
2. Let the client decided what to fetch in a safe way
3. Separate controller logic from model logic
4. Let you be in control of the query for special cases

Practically the repository class is a mix between a query builder and a repository with emphasis on the retrieving stuff.

This package includes one interfaces and three classes for you to build on:

- `Repository` class, to use as is or extend for your model needs.
- `ResourceContext` interface, provides data to the repository.
- `RequestResourceContext`, draws data from the incoming Request object.
- `ArrayResourceContext`, you provide the data. Good for testing.

[![Contact me on Codementor](https://camo.githubusercontent.com/645c6cae28c01bc56cb40e671d04022c398ac3dd4c5239b7479e493791aa55b1/68747470733a2f2f7777772e636f64656d656e746f722e696f2f6d2d6261646765732f6d626c617273656e2f696d2d612d636d2d622e737667)](https://www.codementor.io/@mblarsen?refer=badge)

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require mblarsen/laravel-repository
```

The repository relies on a `ResourceContext` to provide it with the necessary values to be able to sort, filter, paginate and so on. This is handled automatically, but it means that you should let Laravel's depency injection provide a repository for you. This is espesically powerfull if you extende the base repository class.

The default context that is used is the `RequestResourceContext` which is automatically injected into the repository. This is espesically useful when you are building public or private APIs to serve a front-end. However, another implementation is provided that lets you provide the control to a higher degree. This is the `ArrayResourceContext`. This implementation is useful for testing or for when you build a traditional Laravel application using Blade views.

The following examples a biased toward use of the `RequestResourceContext`.

### Basic examples

[](#basic-examples)

The base repository knows nothing of your models, so unless you sub-class the repository, you must specify what model you are querying.

```
// Using Laravel's resolve() helper
$repository = resolve(Repository::class)->setModel(Post::class);
// Using static factory
$repository = Repository::for(Post::class);
```

When used in controllers it is recommended letting Laravel do the work for you:

```
public function index(Repository $repository)
{
    return $repository->setModel(Post::class)->all();
}
```

... or in case of a custom repository:

```
public function index(PostRepository $repository)
{
    return $repository->all();
}
```

The result is now automatically **sorted**, **filtered**, and **paginated** according to the request.

Example requests that you'll be able to provide now:

```
/posts?sort_by=created_at&page=2&filters[title]=laravel
```

That is:

- `/posts`, request posts
- `sort_by=created_at`, sort by created\_at
- `sort_by=desc`, sort in descending order (default: `asc`)
- `page=2`, paginated result, request page 2 (default: `null` meaing not paginated)
- `filters[title]=laravel` search for title in the posts name

You can also filter by relationships. E.g. `filters[address.zip]=1227`.

Since relations are disallowed by default nothing requests to include are ignored. But once we set that up your will be able to request relations as well:

- `with[]=ads&with[]=comments`, will include the relations `ads` and `comments`.

### Repository features

[](#repository-features)

You can define the behaviour of the repository using a chained API. Be sure to check the [full API](#api) below.

#### Control what relations to include

[](#control-what-relations-to-include)

Query: `with` or `with[]`

The repository will include any relations specified in `with` based on what is allowed. That ensures that the client cannot request data that you have not allowed.

```
$repository->setAllowedWith(['comments']);
```

Some times you want to include certain relations by default. In addition to doing that on the model directly the Laravel way you also have the option to set which on the repository. This allows you to control the list of relation per action in the controller.

```
$relation->setDefaultWith(['comments']);
```

*Aside: if you are building a public api (for your SPA or 3rd party) it is recommended that you wrap the result a `JsonResource`. This will give you control of what properties are exposed and will allow you to transform the data further. [See resources section](#resources).*

#### Filtering

[](#filtering)

Query: `filter[key]=value`

This package provides a search like functionality through its filters. Under the hood it uses `LIKE`, ie. `%value%`.

If you want exact matches, ie. `=`, instead you can add an exclamation point to then end of the key name.

The key doesn't have to be properties on the main model. It can be relation properties as well. Here are some examples:

```
// Search on model property
title=cra
// Search on model property exact match
code_name!=wowcrab
// Seacch on relation property
address.city=mass
```

You can combine properties in a search:

```
// Search in full name
first_name+middle_name+last_name=cra
```

And lastly you can choose to search for the same value in multiple properties:

```
// Search in both title, name, and email
title|name|email=cra
```

A different way to filter is to provider a query builder to `all()` and `find()`. See examples in [the API](#api).

#### Transforming `RequestResourceContext`

[](#transforming-requestresourcecontext)

You cannot modify the request context directly, but you have the option to convert it to an `ArrayResourceContext`.

```
public function index(UserRepository $repository)
{
    $repository->setContext(
        ArrayResourceContext::create(
            $repository->getContext()->toArray()
        )
            ->merge([
                'filters' => ['status' => 'approved']
            ])
            ->exclude(['search_by'])
    );
}
```

### Custom repositories

[](#custom-repositories)

Many of your models will likely not need as custom (sub-classed) repository. But often your core models have more logic associated with them. In that case it is advised do extend the base repository.

All the properties except the `model` can be omitted. Well, you can omitted the model too but that is kind of pointless.

Disclaimer: the example's purpose is to demo the flexibility not and isn't very real world.

```
class PostRepository extends Repository
{
    // We serve you Posts
    protected $model = Post::class;

    // The client can request to include the following relations
    protected $allowed_with = ['ads', 'comments'];

    // However, we will include these automatically
    protected $default_with = ['comments'];

    // We change the default sort key to created_at ...
    protected $default_sort_by = 'created_at';

    // ... in descending order
    protected $default_sort_order = 'desc';

    // We override modelQuery to ensure only
    // published posts will be returned
    protected function modelQuery($query = null)
    {
        // It is perfectly okay to not invoke the parent.
        // It simly defaults to an empty query of the current
        // model. These are identical:
        //
        // $query = parent::modelQuery($query);
        // $query = $query ?: Post::query()

        $query = parent::modelQuery($query);
        $query->whereNotNul('published_at');

        return $query;
    }
}
```

You can achieve the same with the base repository, but of course then you would have to repeat the setup every time:

```
public function index(Repository $repository)
{
    $only_published = Post::query()->whereNotNul('published_at');

    $repository
        ->setModel(Post::class)
        ->setDefaulSort('created_at', 'desc')
        ->setAllowedWith(['ads', 'comments'])
        ->setDefaultWith(['comments'])
        ;

    return $repository->all($only_published);
}
```

Versus:

```
public function index(PostRepository $repository)
{
    return $repository->all();
}
```

### Resources

[](#resources)

If you are building a public api (for your SPA or 3rd party) it is recommended that you wrap the result a `JsonResource`. This will give you control of what properties are exposed and will allow you to transform the data further.

Doing is really easy.

```
// using base repository
$repository->setResource(UserResource::class);

// or extending
protected $resource = UserResource::class;
```

Then you just use any of the API methods prepending `Resource` or `Resources`as shown here:

```
$repository->allResources();
$repository->findResource(3);
$repository->createResource(['name' => 'foo']);
$repository->updateResource($user, ['name' => 'foo']);
```

If you implement a collection resource you can set that as the second arguments to the `setResource` method call:

```
$repository->setResource(UserResource::class, UserResourceCollection::class);

// or in class
protected $resource_collection = UserResourceCollection::class
```

*Note: `list()` doesn't support resources. It didn't make any sense to me. You are welcome to change my mind.*

### Customizing `RequestResourceContext`

[](#customizing-requestresourcecontext)

The default values that the `RequestResourceContext` looks for in incoming request are:

- `filters`
- `page`
- `per_page`
- `sort_by`
- `sort_order`
- `with`

If you don't like these or for some reason cannot use them. You have the option to provide your own keys using `mapKeys()` on the context. `mapKeys()` lets you map one or more keys to your preferred scheme.

By far the easiest way to do this is add this bit of code in your `AppServiceProvider`.

```
public function register()
{
    $this->app->resolving(RequestResourceContext::class, function ($context) {
        $context->mapKeys([
            'filters' => 'filter'
            'sort_by' => 'order_by',
            'sort_order' => 'direction',
            'per_page' => 'take',
        ]);

        // or if you simply prefer camelCase

        $context->mapKeys([
            'sort_by' => 'sortBy',
            'sort_order' => 'sortOrder',
            'per_page' => 'perPage',
        ]);
    });
}
```

So as where this would be the old way:

```
/posts?sort_by=title&per_page=10&with=comments

```

your client now should use this scheme:

```
/posts?sortBy=title&perPage=10&with=comments

```

### Testing

[](#testing)

When it comes to testing the `ArrayResourceContext` comes in handy. It lets you pass in context values directly.

```
$repository = Repository::for(User::class, ArrayResourceContext::create([
    'filters' => [
        'status' => 'approved',
    ]
]));

// or shorter

$repository = Repository::for(User::class, [
    'filters' => [
        'status' => 'approved',
    ]
]);
```

API
---

[](#api)

- [`all($query = null)`](#all)
- [`list(string|callabel $column, $query = null)`](#list)
- [`find($id, $query = null)`](#find)
- [`create(array $data)`](#create)
- [`update(Model $model, array $data)`](#update)
- [`destroy(Model $model)`](#destroy)
- [`setContext(ResourceContext|array $resource_context)`](setContext)
- [`setModel(string $model)`](setModel)
- [`setAllowedWith(array $allowed)`](#setAllowedWith)
- [`setDefaultSort(string $by, string $order = 'asc')`](#setDefaulSort)
- [`setDefaultWith(array $with)`](#setDefaultWith)
- [`shouldAuthorize(bool $value = true)`](#shouldAuthorize)

When extending the base repository you may want to check out these additional functions:

- [`modelQuery($query = null)`](#modelQuery)
- [`register()`](#register)

### `all($query = null)`

[](#allquery--null)

### `allQuery($query = null)`

[](#allqueryquery--null)

### `allResources($query = null)`

[](#allresourcesquery--null)

Return all models given the current resource context.

```
public function index(UserRepository $user_repository)
{
    // To get only users with status 'pending'
    $query = User::where('status', 'pending');
    $users = $user_repository->all($query);

    // Of course in this simple case we could have achived the same using a
    // filter: GET /users?filters[status]=pending
}
```

Use with `Query` suffix to return as a query builder.

Use with `Resources` suffix to return as a resource collection.

### `list(string|callabel $column = null, $query = null)`

[](#liststringcallabel-column--null-query--null)

### `listQuery(string|callabel $column = null, $query = null)`

[](#listquerystringcallabel-column--null-query--null)

Produces a result suitable for selects, lists, and autocomplete. All entries that has a 'value' and a 'label' key.

If `$column` is omitted the default sort by is used. In many cases they'll be the same anyway.

Use with `Query` suffix to return as a query builder.

Note: if a callable is used the mapping is performed in memory, while a string is done in the database layer. Also note that when using the listQuery variant the callable is not reflected.

### `find($id, $query = null)`

[](#findid-query--null)

### `findQuery($id, $query = null)`

[](#findqueryid-query--null)

### `findResource($id, $query = null)`

[](#findresourceid-query--null)

Gets a single model. You can further narrow down the result by providing a start query. See example in `all()`

Use with `Query` suffix to return as a query builder.

Use with `Resource` suffix to return as a resource.

### `create(array $data)`

[](#createarray-data)

### `createResource(array $data)`

[](#createresourcearray-data)

Typical crUd. Use with `Resource` suffix to return as a resource.

### `update(Model $model, array $data)`

[](#updatemodel-model-array-data)

Typical crUd.

Use with `Resource` suffix to return as a resource.

### `destroy(Model $model)`

[](#destroymodel-model)

Typical cruD.

### `setContext(ResourceContext|array $resource_context)`

[](#setcontextresourcecontextarray-resource_context)

This method lets you set or change the context after the repository is created.

### `setModel(string $model)`

[](#setmodelstring-model)

See example code.

### `setAllowedWith(array $allowed)`

[](#setallowedwitharray-allowed)

See example code.

### `setDefaultSort(string $by, string $order = 'asc')`

[](#setdefaultsortstring-by-string-order--asc)

### `setDefaultWith(array $with)`

[](#setdefaultwitharray-with)

See example code.

### `modelQuery($query = null)`

[](#modelqueryquery--null)

See example code.

### `register()`

[](#register)

Called when the repository is created. This is useful setting up this `default_list_column` function for a sub-classed repository.

```
protected function register()
{
    $this->default_list_column = function ($model) {
        return $model->full_name;
    };
}
```

### `shouldAuthorize(bool $value = true)`

[](#shouldauthorizebool-value--true)

When set to true authorization is performed before any query is performed.

methodactionallviewAnylistviewAnyfindviewAnycreatecreateupdatecreatedestroydestroyExample:

```
Repository::for(Post::class)
    ->shouldAuthorize()
    ->update($post, ['author' => 'me');
```

### `interface ResourceContext`

[](#interface-resourcecontext)

See [ResourceContext](src/ResourceContext.php) implementation.

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.

Security
--------

[](#security)

If you discover any security related issues, please email  instead of using the issue tracker.

Prior work
----------

[](#prior-work)

- [Spatie QueryBuildir](https://github.com/spatie/laravel-query-builder)
- [bosnadev/repository](https://github.com/bosnadev/repository)

Credits
-------

[](#credits)

- [Michael Bøcker-Larsen](https://github.com/mblarsen)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

Laravel Package Boilerplate
---------------------------

[](#laravel-package-boilerplate)

This package was generated using the [Laravel Package Boilerplate](https://laravelpackageboilerplate.com).

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.

Please see [DEVELOPING](.github/DEVELOPING.md) for help getting started.

Contributors ✨
--------------

[](#contributors-)

Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):

  [![](https://avatars0.githubusercontent.com/u/247048?v=4)
**Michael Bøcker-Larsen**](https://www.codeboutique.com)
[💻](https://github.com/mblarsen/laravel-repository/commits?author=mblarsen "Code") [📖](https://github.com/mblarsen/laravel-repository/commits?author=mblarsen "Documentation") [🚧](#maintenance-mblarsen "Maintenance") This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

###  Health Score

22

—

LowBetter than 22% of packages

Maintenance7

Infrequent updates — may be unmaintained

Popularity16

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Recently: every ~10 days

Total

15

Last Release

2158d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/adf399c8b2a9cea04df413eeff3dd2b55bd0146a63234c7a1e610733d31e6b15?d=identicon)[mblarsen](/maintainers/mblarsen)

---

Top Contributors

[![mblarsen](https://avatars.githubusercontent.com/u/247048?v=4)](https://github.com/mblarsen "mblarsen (92 commits)")

---

Tags

laravellaravel-packagequery-builderrepositoryquery builderlaravel-repositorymblarsen

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/mblarsen-laravel-repository/health.svg)

```
[![Health](https://phpackages.com/badges/mblarsen-laravel-repository/health.svg)](https://phpackages.com/packages/mblarsen-laravel-repository)
```

###  Alternatives

[matchory/elasticsearch

The missing elasticsearch ORM for Laravel!

3059.0k](/packages/matchory-elasticsearch)[orkhanahmadov/eloquent-repository

Eloquent Repository for Laravel

2764.5k](/packages/orkhanahmadov-eloquent-repository)[stevenmaguire/laravel-cache

Seamlessly adding caching to Laravel service objects

171.8k](/packages/stevenmaguire-laravel-cache)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
