Data Mapper Architecture
The Data Mapper pattern separates your domain objects from database persistence. Entities contain business logic and data, while a separate mapper layer — the EntityManager — handles storage and retrieval.
How It Works
The Data Mapper pattern separates your domain objects from the database. Entities hold data and business logic; the database holds records. The EntityManager sits between them, handling all persistence operations so neither side needs to know about the other. The pattern allows you to instantiate, test, and reuse entities without a database connection.
<?php
class ProductEntity {
private ?int $productId = null;
private string $name;
private float $price;
// Business logic lives here
public function applyDiscount(float $percent): void {
$this->price = $this->price * (1 - $percent / 100);
}
// Getters and setters
public function getProductId(): ?int { return $this->productId; }
public function getName(): string { return $this->name; }
public function setName(string $name): void { $this->name = $name; }
public function getPrice(): float { return $this->price; }
public function setPrice(float $price): void { $this->price = $price; }
}
Database operations go through the EntityManager:
<?php
$entityManager = new EntityManager($connection, $config);
// Creating
$product = new ProductEntity();
$product->setName('Wireless Mouse');
$product->setPrice(29.99);
$entityManager->persist($product);
$entityManager->flush();
// Updating
$product = $entityManager->find(ProductEntity::class, 123);
$product->applyDiscount(10);
$entityManager->persist($product);
$entityManager->flush();
The entity never knows it's being stored. It doesn't know what database it lives in, what table it maps to at runtime, or when it gets saved. The EntityManager owns that responsibility entirely.
Frequently Asked Questions
Why does ObjectQuel need a separate persist() and flush() call instead of saving immediately?
persist() only schedules an entity for an INSERT or UPDATE; nothing is written until flush() runs. This lets the UnitOfWork batch every pending change for a request into as few database round-trips as possible, rather than issuing a write the instant each property changes.
Can I use an entity without a database connection, such as in a unit test?
Yes — that's one of the main benefits of the Data Mapper pattern. Since entities carry no persistence knowledge, you can instantiate ProductEntity directly and call applyDiscount() on it in a plain unit test with no EntityManager, connection, or database involved at all.
How is this different from Active Record ORMs where the model saves itself?
In Active Record, the entity typically has its own save()/delete() methods and usually extends a base class. In ObjectQuel's Data Mapper, the entity has no base class and no persistence methods — it doesn't know what table it maps to or when it gets saved. All of that lives in the EntityManager instead, so entities stay plain domain objects.