creode/laravel-repository
A Laravel repository pattern package from Creode that provides base repository classes and interfaces for clean, testable data access. Wrap Eloquent models behind reusable repositories to keep controllers and models lean and swap implementations easily.
Install (verify before committing):
composer require creode/laravel-repository
Given the package has no dependents and a single release (2024-06-23), first confirm the source tree actually contains what the description implies — a Creode\...\Repository base class — before wiring it into a project. Read the README, then src/ in the repo, in that order. Pay attention to the namespace, the abstract methods (if any), and whether the base class expects a Model in the constructor or an injected instance.
Where to look first:
stubs/ directory.composer.json — check require for the Laravel/Eloquent version constraints; a 2024 release should support Laravel 10/11, but pin-check against your app.First use case — wrap a single model:
namespace App\Repositories;
use App\Models\Order;
use Creode\...\Repository; // confirm exact FQCN from src/
class OrderRepository extends Repository
{
public function __construct(Order $model)
{
parent::__construct($model);
}
}
Bind it in a service provider:
$this->app->bind(OrderRepository::class, fn () => new OrderRepository(new Order()));
And inject it into a controller or service. The first tangible win is that OrderRepository can be swapped/mocked in tests, and that any order-specific query (findByStatus, forCustomer) has a home outside the model.
Thin subclass, fat usage. The base class almost certainly gives you all(), find(), create(), update(), delete(), and maybe paginate() / with() passthroughs. Don't duplicate those in every subclass. Add only domain methods:
class OrderRepository extends Repository
{
public function findOpenForCustomer(int $customerId): Collection
{
return $this->model->newQuery()
->where('customer_id', $customerId)
->whereNull('paid_at')
->get();
}
}
Interface + binding, when you need test seams. If the base class doesn't ship an interface, define one in app/Contracts/ and bind the concrete class to it. This is the pattern that actually pays off — controller depends on OrderRepositoryInterface, test substitutes a fake. Otherwise you've just added a pass-through layer over Eloquent.
Service orchestration, not controller orchestration.
Controller -> Service -> Repository -> Model -> DB
Keep the repository at the data-access boundary. Business rules (state transitions, pricing, notifications) belong in a service or action class, not in repository methods — otherwise OrderRepository becomes a god object and you've lost the abstraction benefit.
Eager loading. If the base class exposes with(), use it fluently:
$orders = $repo->with(['items', 'customer'])->findOpenForCustomer($id);
Confirm the return type — some base repositories return the repository itself for chaining, others return a query builder. This matters; if it returns $this, a second call mutates shared state (see Gotchas).
Testing. Prefer RefreshDatabase against SQLite for repository tests (they're data-access, so mocking the repository there is pointless), and mock the repository interface in service/controller tests. If the base class calls $this->model internally, you can swap in a fake model via the constructor for a quick unit test.
Migration path. If you're adopting this in an existing app, introduce it one model at a time behind an interface, keep the Eloquent calls in the repository, and leave the rest of the codebase untouched until the seams are proven.
findOrFail vs find returning null) will cost you more time than reading the one src/ file.$this for with() / where() style methods, a repository resolved as a singleton can leak state between requests and between calls in a queue worker. Check whether the package registers a singleton binding, and if so, either avoid fluent stateful calls or bind it as a scoped/bind rather than singleton.Model / Builder out of the repository. The whole point is that callers get arrays, collections, or DTOs. If Controller receives an EloquentBuilder, the abstraction is cosmetic.ModelNotFoundException from findOrFail and QueryException on constraint violations. Decide whether the repository translates these into domain exceptions; consistency here saves you from try/catch scattered across callers.php artisan vendor:publish exposes anything, freeze it in VCS. Given the release cadence, don't rely on the maintainer to keep published defaults current.illuminate/* requirement against your app. A 2024 pin against Laravel 11 may block an upgrade to 12 until the package is updated — plan a fallback (your own base class is ~50 lines) if that matters.app/Repositories/BaseRepository.php copy is a legitimate alternative.abstract with protected $model, that's your hook for overriding create()/update() to fire domain events or normalize input. If it's final or has private members, there is nowhere to extend — that's a strong signal to write your own base class.DB::listen() or ->dd() on the underlying builder will show the actual SQL. Given the lack of docs, log the SQL early rather than stepping through the package.How can I help you explore Laravel packages today?