yuriitatur / repository-laravel
Laravel repository drivers and more
Package info
bitbucket.org/yurii_tatur/repository-laravel
pkg:composer/yuriitatur/repository-laravel
Requires
- php: >=8.4
- jms/serializer: ^3.0
- yuriitatur/exceptions: ^1.0
- yuriitatur/repository: dev-master
Requires (Dev)
- dg/bypass-finals: ^1.9
- kint-php/kint: ^6.0
- orchestra/testbench: ^v11.0.0
- ramsey/uuid: ^4.1
Suggests
- trevorpe/laravel-symfony-cache: Adds Symfony to Laravel Cache bridge, useful when you want to use CachedRepository in Laravel
Provides
None
Conflicts
None
Replaces
None
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
| Strategy | Requirement | How it works |
|---|---|---|
ModelAsEntityHydrator | Model implements Entity | Model is used directly as the entity — no conversion |
ModelEntityHydrator | Model implements HydratorModelInterface | Model provides toEntity() / fromEntity(Entity $e) |
WithoutEagerLoadsModelEntityHydrator | Model implements HydratorModelInterface, ->withoutEagerLoadsOnSave() passed to builder | Like ModelEntityHydrator, but the row a save updates is read without the relations the model eager loads |
EloquentEntityHydrator | ->forEntity(MyEntity::class) passed to builder | JMS 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):
| Event | Fired when |
|---|---|
EntitySavedEvent | An entity is persisted |
EntityDeletedEvent | An entity is deleted |
EntityHydratedEvent | A single entity is hydrated from a DB record |
CollectionHydratedEvent | A collection of entities is hydrated |
RunningQueryEvent | A 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.