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.
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
Initialize MapperLocator
Configure a MapperLocator with your PDO connection:
use Atlas\Table\MapperLocator;
$mapperLocator = MapperLocator::new('mysql://user:pass@localhost/dbname');
Retrieve a Mapper
Fetch a mapper for a generated class (e.g., User):
$userMapper = $mapperLocator->get(User::class);
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);
Fetch a record with its related data (e.g., posts for a User):
$user = $userMapper->fetchRecord(1, [
'posts' => fn($select) => $select->where('published', true)
]);
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);
MapperSelectChain methods for complex queries:
$users = $userMapper->select()
->where('active', true)
->orderBy('created_at', 'desc')
->limit(10)
->fetchRecords();
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)]);
// Insert multiple records
$users = [
['name' => 'Alice'],
['name' => 'Bob']
];
$userMapper->insert($users);
// Update in bulk
$userMapper->update(['active' => true], ['role' => 'admin']);
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;
}
Use Atlas.Mapper alongside Eloquent by converting records:
$user = $userMapper->fetchRecord(1);
$eloquentUser = new App\Models\User($user->getArrayCopy());
Extend MapperEvents for custom logic:
use Atlas\Table\MapperEvents;
MapperEvents::modifySelect(function ($select) {
$select->where('deleted_at', null);
});
Extend MapperSelect for domain-specific queries:
class UserSelect extends MapperSelect
{
public function activeOnly(): self
{
return $this->where('active', true);
}
}
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']);
Generated Class Naming
Mapper (e.g., User, not UserMapper).atlas-cli if migrating from older versions.Foreign Key Quoting
Identity-Mapped Records
fetchRecords() on a RecordSet where all records are identity-mapped (fixed in v1.3.1).fetchRecordSet() instead for consistency.Many-to-Many Through Tables
through table is properly defined.joinWith() for eager-loading:
$userMapper->select()->joinWith('roles')->fetchRecords();
Dynamic Properties
$user->newField = 'value') are not persisted by default.setDynamicProperty() or configure MapperLocator to handle them.Cascading Deletes
CASCADE deletes, but ensure this is explicitly set:
$this->hasMany(Post::class, 'user_id')->setDelete(DeletableRelationship::CASCADE);
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);
Relationship Debugging Dump relationships for a record:
dd($user->getRelationships());
Performance Bottlenecks
joinWith() with limits:
$userMapper->select()->joinWith('roles', fn($sub) => $sub->limit(100))->fetchRecords();
tests/bench.php (included in the package).Custom Mapper Classes
Extend Mapper for domain-specific logic:
class UserMapper extends Mapper
{
public function findByEmail(string $email): ?User
{
return $this->fetchRecordBy(['email' => $email]);
}
}
Event Hooks Subscribe to mapper events:
MapperEvents::onPersist(function ($record) {
if ($record instanceof User) {
\Log::info("User {$record->id} persisted");
}
});
Custom Selectors
Override fetchRecordBy() or fetchRecordsBy() for custom logic:
class UserMapper extends Mapper
{
public function fetchActiveUsers(): RecordSet
{
return $this->fetchRecordsBy(['active' => true]);
}
}
Table Locator Access
Access the underlying TableLocator for advanced queries:
$tableLocator = $mapperLocator->getTableLocator();
$table = $tableLocator->get(User::class);
Dynamic Relationships Define relationships dynamically at runtime:
$user->defineRelationship('temporary_roles', fn() =>
$this->belongsToMany(Role::class, 'temp_user_roles')
);
Connection Management
mysql:host=...).:memory: or a file path (e.g., sqlite:/path/to/db.sqlite).Case Sensitivity
setQuoteIdentifier() on TableLocator if needed:
$tableLocator->get(User::class)->setQuoteIdentifier(true);
Default Join Types
joinWith() defaults to INNER JOIN. Use LEFT, RIGHT, or FULL explicitly:
$select->joinWith('LEFT
How can I help you explore Laravel packages today?