2.4. Data Mapper
2.4.1. Purpose
A Data Mapper, is a Data Access Layer that performs bidirectionaltransfer of data between a persistent data store (often a relationaldatabase) and an in memory data representation (the domain layer). Thegoal of the pattern is to keep the in memory representation and thepersistent data store independent of each other and the data mapperitself. The layer is composed of one or more mappers (or Data AccessObjects), performing the data transfer. Mapper implementations vary inscope. Generic mappers will handle many different domain entity types,dedicated mappers will handle one or a few.
The key point of this pattern is, unlike Active Record pattern, the datamodel follows Single Responsibility Principle.
2.4.2. Examples
- DB Object Relational Mapper (ORM) : Doctrine2 uses DAO named as“EntityRepository”
2.4.3. UML Diagram
2.4.4. Code
You can also find this code on GitHub
User.php
- <?php
- namespace DesignPatterns\Structural\DataMapper;
- class User
- {
- /**
- * @var string
- */
- private $username;
- /**
- * @var string
- */
- private $email;
- public static function fromState(array $state): User
- {
- // validate state before accessing keys!
- return new self(
- $state['username'],
- $state['email']
- );
- }
- public function __construct(string $username, string $email)
- {
- // validate parameters before setting them!
- $this->username = $username;
- $this->email = $email;
- }
- /**
- * @return string
- */
- public function getUsername()
- {
- return $this->username;
- }
- /**
- * @return string
- */
- public function getEmail()
- {
- return $this->email;
- }
- }
UserMapper.php
- <?php
- namespace DesignPatterns\Structural\DataMapper;
- class UserMapper
- {
- /**
- * @var StorageAdapter
- */
- private $adapter;
- /**
- * @param StorageAdapter $storage
- */
- public function __construct(StorageAdapter $storage)
- {
- $this->adapter = $storage;
- }
- /**
- * finds a user from storage based on ID and returns a User object located
- * in memory. Normally this kind of logic will be implemented using the Repository pattern.
- * However the important part is in mapRowToUser() below, that will create a business object from the
- * data fetched from storage
- *
- * @param int $id
- *
- * @return User
- */
- public function findById(int $id): User
- {
- $result = $this->adapter->find($id);
- if ($result === null) {
- throw new \InvalidArgumentException("User #$id not found");
- }
- return $this->mapRowToUser($result);
- }
- private function mapRowToUser(array $row): User
- {
- return User::fromState($row);
- }
- }
StorageAdapter.php
- <?php
- namespace DesignPatterns\Structural\DataMapper;
- class StorageAdapter
- {
- /**
- * @var array
- */
- private $data = [];
- public function __construct(array $data)
- {
- $this->data = $data;
- }
- /**
- * @param int $id
- *
- * @return array|null
- */
- public function find(int $id)
- {
- if (isset($this->data[$id])) {
- return $this->data[$id];
- }
- return null;
- }
- }
2.4.5. Test
Tests/DataMapperTest.php
- <?php
- namespace DesignPatterns\Structural\DataMapper\Tests;
- use DesignPatterns\Structural\DataMapper\StorageAdapter;
- use DesignPatterns\Structural\DataMapper\User;
- use DesignPatterns\Structural\DataMapper\UserMapper;
- use PHPUnit\Framework\TestCase;
- class DataMapperTest extends TestCase
- {
- public function testCanMapUserFromStorage()
- {
- $storage = new StorageAdapter([1 => ['username' => 'domnikl', 'email' => 'liebler.dominik@gmail.com']]);
- $mapper = new UserMapper($storage);
- $user = $mapper->findById(1);
- $this->assertInstanceOf(User::class, $user);
- }
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testWillNotMapInvalidData()
- {
- $storage = new StorageAdapter([]);
- $mapper = new UserMapper($storage);
- $mapper->findById(1);
- }
- }