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

Laravel Repository Laravel Package

ark4ne/laravel-repository

A Laravel package implementing the repository pattern, providing a structured way to separate data access from business logic. It helps keep model queries organized behind repository classes, making applications easier to test, maintain, and adapt as data sources change.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a 0.x package, so treat the public API as provisional and pin the version in composer.json ("vendor/package": "0.1.*"). The single headline change in v0.1.0 — instantiating models through an abstract method rather than a hardcoded class — is the seam everything else hangs off.

Minimal steps:

  1. composer require vendor/package:^0.1
  2. Open the package's abstract base class in vendor/vendor/package/src/ first. Locate the abstract method (conventionally newModel() or newInstance()) — that is your integration point, not the constructor.
  3. Because the class is abstract, you cannot resolve it directly from the container. Create one concrete subclass per Eloquent model you want to target:
namespace App\Support;

use App\Models\Post;
use Vendor\Package\AbstractThing;

class PostThing extends AbstractThing
{
    protected function newModel(): Post
    {
        return app(Post::class); // or: new Post
    }
}
  1. Bind your concrete subclass in a service provider (or let Laravel autowire it if it has no constructor dependencies):
$this->app->bind(AbstractThing::class, PostThing::class);
  1. First use case: resolve it and run the package's primary operation against a real model instance. Prefer constructor/method injection over app() calls so tests can swap the binding:
public function __construct(private AbstractThing $thing) {}

If the package ships a config file, publish it now (php artisan vendor:publish --tag=...) before you have working code to debug against — config merges are easier to reason about on a clean install.

Implementation Patterns

One subclass per model, bound by context. The abstract-method design implies the package is model-agnostic. Mirror that: keep a thin subclass per aggregate (PostThing, CommentThing) and bind whichever one a given service needs. Avoid a single "god" subclass with a match on model type inside newModel() — that reintroduces the coupling the abstraction was meant to remove.

Interfaces over the abstract class at injection sites. Type-hint an interface your subclasses implement, not the package's abstract class, so you can double it in tests without dragging in package internals:

interface CreatesModels { public function newModel(): Model; }

Explicit container resolution inside newModel(). Returning app(Post::class) (rather than new Post) means test bindings, Model::preventLazyLoading(), and global scopes all apply consistently. It also lets you swap in a stub model during tests. Use new Post only if you specifically need to bypass container overrides.

Compose with query building, don't rebuild it. The abstract method gives you a blank instance; scopes, where() clauses, and eager loads still belong in your repositories/queries. Pattern:

$post = $this->thing->newModel()->newQuery()->wherePublished()->firstOrFail();

Testing. Two reliable approaches:

  • Subclass with a fake in tests/Support/ returning a model with a factory-built state.
  • Anonymous class for one-off assertions: new class extends AbstractThing { protected function newModel(): Post { return new Post; } }.

Assert on the returned instance type, not the internal call, since the abstract method is the only contract.

Jobs and queues. Because newModel() produces a fresh, unsaved instance, never serialize the package object itself into a queued job. Pass the model's key and re-resolve inside handle() — otherwise you may serialize a stale or partially-hydrated instance.

Multi-tenant / multi-connection setups. Override newModel() to call $model->setConnection($tenant->connection) or ->on('tenant') before returning. This is the cleanest place for per-context model configuration, since every instance the package creates funnels through it.

Gotchas and Tips

  • Abstract means no direct instantiation. app(AbstractThing::class) throws. Every error of the form "Cannot instantiate abstract class" traces back to a missing concrete binding — check your service provider's bind() line first.
  • Return-type compatibility. If the abstract signature declares : Model, PHP covariance permits : Post, but not the reverse, and not a union that omits Model. Getting this wrong produces fatal errors at class-load time, not at call time — the stack trace points at the subclass, not the call site.
  • Accidental singletons. If you bind() instead of bind()-per-resolution in a long-running process (Octane, queue workers), a cached model instance can leak state between requests. Register as a non-shared binding, or return a fresh instance from newModel() every call.
  • Don't cache inside newModel(). The method name promises a new instance. Memoizing it (??=) turns a factory into a repository and produces subtle "my changes disappeared" bugs when the package internally calls it more than once per operation.
  • Debugging resolution. When behaviour differs between environments, dump the concrete class actually in play:
    dd(get_class($this->thing->newModel()));
    
    A stale bind() in a package's own provider can silently override yours depending on provider registration order — check bootstrap/providers.php / config/app.php ordering.
  • Static analysis. Add a @return docblock (or template annotation) on your overrides. Tools like PHPStan and Psalm cannot infer the concrete model from the abstract signature alone, so property access on the returned model will report as undefined without it.
  • 0.x churn. v0.1.0 changed how instances are created; assume further signature changes before 1.0. Keep subclasses thin — a one-line newModel() body is trivial to update if the abstract signature gains parameters or a return type. Anything more elaborate in there becomes migration debt.
  • Extension points. Override newModel() for instance construction, but reach for the surrounding template methods (or a decorator wrapping your subclass) before forking the package. Because construction is now abstracted, decorators compose cleanly — wrap the concrete subclass, delegate, and post-process the returned model.
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.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr