Skip to main content

Using Mapper Object

The Mapper object is the main object that you will use to interact with the database and your model.

Why Use Mapper Objects?

The Key Advantage: Decoupling

The biggest advantage of using Mapper objects is decoupling your code from the database. With Mapper, you can:

  • Work with legacy models - Map existing classes without modifying them
  • Use third-party libraries - Map models you don't own or can't change
  • Keep domain models clean - No database annotations polluting your business logic
  • Change database schema independently - Rename columns without touching your models
  • Support multiple databases - Same model, different mappings for different databases

When to Use Mapper Objects vs Attributes

ApproachUse WhenBenefits
Attributes (#[FieldAttribute])You control the model classesConvenient, everything in one place
Mapper ObjectsLegacy code, third-party models, clean architectureComplete decoupling, no model changes needed

For the majority of cases, you can use attributes as shown in the Getting Started section. This creates the mapper automatically.

Use Cases

Use Case 1: Legacy/Third-Party Code

Let's say you have a legacy class or a third-party library class that you cannot modify:

<?php
// Legacy class from old codebase - YOU CANNOT CHANGE THIS
class Users
{
public $id;
public $name;
public $createdate;
}

And your database has different column names:

CREATE TABLE user_accounts
(
user_id INT PRIMARY KEY,
full_name VARCHAR(255),
created_at DATETIME
);

Solution: External Mapping with Mapper Object

Instead of modifying the Users class (which you can't or don't want to do), you create an external mapping:

<?php
use ByJG\MicroOrm\Mapper;
use ByJG\MicroOrm\FieldMapping;

// Create mapper externally - no changes to Users class needed!
$mapper = new Mapper(
Users::class, // Your legacy/third-party class
'user_accounts', // Actual database table name
'user_id' // Actual primary key column name
);

// Map properties to different column names
$mapper->addFieldMapping(
FieldMapping::create('id')->withFieldName('user_id')
);
$mapper->addFieldMapping(
FieldMapping::create('name')->withFieldName('full_name')
);
$mapper->addFieldMapping(
FieldMapping::create('createdate')->withFieldName('created_at')
);

Now use the repository for CRUD operations:

<?php
$dbDriver = \ByJG\AnyDataset\Db\Factory::getDbInstance('mysql://user:password@server/schema');
$executor = \ByJG\AnyDataset\Db\DatabaseExecutor::using($dbDriver);

$repository = new \ByJG\MicroOrm\Repository($executor, $mapper);

// Get data - automatically maps database columns to object properties
$user = $repository->get(10);
echo $user->name; // ← Comes from 'full_name' column

// Save data - automatically maps object properties to database columns
$user->name = "New name";
$repository->save($user); // ← Updates 'full_name' column

// Delete
$repository->delete($user);

Key Point: The Users class remains completely untouched - all mapping happens externally!

Use Case 2: Clean Architecture with Pure Domain Models

One of the most powerful use cases for Mapper objects is implementing Clean Architecture where your domain models remain completely pure - no database concerns whatsoever.

Pure Domain Model (No Database Knowledge)

<?php
namespace App\Domain\User;

// Pure domain model - no attributes, no ORM knowledge
class User
{
private string $id;
private string $email;
private string $name;
private \DateTimeImmutable $createdAt;

public function __construct(string $id, string $email, string $name)
{
$this->id = $id;
$this->email = $email;
$this->name = $name;
$this->createdAt = new \DateTimeImmutable();
}

// Pure business logic - no database concerns
public function changeEmail(string $newEmail): void
{
if (!filter_var($newEmail, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Invalid email');
}
$this->email = $newEmail;
}

public function getId(): string { return $this->id; }
public function getEmail(): string { return $this->email; }
public function getName(): string { return $this->name; }
public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; }
}

Infrastructure Layer Mapping (Separate from Domain)

<?php
namespace App\Infrastructure\Persistence;

use ByJG\MicroOrm\Mapper;
use ByJG\MicroOrm\FieldMapping;
use ByJG\MicroOrm\Updatable;
use App\Domain\User\User;

// Mapping configuration lives in infrastructure layer
class UserMapper
{
public static function create(): Mapper
{
$mapper = new Mapper(
User::class,
'users',
'id'
);

// Map private properties (using getters/setters)
$mapper->addFieldMapping(FieldMapping::create('id'));
$mapper->addFieldMapping(FieldMapping::create('email'));
$mapper->addFieldMapping(FieldMapping::create('name'));
$mapper->addFieldMapping(
FieldMapping::create('createdAt')
->withFieldName('created_at')
// Transform DateTime to string for DB
->withUpdateFunction(fn($value) => $value->format('Y-m-d H:i:s'))
// Transform string from DB to DateTime
->withSelectFunction(fn($value) => new \DateTimeImmutable($value))
);

return $mapper;
}
}

Repository in Infrastructure Layer

<?php
namespace App\Infrastructure\Persistence;

use ByJG\MicroOrm\Repository;
use ByJG\AnyDataset\Db\DatabaseExecutor;

class UserRepository
{
private Repository $repository;

public function __construct(DatabaseExecutor $executor)
{
$this->repository = new Repository($executor, UserMapper::create());
}

public function findById(string $id): ?User
{
return $this->repository->get($id);
}

public function save(User $user): void
{
$this->repository->save($user);
}
}

Benefits: Pure domain model, testable without database, flexible to schema changes, clear architectural separation.

Creating Mappers Reference

Basic Mapper (Properties Match Column Names)

<?php
// When property names match database column names
$mapper = new \ByJG\MicroOrm\Mapper(
Users::class, // Class name
'users', // Table name
'id' // Primary key
);

// No field mappings needed!

Mapper with Field Mappings

<?php
// When property names differ from column names
$mapper = new \ByJG\MicroOrm\Mapper(
Users::class,
'users',
'id'
);

// Map property to different column name
$mapper->addFieldMapping(
FieldMapping::create('createdate')->withFieldName('created')
);

Summary

FeatureAttributesMapper Objects
Convenience✅ Very convenient⚠️ More setup required
Legacy Support❌ Need to modify classes✅ No modification needed
Clean Architecture❌ Couples domain to ORM✅ Pure domain models
Third-party Classes❌ Can't add attributes✅ Works perfectly
Multiple Mappings❌ One mapping per class✅ Different mappings possible
Best ForNew projects you controlLegacy, third-party, clean architecture

Choose Mapper objects when you need complete decoupling between your domain models and database concerns.