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

Skeleton Mapper Laravel Package

doctrine/skeleton-mapper

Doctrine Skeleton Mapper is a lightweight mapping library for Doctrine entities, helping convert entity data into simple skeleton objects or arrays for APIs, read models, and presentation layers while keeping persistence concerns separate.

View on GitHub
Deep Wiki
Context7

Getting Started

doctrine/skeleton-mapper is not a ready-to-run ORM. It is a skeleton of infrastructure abstractions — built on doctrine/persistence and doctrine/event-manager — for building your own object mapper with Doctrine-style semantics (identity map, change tracking, life-cycle events). Think "Doctrine ORM minus any storage, hydration, or metadata implementation."

Minimal steps:

  1. composer require doctrine/skeleton-mapper and pin the last tag (2024-06-11). It is archived, so treat the version as frozen.
  2. Read the source in this order — these are the contracts you will implement or extend:
    • Doctrine\SkeletonMapper\ObjectManager — the facade your application talks to.
    • Doctrine\SkeletonMapper\UnitOfWork — identity map, snapshots, changesets, commit.
    • Doctrine\SkeletonMapper\Mapping\ClassMetadata and ClassMetadataFactory — describe your model and its identifiers.
    • Doctrine\SkeletonMapper\Persister\Persister — insert/update/delete against your store.
    • Doctrine\SkeletonMapper\Hydrator\Hydrator — turn raw rows/data into objects and back.
    • Doctrine\SkeletonMapper\Mapping\Driver\MappingDriver — where metadata comes from (attributes, PHP, XML, or hand-written).
  3. Wire an ObjectManager with: a ClassMetadataFactory (plus a driver), a UnitOfWork (plus event manager and hydrator/persister), and a repository factory.
  4. First use case: map plain, framework-agnostic PHP domain objects onto a non-SQL store (REST API, Redis, filesystem, in-memory fixtures) while keeping Doctrine's familiar persist / flush / find / remove workflow:
$objectManager->persist($order);
$objectManager->flush();

$order = $objectManager->find(Order::class, $id);
$objectManager->remove($order);
$objectManager->flush();

If you want a SQL ORM, use doctrine/orm. This package is the correct choice when you own the storage layer already and only want the object-mapping plumbing.

Implementation Patterns

Custom ObjectManager facade. Subclass or compose Doctrine\SkeletonMapper\ObjectManager and delegate to UnitOfWork. Keep the public surface Doctrine-shaped (find, persist, remove, flush, clear, getRepository, detach, merge) so application code does not learn your internal names.

Persister-per-aggregate. Implement Persister with insert, update, delete, and whatever find shape your repositories need. Keep each persister dumb: convert a changeset row into your storage call, no business logic, no UnitOfWork access. Bulk operations belong in a separate repository method, not in persist.

Hydrator strategy. Start with a reflection-based generic hydrator (property name → column key) to get moving, then hand-write hydrators for hot classes or for objects with value objects, enums, dates, and collections. Register them per-class in the metadata factory / hydrator registry.

Mapping driver choice.

  • Greenfield: an attribute-based driver (#[Id], #[Column]) — annotations are deprecated.
  • Legacy models you cannot touch: a PHP-array or XML driver keyed by class name.
  • Dynamic: implement MappingDriver::loadMetadataForClass and read from a config cache.

Change tracking via snapshots. Let UnitOfWork capture a snapshot on load/hydrate. On commit, it diffs current state against the snapshot, computes changesets, and hands them to the persister. Call UnitOfWork::computeChangeSet($object) eagerly when your storage round-trip needs to know before flush.

Identity map discipline. One UnitOfWork per request/scope. clear() between batches to avoid unbounded memory growth; detach() for objects you no longer want tracked; never let two UnitOfWorks manage the same instance.

Repositories. Extend Doctrine\SkeletonMapper\ObjectRepository (or implement the interface) for query methods; keep find and findAll going through the identity map, and let everything else go straight to the persister.

Life-cycle events. Register listeners on the Doctrine event manager for prePersist, postPersist, preUpdate, postUpdate, postLoad, preRemove, postRemove. This is where audit timestamps, slugs, and normalization belong — not in the ObjectManager or persisters.

Testing. An in-memory Persister + simple Hydrator is the fastest way to test domain logic without a database. Assert on UnitOfWork::getEntityChangeSet() to prove change detection works for your mapping.

DI wiring. Provide factory classes for: event manager, metadata driver, metadata factory (wrapped in a cache decorator), hydrator registry, persister registry, UnitOfWork, ObjectManager. In Symfony/Laminas, expose ObjectManager as the only injectable service.

Transaction boundary. Because this package does not own your storage, align flush() with your storage transaction (DB transaction, HTTP batch request, queue drain). If the persister can partially fail, implement compensation or re-enqueue; UnitOfWork will not roll itself back.

Gotchas and Tips

  • Archived — plan for the exit. No security or PHP-version fixes will land. Pin composer.json to the last release, and if you depend on it long-term, vendor-fork or copy the handful of abstractions you actually use (ObjectManager, UnitOfWork, Persister, Hydrator, ClassMetadata) — they are small.
  • Not an ORM. No DQL, no schema tool, no migrations, no proxy generation by default, no association management. Everything Doctrine gives you for free (lazy collections, cascades, orphan removal) you must implement.
  • Identifiers are mandatory. ClassMetadata requires an identifier mapping; a class without one will fail in UnitOfWork. Composite identifiers are supported but make your identity-map key function explicit and consistent.
  • Snapshot-based change detection misses in-place mutation. If a mapped property is an array or a mutable value object that you mutate without reassigning, the diff may not see it. Either reassign the property, map the field as a whole, or call computeChangeSet after mutation.
  • Proxies and lazy loading. Skeleton-mapper's proxy support leans on proxy-manager, which is itself EOL. Prefer eager loading + explicit repository calls; if you need proxies, use friendsofphp/proxy-manager-lts and make sure hydrators skip uninitialized proxy state.
  • Annotations are dead. Use the attribute driver or a hand-rolled driver; doctrine/annotations is deprecated and will not run on modern PHP without care.
  • Event manager version skew. doctrine/event-manager 1.x vs 2.x changes listener registration and dispatch signatures. Match the version your skeleton-mapper release expects, or wrap event dispatch in your own small adapter.
  • merge semantics are yours. Do not assume ORM behavior for detached objects; define what merge returns and whether it creates a new tracked instance or re-attaches.
  • Debugging. The UnitOfWork is the source of truth: inspect getEntityChangeSet(), the scheduled insertions/updates/deletions, and the identity map before blaming the persister. Wrap persister calls in a logger during development to see exactly what SQL/HTTP/storage request is emitted.
  • Batching. flush() per entity will be slow against remote stores. Collect changes, flush in batches, and clear() afterward to release snapshots.
  • Extension points worth knowing: Persister, Hydrator, MappingDriver, ClassMetadataFactory (decorate with a cache), and ObjectRepository. Everything else is orchestration; those five are where your implementation work concentrates.
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.
vento/laravel-airbrake
wfeller/laravel-batch
maco-studios/mage-os-installer
busarm/armie
siberfx/laravel-logger
kfteam1991/dcat-admin
dashed/dashed-laposta
dashed/dashed-ecommerce-montaportal
dashed/dashed-ecommerce-etsy
neohq/string-blade-compiler
log1x/acf-composer
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager