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

bfg/repository

bfg/repository is a Laravel package that adds a clean repository layer between your application and Eloquent models, keeping queries organized and controllers lean. It supports reusable query logic and a testable data access structure.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is effectively undocumented at scale (1 star, 0 dependents), so treat src/ as the specification and the README as a hint, not a contract. Budget 15 minutes to read the source before wiring anything into a real project.

  1. Confirm it even installs. Open composer.json in the repo and check the require block for illuminate/support and php constraints. A low-traffic package like this is often pinned to an older Laravel major; if the constraint is ^8.0 you may need --ignore-platform-reqs or a fork for Laravel 10/11. Also check autoload.psr-4 to learn the real namespace — do not guess it from the package name.
  2. Install and inspect.
    composer require bfg/repository
    ls vendor/bfg/repository/src
    
    Look for: a base repository class (abstract, wraps Model), an optional Contracts/Interfaces directory, a Criteria or Filters directory, and any Console commands (a make:repository generator is common in this family of packages).
  3. Check for a service provider. If composer.json does not list a provider under extra.laravel.providers, you must register it manually in config/app.php (Laravel ≤10) or bootstrap/providers.php (Laravel 11+) — otherwise any container bindings or generator commands silently won't exist.
  4. First use case. Pick one model, e.g. Post. Write the smallest possible repository against it — extend the package's base class, set the model, and call a single read method — then call it from php artisan tinker. Do not convert a whole domain on day one; you want to discover the return types (Eloquent Collection vs LengthAwarePaginator vs plain array) on a throwaway class.

If step 1 or 4 fails, stop. For a package this small the cost of writing a 60-line base repository yourself is lower than the cost of debugging someone else's.

Implementation Patterns

One repository per aggregate root, not per table. The value of the pattern is that PostRepository can own the join to authors and the eager-loads; a repository per table just adds a passthrough layer over Eloquent.

Interface binding in a dedicated provider. This is where the package earns its keep — controllers depend on the interface, tests swap in a fake:

// app/Providers/RepositoryServiceProvider.php
public function register(): void
{
    $this->app->bind(
        \App\Contracts\PostRepository::class,
        \App\Repositories\EloquentPostRepository::class
    );
}

Extend, don't wrap. Subclass the package's base repository and add domain-specific methods (publishedForHomepage(), archiveOlderThan()). Keep the inherited generic CRUD (all, find, create, update, delete) for the boring cases and write explicit methods for anything with a where clause that matters. Generic all($columns, $orderBy) signatures are where repository packages usually leak.

Criteria/filter objects for cross-cutting query fragments. If the package ships a criteria mechanism, use it for reusable filters (tenant scope, published scope) rather than for one-off queries — criteria that are used once are just indirection with extra files. If there is no criteria system, closures or Eloquent local scopes are a fine substitute; don't build one.

Keep transactions in the service layer, not the repository. Repositories that call DB::transaction() internally compose badly when two of them need to share a transaction. The controller/service calls DB::transaction(fn () => ...) and the repositories stay transaction-agnostic.

Incremental migration path. Introduce the repository alongside existing Model::query() calls, route new endpoints through it, and backfill old call sites only when you touch them. A big-bang conversion creates a huge diff for zero user-visible benefit and is the usual reason repository refactors get reverted.

Testing. Bind the interface to a real implementation and run against SQLite in-memory for query correctness; bind to a mock only for controller tests where you're asserting HTTP behaviour, not SQL. Mocked repositories that return canned collections hide broken where clauses very effectively.

Gotchas and Tips

  • The package is a leaky abstraction by design. Anything the base class doesn't proxy (whereHas, withCount, cursor, chunkById, lockForUpdate) either needs a method on your subclass or it defeats the point. Audit the base class's method list early and decide which gaps you'll fill.
  • Return types are the #1 source of breakage. paginate() returning a paginator but find() returning ?Model and all() returning a Collection is fine — until a refactor makes one of them ->toArray(). Document the contract on your interface with return types and stick to it.
  • Eager loading gets lost. If the base class exposes all() with no $with support, you will ship N+1s. Set protected $with = [...] on the model itself, or override the read methods to apply with().
  • Bulk operations skip model events. A repository update() built on ->where(...)->update([...]) bypasses saving/saved observers, so slugs, UUIDs, and audit columns silently don't fire. Know which of the two code paths each method uses.
  • Cache invalidation belongs with the writes. If you add caching inside repository reads, the corresponding forget must live in the same class's write methods — otherwise the next developer adds a write elsewhere and the cache goes stale.
  • Container binding in the wrong provider order. Bindings registered in a provider that loads after a controller resolution (or inside a package provider) will appear to "not work." Use register(), not boot(), for bindings.
  • Check for a hard-coded connection or table. Small packages sometimes assume $model->getTable() or default connection; multi-tenant or multi-database setups need those overridden.
  • Maintenance risk. With one star, no dependents, and no release cadence, assume no upstream fixes. If you adopt it, fork it into your own namespace or vendor it, and pin the version with a caret constraint plus a lockfile commit. A composer update six months from now should not be able to change your data layer.
  • When to skip it entirely. If you're not going to write interface bindings and swap implementations in tests, the repository layer is pure ceremony — Eloquent's query builder plus local scopes is the idiomatic Laravel answer, and a hand-rolled abstract base class (~60 lines) gives you the same benefit with full control and no dependency.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console