2.4. Data Mapper

2.4.1. Amaç

Bir Veri Haritalayıcı, kalıcı bir veri deposu (çoğunlukla ilişkisel bir veritabanı (relational database)) ile bellekteki veri sunumu katmanı (alan katmanı, sunum katmanı (domain layer, representation layer)) arasında çift yönlü veri aktarımını gerçekleştiren bir Veri Erişim Katmanıdır (Data Access Layer).Desenin amacı, bellekteki verinin sunum katmanını ve kalıcı veri deposunu birbirinden ve de kendisinden bağımsız tutmaktır. Katman, veri aktarımını gerçekleştiren bir veya daha fazla haritalayıcıdan (veya Veri Erişim Nesnesi’nden (Data Access Object)) oluşur. Haritalayıcı uygulanımları (implementation) kapsama göre değişiklik gösterir. Üretken (generic) haritalayıcılar birkaç farklı alanın varlık türü (entity type) ile işlerken, özel (dedicated) haritalayıcılar bir veya birkaçıyla işler.

Bu desenin kilit noktası, Etkin Kayıt (Active Record) deseninden farklı olarak, veri modelinin Tek Sorumluluk İlkesi’ni (Single Responsibility Principle) uygulamasıdır.

2.4.2. Örnekler

  • İlişkisel Veri Haritalayıcı (Object Relational Mapper (ORM)): Doctrine 2, “EntityRepository” adında bir Veri Erişim Nesnesi (Data Access Object (DAO)) kullanır.

2.4.3. UML Diyagramı

Alt DataMapper UML Diyagramı

2.4.4. Kod

Bu kodu Github üzerinde de bulabilirsiniz.

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}