Search by

yuriitatur / repository-laravel

yuriitatur

Laravel repository drivers and more

Package info

bitbucket.org/yurii_tatur/repository-laravel

pkg:composer/yuriitatur/repository-laravel

Statistics

Installs: 71

Dependents: 5

Suggesters: 3

v0.3.0 2026-10-25 19:54 UTC

This package is auto-updated.

Last update: 2026-10-25 19:54:40 UTC


README

A Laravel integration package for the yuriitatur/repository repository pattern library. Provides Eloquent and raw-table drivers, fluent repository builders, transaction middleware, and Laravel event bridging.

Installation

composer require yuriitatur/repository-laravel

Register the service provider (auto-discovered in Laravel 5.5+):

// config/app.php
'providers' => [
    YuriiTatur\Repository\Laravel\Providers\RepositoryServiceProvider::class,
],

Building a Repository

Eloquent driver

use YuriiTatur\Repository\Laravel\Services\EloquentRepositoryBuilder;

$repository = EloquentRepositoryBuilder::create(UserModel::class)
    ->forEntity(UserEntity::class)   // required unless the model itself is the entity
    ->build();

The builder auto-selects a hydration strategy based on what the model implements (see Hydration strategies).

Raw-table driver

use YuriiTatur\Repository\Laravel\Services\TableRepositoryBuilder;

$repository = TableRepositoryBuilder::create('users')
    ->onConnection('pgsql')          // optional, defaults to the default connection
    ->withPrimaryKey('uuid')         // optional, auto-detected from schema if omitted
    ->forEntity(UserEntity::class)
    ->build();

Custom column matcher or hydrator

Both builders accept overrides:

EloquentRepositoryBuilder::create(UserModel::class)
    ->withColumnMatcher(MyCustomMatcher::class)
    ->withHydrator(MyCustomHydrator::class)
    ->build();

Hydration Strategies

StrategyRequirementHow it works
ModelAsEntityHydratorModel implements EntityModel is used directly as the entity — no conversion
ModelEntityHydratorModel implements HydratorModelInterfaceModel provides toEntity() / fromEntity(Entity $e)
WithoutEagerLoadsModelEntityHydratorModel implements HydratorModelInterface, ->withoutEagerLoadsOnSave() passed to builderLike ModelEntityHydrator, but the row a save updates is read without the relations the model eager loads
EloquentEntityHydrator->forEntity(MyEntity::class) passed to builderJMS Serializer maps model attributes to the entity class

HydratorModelInterface contract:

interface HydratorModelInterface
{
    public function toEntity(): Entity;
    public function fromEntity(Entity $entity): void;
}

Saving models that eager load relations

ModelEntityHydrator reads the row to update (lockForUpdate()->find()) before calling fromEntity(), so a model with $with loads all its relations on every save, only for fromEntity() to overwrite the row. Opt out with:

EloquentRepositoryBuilder::create(SubscriptionModel::class)
    ->withoutEagerLoadsOnSave()
    ->build();

// or explicitly
EloquentRepositoryBuilder::create(SubscriptionModel::class)
    ->withHydrator(new WithoutEagerLoadsModelEntityHydrator(SubscriptionModel::class))
    ->build();

The entity re-hydrated after the save (toEntity() on the saved model) lazy loads the relations it reads. To avoid that, keep the related entities of the saved entity in fromEntity() and reuse them in toEntity():

public function fromEntity(Entity $entity): void
{
    $this->savedPlan = $entity->getPlan();
    $this->plan_id = $entity->getPlan()->getId();
    // ...
}

public function toEntity(): Entity
{
    return new Subscription($this->savedPlan ?? $this->plan->toEntity(), /* ... */);
}

Transaction Middleware

The service provider registers a transaction middleware alias that wraps the entire request in a database transaction:

// routes/api.php
Route::middleware('transaction')->group(function () {
    Route::post('/orders', OrderController::class);
});

It can also be applied per-route or in a controller constructor.

The transaction is rolled back when the request fails, even though Laravel's router renders an exception thrown by the controller into a response before it reaches route middleware: the middleware checks the response's exception (set by the router) and rolls back through RolledBackResponse, then returns the rendered response unchanged. An error response the action returns without throwing (e.g. a 409 it built itself) is committed.

Events

Repository operations dispatch the following events through Laravel's Event facade (bridged from the Symfony EventDispatcher used internally):

EventFired when
EntitySavedEventAn entity is persisted
EntityDeletedEventAn entity is deleted
EntityHydratedEventA single entity is hydrated from a DB record
CollectionHydratedEventA collection of entities is hydrated
RunningQueryEventA query is about to execute

Listen to them via standard Laravel listeners:

Event::listen(EntitySavedEvent::class, function (EntitySavedEvent $event) {
    // ...
});

Testing

composer test

License

MIT — see LICENSE.