Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Repository Laravel Package

crcms/repository

crcms/repository is a Laravel repository-pattern package that separates data access from business logic, with support for Eloquent models, reusable criteria, caching, and fluent query methods to keep controllers and services clean and testable.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install via Composer — composer require crcms/repository. Laravel 5.5+ auto-discovers the service provider; older versions need it registered in config/app.php. There is no publishable config for most use cases — the package is convention-driven, so you configure behavior per-repository rather than globally.

  2. Read the base class first. Open vendor/crcms/repository/src/Repository.php (and any Resource/Traits directories) before writing your own. The value of this package is short — the README covers 80% of it in a few lines — so spending 10 minutes on the source is the fastest path to avoiding surprises around query proxying and resource binding.

  3. Create your first repository. Generate a class extending Crcms\Repository\Repository, point it at a model via the conventional property (typically protected $model = User::class;), and place it under app/Repositories/.

    namespace App\Repositories;
    
    use App\Models\User;
    use Crcms\Repository\Repository;
    
    class UserRepository extends Repository
    {
        protected $model = User::class;
    }
    
  4. Wire it into a controller using constructor injection (Laravel's container resolves it automatically because the repository has no constructor args by default):

    public function index(UserRepository $users)
    {
        return $users->paginate(15);
    }
    
  5. First real use case: a paginated JSON list endpoint. This is where the repository shines — the controller line is a single expression, and swapping pagination, eager loading, or resources later does not touch the controller.

Implementation Patterns

Thin controller, thick repository

Keep controllers to orchestration only: validate, call a repository method, return. Any where, orderBy, or with chain belongs in a named repository method (e.g. $users->activePaginated()), not inline in the controller. This is the whole point of the "detached model from controller" premise — if you find yourself importing the Eloquent model into a controller, you have broken the abstraction and it will regress.

Fluent query building then a terminal call

The base class proxies Eloquent builder methods. The idiomatic pattern is: chain filters on the repository instance, then terminate with get/first/paginate/simplePaginate/find. Treat the repository instance as a short-lived query scope — instantiate-resolve it per request, chain, terminate, discard. Do not store it as a singleton in the container (see Gotchas).

public function findActive(int $id): ?User
{
    return $this->where('active', true)->first();
}

Eager loading at the repository boundary

Because the model never leaks to the controller, with() calls live inside repository methods. This is also your N+1 firewall: enforce a rule that any repository method returning a collection must specify its relations explicitly, otherwise API response shaping (below) will lazily load per-item.

public function paginatedWithTeam(int $perPage = 15)
{
    return $this->with('team')->paginate($perPage);
}

Resource transformation

The package separates data access from serialization via a Resource type. Bind a resource to a repository so the same underlying query can be serialized differently per endpoint (list vs. detail). Keep resources pure — no queries inside toArray(). If you need related data, eager load it in the repository, not the resource.

class UserResource extends \Crcms\Repository\Resource
{
    public function toArray(): array
    {
        return [
            'id'    => $this->id,
            'name'  => $this->name,
            'team'  => $this->team->name ?? null,
        ];
    }
}

Custom apply() / scoping overrides

The extension point for shared filtering (tenant scoping, soft-delete handling, request-driven filters) is to override the repository's apply/scope hook rather than duplicating where clauses across methods. Put tenant IDs here so every query inherits the constraint. This is a single choke point — if you leak tenant data, this is where it will have happened.

Request-driven filters

The package is designed for the "warehouse data provider" use case, so filter/sort/paginate parameters commonly drive the query from the request. Wrap this in a dedicated repository method and validate the allowed filter keys against a whitelist. Never pass raw request input into orderBy — a user-controlled column name is a SQL injection surface.

Layering for larger apps

For anything beyond a handful of models, introduce a BaseRepository extends Repository with your cross-cutting behavior (tenant scope, apply defaults, resource binding helpers), then have domain repositories extend that. This keeps the per-model classes to one or two meaningful methods each and makes upgrades of crcms/repository less disruptive.

Gotchas and Tips

  • Container lifecycle. Repositories wrap a query builder that accumulates state. If you bind a repository as a singleton (or ->singleton(UserRepository::class)), the builder state persists across requests in long-running workers (Octane, queue workers) and across calls in the same request. Always resolve per-use: app(UserRepository::class) or method injection.

  • Model resolution quirk. Some versions resolve the model from a $model property string, others via a getModel() override. If your repository silently binds to the wrong table, inspect the base class method that instantiates the model — this is the single most common "why is my repository querying the wrong thing" bug.

  • Terminal method required. Chaining where() returns the repository (or builder) — nothing executes until get/first/paginate/count. Debugging "my query is empty" almost always means a missing terminal call, not a missing filter.

  • Pagination vs. simplePaginate. paginate runs a COUNT query first — noticeable on wide tables. Use simplePaginate for infinite-scroll style endpoints and reserve paginate for where users see total counts. Verify which one the base paginate delegates to in the version you installed; some releases wrap paginate with resource transformation, and swapping in simplePaginate bypasses that wrapper.

  • Resource binding failures are silent. If the resource class is bound by convention (namespace/suffix) and not found, you may get raw model output instead of your curated payload. Diff the JSON in a test — always assert on the structure, not just the status code.

  • N+1 in resources is the classic trap. A resource accessing $this->team triggers a lazy load per row when the resource is used inside a paginator transform. Confirm eager loads with DB::listen or Laravel Telescope's query log on your list endpoint before shipping — the controller looks clean, so the problem hides.

  • Request filters need whitelisting. If you override the filter hook to read request()->only([...]), that array is your security boundary. Sort columns, filter operators, and relation names must all be whitelisted; anything driven by user input into orderBy/where(column, ...) is exploitable.

  • Mocking for tests. Because the repository proxies the builder, faking queries in unit tests is awkward. Prefer feature tests that hit the database (SQLite in-memory) over mocking the repository — assertion via assertDatabaseHas and JSON structure is more robust than stubbing a fluent chain.

  • Extension point over forking. If a method is missing (e.g., cursorPaginate), add it to your BaseRepository rather than editing the vendor code. The package is small and stable, but vendor edits are lost on composer update and will bite during the next Laravel upgrade.

  • Upgrade hygiene. With 66 stars and low churn, the package isn't receiving frequent releases — that's fine for stability but means you are the maintainer of edge cases. Pin the version in composer.json, keep a thin BaseRepository as your adaptation layer, and write one feature test per public repository method so a composer update that changes base-class behavior fails CI rather than production.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo