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

Data Mapper Laravel Package

thruster/data-mapper

Thruster Data Mapper is a Laravel/PHP package for mapping arrays, objects, and DTOs through a clean, configurable API. Define mappings once to transform and hydrate data reliably across APIs, requests, and domain layers.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first. thruster/data-mapper is a 2016-era, single-star component with zero dependents and no release since April 2016. Before you invest a day in it, confirm the package still installs and autoloads on your PHP version:

composer require thruster/data-mapper
php -r "require 'vendor/autoload.php'; var_dump(class_exists('Thruster\\DataMapper\\Mapper'));"

If that fails on PHP 8.x (very likely — expect each(), curly-brace string offsets, or missing return types), treat the rest of this document as a porting guide rather than an adoption guide.

Where to look first, in order:

  1. README.md — for a package this small, the README is the API reference. Expect a composer.json-driven usage snippet with a Mapper, a connection, and an entity class.
  2. tests/ — with 1 star and no docs site, the test suite is the only authoritative spec for hydration rules and edge cases.
  3. src/ — usually a handful of classes: a Mapper/DataMapper, a Repository or Collection, and a Hydrator. Read the hydrator first; it tells you exactly how column names become properties.
  4. composer.json — check require for the exact PHP constraint and whether it depends on doctrine/dbal, raw PDO, or an annotations library. That dependency determines your integration surface.

Minimal first use case — map a users table to a plain PHP User object and read one row:

use Thruster\DataMapper\Mapper;

$pdo = new PDO('mysql:host=127.0.0.1;dbname=app', $user, $pass);

$mapper = new Mapper($pdo);
$mapper->map('users', App\Entities\User::class); // table -> entity

$user = $mapper->find(1);        // hydrated User instance or null
$all  = $mapper->findAll();      // collection/array of User

Wire the $mapper (or a per-entity repository) into Laravel's container in a service provider and you have a working, if spartan, data layer. That is the entire "hello world" — there is no Artisan generator, no migrations, no schema introspection beyond what the component ships.

Implementation Patterns

Entity-as-POPO with explicit mapping. The package's value proposition is keeping domain objects free of Model inheritance. Define entities as plain classes with typed-ish properties and getters/setters, then let the mapper hydrate them:

final class User
{
    public int $id;
    public string $email;
    public ?string $name = null;
    private array $changed = [];

    public function rename(string $name): void
    {
        $this->name = $name;
        $this->changed['name'] = true;
    }
}

Keep setters narrow (rename() instead of setName()) so business rules stay in the entity — the mapper only needs some way to write each column.

Repository-per-aggregate, wrapping the mapper. Don't let controllers touch Mapper directly. One thin repository per aggregate gives you a seam that survives the package being replaced:

final class UserRepository
{
    public function __construct(private Mapper $mapper) {}

    public function find(int $id): ?User
    {
        return $this->mapper->find($id) ?: null;
    }

    public function save(User $user): void
    {
        $this->mapper->persist($user);   // insert vs update by PK presence
    }
}

Bind in a service provider, inject everywhere. This is the only Laravel-specific work you do:

// AppServiceProvider::register()
$this->app->singleton(Mapper::class, function () {
    return new Mapper(new PDO(
        config('database.connections.mysql.dsn'),
        config('database.connections.mysql.username'),
        config('database.connections.mysql.password'),
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
    ));
});
$this->app->bind(UserRepository::class, fn ($app) => new UserRepository($app->make(Mapper::class)));

Then public function __construct(UserRepository $users) in controllers, form requests, jobs — normal Laravel DI.

Read/write workflow in application code. Typical controller shape:

public function update(UpdateUserRequest $request, int $id, UserRepository $users)
{
    $user = $users->find($id) ?? throw new ModelNotFoundException();
    $user->rename($request->validated('name'));
    $users->save($user);
    return response()->noContent();
}

Batch reads and transactions. Because it sits on raw PDO, transactions are yours to manage:

$pdo->beginTransaction();
try {
    foreach ($rows as $row) { $users->save($row); }
    $pdo->commit();
} catch (\Throwable $e) {
    $pdo->rollBack();
    throw $e;
}

Wrap this in a small Transactional helper — the package will not do it for you.

Cross-cutting concerns via decorators. Rather than patching the mapper, decorate the repository for logging, caching, or authorization:

final class CachedUserRepository implements UserRepositoryContract
{
    public function __construct(private UserRepositoryContract $inner, private Cache $cache) {}
    public function find(int $id): ?User
    {
        return $this->cache->remember("user:$id", 300, fn () => $this->inner->find($id));
    }
}

Use it where Eloquent is a poor fit. The genuine daily-use niche: read-model projections, import scripts, and legacy tables you don't want to model as Eloquent. Queue workers and Artisan commands are the sweet spot — no request lifecycle, no Eloquent overhead, entity objects that serialize cleanly.

Gotchas and Tips

Abandonment is the top gotcha. Last release 2016, one star, no dependents, no maintainer activity. Concretely this means: no PHP 8.1+ enum/readonly support, no PDO::ATTR_EMULATE_PREPARES guidance, likely broken on PHP 8.2 (dynamic properties deprecation will spam your logs if the hydrator assigns undeclared props). Test on your target PHP version before committing. If it breaks, doctrine/orm, cycle/orm, or even a hand-rolled hydrator gives you the same POPO result with support.

No Laravel integration ships with it. No service provider, no facade, no config file, no package discovery. Everything in "Getting Started" is code you write. Don't expect artisan commands or ->where() chaining — this is a mapper, not a query builder. Bring your own query construction or pair it with illuminate/database's connection for reads.

Hydration is strict about column names. Expects snake_case columns to map to your entity's naming convention. first_name → firstName (or first_name) depends entirely on the hydrator's convention — read the hydrator source, don't guess. A single mismatched column silently yields an uninitialized property. Debug recipe: dump the raw row ($pdo->query(...)->fetch(PDO::FETCH_ASSOC)) next to the hydrated object and diff keys. Nine out of ten bugs are a column/property name mismatch.

No identity map by default. If you find() the same row twice you likely get two distinct objects. Mutate one, save it, and the other goes stale. Either confirm the mapper has an identity map (some do) or enforce one-instance-per-request discipline in your repositories.

No automatic timestamps, no soft deletes, no lazy loading. You set created_at/updated_at in the entity or the repository. Relations are eager or nothing — plan for N+1 if you loop find() over a collection. Batch the read with a single WHERE id IN (...) query and map in memory.

Type coercion is weak. PDO returns strings for integers on many drivers. Cast in the hydrator or in entity setters, otherwise int $id will blow up with a TypeError the moment you add a type declaration.

Extension points are where the package earns its keep. Look for an overridable hydrator or a map() registration hook. If present, subclass it to add casting, snake→camel conversion, and timestamp bookkeeping once instead of in every entity. If there is no such seam, that's your signal to stop and pick a maintained library.

Pin the version and vendor it. With no releases since 2016, add "thruster/data-mapper": "dev-master#<commit>" and consider committing vendor/ or a fork. A composer update in 2026 that resolves to a different branch could quietly change hydration behavior with no changelog to tell you.

Migration path if you adopt and later regret it. Keep all Mapper usage behind your repositories and a single UserRepositoryContract interface. Swapping to Doctrine or Eloquent then becomes a one-file change per aggregate — which is the only reason this package is defensible for greenfield-adjacent work in 2026. Use it for a bounded import script, not for your domain core.

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