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 Diagram

2.4.4. Kod

Bu kodu Github üzerinde de bulabilirsiniz.

User.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

declare(strict_types=1);

namespace DesignPatterns\Structural\DataMapper;

class User
{
    public static function fromState(array $state): User
    {
        // validate state before accessing keys!

        return new self(
            $state['username'],
            $state['email']
        );
    }

    public function __construct(private string $username, private string $email)
    {
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function getEmail(): string
    {
        return $this->email;
    }
}

UserMapper.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

declare(strict_types=1);

namespace DesignPatterns\Structural\DataMapper;

use InvalidArgumentException;

class UserMapper
{
    public function __construct(private StorageAdapter $adapter)
    {
    }

    /**
     * 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
     */
    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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

declare(strict_types=1);

namespace DesignPatterns\Structural\DataMapper;

class StorageAdapter
{
    public function __construct(private array $data)
    {
    }

    /**
     * @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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

declare(strict_types=1);

namespace DesignPatterns\Structural\DataMapper\Tests;

use InvalidArgumentException;
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);
    }

    public function testWillNotMapInvalidData()
    {
        $this->expectException(InvalidArgumentException::class);

        $storage = new StorageAdapter([]);
        $mapper = new UserMapper($storage);

        $mapper->findById(1);
    }
}