rekalogika/reconstitutor
Rekalogika Reconstitutor is a PHP library for rebuilding objects from persisted data. It maps database rows or arrays to typed objects through a simple reconstitution API, helping keep persistence logic separate from domain models.
rekalogika/reconstitutor is a framework-agnostic Doctrine ORM helper: you write a small class per entity that maps a plain array (request payload, CSV row, queue message) onto a managed entity, and the library handles the plumbing around identifiers, associations and collections. In Laravel you pair it with laravel-doctrine/orm (or your own EntityManager binding) — there is no Laravel service provider in this package, the Symfony bundle lives in a separate repo.
Minimal steps:
composer require rekalogika/reconstitutor alongside your Doctrine ORM setup.Reconstitutor interface and ReconstitutionContext in src/, then read tests/ — the test suite doubles as the best usage documentation.getClass():final class BookReconstitutor implements Reconstitutor
{
public function __construct(private EntityManagerInterface $em) {}
public static function getClass(): string
{
return Book::class;
}
public function reconstitute(object $book, array $data, ReconstitutionContext $context): void
{
if (array_key_exists('title', $data)) {
$book->setTitle($data['title']);
}
if (array_key_exists('authorId', $data)) {
$book->setAuthor($this->em->getReference(Author::class, $data['authorId']));
}
}
}
First use case: a PUT/PATCH endpoint that accepts a JSON body with scalars plus relation IDs and needs a managed entity back — without hand-writing a setter for every field and without the serializer's ORM hydrator.
ReconstitutorRegistry (map getClass() → instance) lets a single generic controller handle many resources: resolve the class from the route, $em->find() or new, reconstitute, flush.$book = $em->find(Book::class, $id) ?? new Book(); then reconstitute, then $em->flush(). This keeps write logic in exactly one place.array_key_exists() so an absent key means "leave alone" rather than "set to null". Agree on this convention early — it's the single biggest source of accidental data wipes.$em->getReference(Author::class, $id) avoids a SELECT and is the idiomatic way to satisfy a ManyToOne. For nested writes (creating a new related entity from a nested array), delegate to that entity's own reconstitutor via the context.getReference() proxies, or diff by ID. Watch orphan removal — an empty array plus orphanRemoval: true deletes rows.FormRequest first, then wrap reconstitute + flush in $em->transactional(fn () => ...) or DB::transaction(). Flush once at the end, not per entity.find() + reconstitute + batched flush is far cheaper than per-row serializers.$em->clear() or detach() mid-loop drops pending changes — 2.5.0 added fixes specifically for detach-during-transaction and begin/flush/clear/commit sequences, so upgrade if you hit that.reconstitute() is reachable from request data. Validate and authorize before calling it.ErrorException in Laravel. Undefined array keys are converted to exceptions by Laravel's error handler; always array_key_exists() / ??.getReference() never validates existence; a bogus foreign key only fails on flush() as a constraint violation (or on first proxy initialization). Map that to a 422, not a 500.flush(), not during reconstitution. Log the payload plus entity class, and inspect $em->getUnitOfWork()->getScheduledEntityUpdates() when changes "disappear".symfony/* constraint to allow Symfony 8 — no API breaks, no deprecations. Re-run anything touching transactions or detached entities, then bump.Reconstitutor per class; use ReconstitutionContext for EntityManager access, nested reconstitution and identifier assignment; wrap the registry in your own service so controllers never reference concrete reconstitutor classes.How can I help you explore Laravel packages today?