2.4. Data Mapper
2.4.1. Rôle
Un Data Mapper est une couche d’accès aux données qui effectue un transfert bidirectionnel de données entre un stockage de données permanent (souvent une base de données relationnelle) et une représentation de données en mémoire (la couche de domaine). L’objectif de ce modèle est de faire en sorte que la représentation en mémoire et le stock de données permanent soient indépendants l’un de l’autre et du Data Mapper lui-même. La couche est composée d’un ou plusieurs mappeurs (ou objets d’accès aux données), qui effectuent le transfert de données. Les mappeurs génériques traiteront de nombreux types d’entités de domaine différents, les mappeurs spécialisés n’en traiteront qu’un ou quelques-uns.
Le point clé de ce modèle est que, contrairement au pattern [Active Record](https://en.wikipedia.org/wiki/Active_record_pattern), le modèle de données suit le principe de responsabilité unique.
2.4.2. Examples
DB Object Relational Mapper (ORM) : Doctrine2 utilise un DAO nommé « EntityRepository ».
2.4.3. Diagramme UML
2.4.4. Code
Vous pouvez également trouver ce code sur GitHub
User.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\DataMapper;
6
7class User
8{
9 public static function fromState(array $state): User
10 {
11 // validate state before accessing keys!
12
13 return new self(
14 $state['username'],
15 $state['email']
16 );
17 }
18
19 public function __construct(private string $username, private string $email)
20 {
21 }
22
23 public function getUsername(): string
24 {
25 return $this->username;
26 }
27
28 public function getEmail(): string
29 {
30 return $this->email;
31 }
32}
UserMapper.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\DataMapper;
6
7use InvalidArgumentException;
8
9class UserMapper
10{
11 public function __construct(private StorageAdapter $adapter)
12 {
13 }
14
15 /**
16 * finds a user from storage based on ID and returns a User object located
17 * in memory. Normally this kind of logic will be implemented using the Repository pattern.
18 * However the important part is in mapRowToUser() below, that will create a business object from the
19 * data fetched from storage
20 */
21 public function findById(int $id): User
22 {
23 $result = $this->adapter->find($id);
24
25 if ($result === null) {
26 throw new InvalidArgumentException("User #$id not found");
27 }
28
29 return $this->mapRowToUser($result);
30 }
31
32 private function mapRowToUser(array $row): User
33 {
34 return User::fromState($row);
35 }
36}
StorageAdapter.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\DataMapper;
6
7class StorageAdapter
8{
9 public function __construct(private array $data)
10 {
11 }
12
13 /**
14 * @return array|null
15 */
16 public function find(int $id)
17 {
18 if (isset($this->data[$id])) {
19 return $this->data[$id];
20 }
21
22 return null;
23 }
24}
2.4.5. Test
Tests/DataMapperTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\DataMapper\Tests;
6
7use InvalidArgumentException;
8use DesignPatterns\Structural\DataMapper\StorageAdapter;
9use DesignPatterns\Structural\DataMapper\User;
10use DesignPatterns\Structural\DataMapper\UserMapper;
11use PHPUnit\Framework\TestCase;
12
13class DataMapperTest extends TestCase
14{
15 public function testCanMapUserFromStorage()
16 {
17 $storage = new StorageAdapter([1 => ['username' => 'someone', 'email' => 'someone@example.com']]);
18 $mapper = new UserMapper($storage);
19
20 $user = $mapper->findById(1);
21
22 $this->assertInstanceOf(User::class, $user);
23 }
24
25 public function testWillNotMapInvalidData()
26 {
27 $this->expectException(InvalidArgumentException::class);
28
29 $storage = new StorageAdapter([]);
30 $mapper = new UserMapper($storage);
31
32 $mapper->findById(1);
33 }
34}