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

Alt DataMapper UML Diagram

2.4.4. Code

You can also find this code on GitHub

User.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\DataMapper;
  4.  
  5. class User
  6. {
  7. /**
  8. * @var string
  9. */
  10. private $username;
  11.  
  12. /**
  13. * @var string
  14. */
  15. private $email;
  16.  
  17. public static function fromState(array $state): User
  18. {
  19. // validate state before accessing keys!
  20.  
  21. return new self(
  22. $state['username'],
  23. $state['email']
  24. );
  25. }
  26.  
  27. public function __construct(string $username, string $email)
  28. {
  29. // validate parameters before setting them!
  30.  
  31. $this->username = $username;
  32. $this->email = $email;
  33. }
  34.  
  35. /**
  36. * @return string
  37. */
  38. public function getUsername()
  39. {
  40. return $this->username;
  41. }
  42.  
  43. /**
  44. * @return string
  45. */
  46. public function getEmail()
  47. {
  48. return $this->email;
  49. }
  50. }

UserMapper.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\DataMapper;
  4.  
  5. class UserMapper
  6. {
  7. /**
  8. * @var StorageAdapter
  9. */
  10. private $adapter;
  11.  
  12. /**
  13. * @param StorageAdapter $storage
  14. */
  15. public function __construct(StorageAdapter $storage)
  16. {
  17. $this->adapter = $storage;
  18. }
  19.  
  20. /**
  21. * finds a user from storage based on ID and returns a User object located
  22. * in memory. Normally this kind of logic will be implemented using the Repository pattern.
  23. * However the important part is in mapRowToUser() below, that will create a business object from the
  24. * data fetched from storage
  25. *
  26. * @param int $id
  27. *
  28. * @return User
  29. */
  30. public function findById(int $id): User
  31. {
  32. $result = $this->adapter->find($id);
  33.  
  34. if ($result === null) {
  35. throw new \InvalidArgumentException("User #$id not found");
  36. }
  37.  
  38. return $this->mapRowToUser($result);
  39. }
  40.  
  41. private function mapRowToUser(array $row): User
  42. {
  43. return User::fromState($row);
  44. }
  45. }

StorageAdapter.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\DataMapper;
  4.  
  5. class StorageAdapter
  6. {
  7. /**
  8. * @var array
  9. */
  10. private $data = [];
  11.  
  12. public function __construct(array $data)
  13. {
  14. $this->data = $data;
  15. }
  16.  
  17. /**
  18. * @param int $id
  19. *
  20. * @return array|null
  21. */
  22. public function find(int $id)
  23. {
  24. if (isset($this->data[$id])) {
  25. return $this->data[$id];
  26. }
  27.  
  28. return null;
  29. }
  30. }

2.4.5. Test

Tests/DataMapperTest.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\DataMapper\Tests;
  4.  
  5. use DesignPatterns\Structural\DataMapper\StorageAdapter;
  6. use DesignPatterns\Structural\DataMapper\User;
  7. use DesignPatterns\Structural\DataMapper\UserMapper;
  8. use PHPUnit\Framework\TestCase;
  9.  
  10. class DataMapperTest extends TestCase
  11. {
  12. public function testCanMapUserFromStorage()
  13. {
  14. $storage = new StorageAdapter([1 => ['username' => 'domnikl', 'email' => 'liebler.dominik@gmail.com']]);
  15. $mapper = new UserMapper($storage);
  16.  
  17. $user = $mapper->findById(1);
  18.  
  19. $this->assertInstanceOf(User::class, $user);
  20. }
  21.  
  22. /**
  23. * @expectedException \InvalidArgumentException
  24. */
  25. public function testWillNotMapInvalidData()
  26. {
  27. $storage = new StorageAdapter([]);
  28. $mapper = new UserMapper($storage);
  29.  
  30. $mapper->findById(1);
  31. }
  32. }