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

Mapper Laravel Package

atlas/mapper

Atlas.Mapper is a lightweight data mapper for Atlas, used as the core of Atlas.Orm or standalone. Create a MapperLocator with PDO params, fetch mappers by class, and work with Records via select, fetch, insert, update, delete, and persist.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Generate Data-Source Classes Run atlas-cli generate:data-source (requires Atlas.Cli 2.x) to scaffold mapper classes for your database tables. Example:

    atlas-cli generate:data-source --connection=mysql --path=src/DataSources
    
  2. Initialize MapperLocator Configure a MapperLocator with your PDO connection:

    use Atlas\Table\MapperLocator;
    
    $mapperLocator = MapperLocator::new('mysql://user:pass@localhost/dbname');
    
  3. Retrieve a Mapper Fetch a mapper for a generated class (e.g., User):

    $userMapper = $mapperLocator->get(User::class);
    
  4. Basic CRUD Operations

    // Create
    $user = $userMapper->newRecord(['name' => 'John Doe']);
    $userMapper->persist($user);
    
    // Read
    $user = $userMapper->fetchRecord(1); // Fetch by ID
    
    // Update
    $user->name = 'Jane Doe';
    $userMapper->persist($user);
    
    // Delete
    $userMapper->delete($user);
    

First Use Case: Eager-Loading Relationships

Fetch a record with its related data (e.g., posts for a User):

$user = $userMapper->fetchRecord(1, [
    'posts' => fn($select) => $select->where('published', true)
]);

Implementation Patterns

Core Workflows

1. Repository Pattern Integration

Use MapperLocator as a service container for mappers in Laravel’s service container:

// In a service provider
$this->app->singleton(MapperLocator::class, fn() =>
    MapperLocator::new(config('database.connections.mysql'))
);

// In a controller
$userMapper = app(User::class);

2. Query Building with MapperSelect

Chain methods for complex queries:

$users = $userMapper->select()
    ->where('active', true)
    ->orderBy('created_at', 'desc')
    ->limit(10)
    ->fetchRecords();

3. Relationship Management

Define relationships in generated classes (e.g., User):

// In User.php (auto-generated)
public function posts(): HasMany
{
    return $this->hasMany(Post::class, 'user_id');
}

Load relationships dynamically:

$user = $userMapper->fetchRecord(1, ['posts' => fn($select) => $select->limit(5)]);

4. Bulk Operations

// Insert multiple records
$users = [
    ['name' => 'Alice'],
    ['name' => 'Bob']
];
$userMapper->insert($users);

// Update in bulk
$userMapper->update(['active' => true], ['role' => 'admin']);

5. Transactions

Wrap operations in a transaction:

$mapperLocator->getConnection()->beginTransaction();
try {
    $userMapper->persist($user);
    $postMapper->persist($post);
    $mapperLocator->getConnection()->commit();
} catch (\Exception $e) {
    $mapperLocator->getConnection()->rollBack();
    throw $e;
}

Integration Tips

Laravel Eloquent Interop

Use Atlas.Mapper alongside Eloquent by converting records:

$user = $userMapper->fetchRecord(1);
$eloquentUser = new App\Models\User($user->getArrayCopy());

Event Listeners

Extend MapperEvents for custom logic:

use Atlas\Table\MapperEvents;

MapperEvents::modifySelect(function ($select) {
    $select->where('deleted_at', null);
});

Custom Select Classes

Extend MapperSelect for domain-specific queries:

class UserSelect extends MapperSelect
{
    public function activeOnly(): self
    {
        return $this->where('active', true);
    }
}

Many-to-Many Relationships

Define and load through relationships:

// In User.php
public function roles(): BelongsToMany
{
    return $this->belongsToMany(Role::class, 'user_roles');
}

// Load with join
$user = $userMapper->fetchRecord(1, ['roles']);

Gotchas and Tips

Pitfalls

  1. Generated Class Naming

    • Mappers are not suffixed with Mapper (e.g., User, not UserMapper).
    • Regenerate classes with atlas-cli if migrating from older versions.
  2. Foreign Key Quoting

    • Atlas.Mapper automatically quotes foreign table/column names in relationships.
    • Ensure your database schema matches the generated mapper definitions.
  3. Identity-Mapped Records

    • Avoid calling fetchRecords() on a RecordSet where all records are identity-mapped (fixed in v1.3.1).
    • Use fetchRecordSet() instead for consistency.
  4. Many-to-Many Through Tables

    • When persisting many-to-many relationships, ensure the through table is properly defined.
    • Use joinWith() for eager-loading:
      $userMapper->select()->joinWith('roles')->fetchRecords();
      
  5. Dynamic Properties

    • Dynamically declared properties (e.g., $user->newField = 'value') are not persisted by default.
    • Use setDynamicProperty() or configure MapperLocator to handle them.
  6. Cascading Deletes

    • Relationships support CASCADE deletes, but ensure this is explicitly set:
      $this->hasMany(Post::class, 'user_id')->setDelete(DeletableRelationship::CASCADE);
      

Debugging Tips

  1. SQL Logging Enable PDO logging to inspect generated queries:

    $mapperLocator->getConnection()->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $mapperLocator->getConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    
  2. Relationship Debugging Dump relationships for a record:

    dd($user->getRelationships());
    
  3. Performance Bottlenecks

    • For large many-to-many datasets, use joinWith() with limits:
      $userMapper->select()->joinWith('roles', fn($sub) => $sub->limit(100))->fetchRecords();
      
    • Benchmark with tests/bench.php (included in the package).

Extension Points

  1. Custom Mapper Classes Extend Mapper for domain-specific logic:

    class UserMapper extends Mapper
    {
        public function findByEmail(string $email): ?User
        {
            return $this->fetchRecordBy(['email' => $email]);
        }
    }
    
  2. Event Hooks Subscribe to mapper events:

    MapperEvents::onPersist(function ($record) {
        if ($record instanceof User) {
            \Log::info("User {$record->id} persisted");
        }
    });
    
  3. Custom Selectors Override fetchRecordBy() or fetchRecordsBy() for custom logic:

    class UserMapper extends Mapper
    {
        public function fetchActiveUsers(): RecordSet
        {
            return $this->fetchRecordsBy(['active' => true]);
        }
    }
    
  4. Table Locator Access Access the underlying TableLocator for advanced queries:

    $tableLocator = $mapperLocator->getTableLocator();
    $table = $tableLocator->get(User::class);
    
  5. Dynamic Relationships Define relationships dynamically at runtime:

    $user->defineRelationship('temporary_roles', fn() =>
        $this->belongsToMany(Role::class, 'temp_user_roles')
    );
    

Configuration Quirks

  1. Connection Management

    • Ensure your PDO connection is properly configured (e.g., mysql:host=...).
    • For SQLite, use :memory: or a file path (e.g., sqlite:/path/to/db.sqlite).
  2. Case Sensitivity

    • Table/column names in relationships are case-sensitive by default.
    • Use setQuoteIdentifier() on TableLocator if needed:
      $tableLocator->get(User::class)->setQuoteIdentifier(true);
      
  3. Default Join Types

    • joinWith() defaults to INNER JOIN. Use LEFT, RIGHT, or FULL explicitly:
      $select->joinWith('LEFT
      
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.
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin