PHP 设计模式 - 结构型 - 数据映射模式(Data Map

2021-01-15  本文已影响0人  SylviaYuan95

1. 模式定义

数据映射:是持久化数据存储层(通常是关系型数据库)和驻于内存得数据表现层直接进行双向数据传输得数据访问层
数据映射模式的目的:让持久化数据存储层、驻于内存的数据表现层、以及数据映射本身三者相互独立、互不依赖。
这个数据访问层由一个或多个映射器(或者数据访问对象)组成,用于实现数据传输。通用的数据访问层可以处理不同的实体类型,而专用的则处理一个或几个。

2. 数据映射模式 ( Data Mapper ) VS 活动记录模式(Active Record)

首先我们将 ORM 模型拆分开来就是两个功能

  1. 数据操作 - 对数据对象做变更,就是我们常说的业务逻辑。
  2. 数据持久化 - 将数据落地,比如存储到 MySQL,MongoDB 等不同的数据库。

数据映射模式 ( Data Mapper ):主张两个功能必须分开,扩展灵活,逼格高,数据模型遵循单一职责原则(Single Responsibility Principle)。使用Data Mappers的框架数量相比ActiveRecord要少很多,主要有Java Hibernate,PHP Doctrine,SQLAlchemy in Python,EntityFramework for Microsoft .NET。

<?php
$model = new User();
$model->setId(1);
$model->setAccount('it2048');
$model->setPassword('123456'); 

$result = (new UserMapper())->save($user);

$model 对象属性的修改属于业务逻辑UserMapper涵括持久化逻辑

活动记录模式 (Active Record) : 主张把两个功能合在一起,简单方便,易上手。用ActiveRecord ORM的PHP框架有Laravel, Yii, CodeIgniter, CakePHP等。其他语言用的有 Ruby on Rails,Django等。

<?php
$model = new User();
$model->user_id = 1;
$model->name = 'Sylvia';
$model->save();

$model 属性的修改属于业务逻辑,调用save()方法属于持久化逻辑。使用者完全不用关心save()方法执行后数据是存储到MySQL还是MongoDB,在开发过程中可以将精力全部放到业务逻辑,开发速度非常快。

3. UML类图

image.png

4. 示例代码

业务逻辑 User

<?php

namespace DesignPattern\Structural\DataMapper;

/**
 * 数据库记录在内存的表现层
 */
class User
{
    /**
     * @var int
     */
    protected $userId;

    /**
     * @var string
     */
    protected $name;


    /**
     * @param null $id
     * @param null $name
     */
    public function __construct($id = null, $name = null)
    {
        $this->userId = $id;
        $this->name = $name;
    }

    /**
     * @return int
     */
    public function getUserId()
    {
        return $this->userId;
    }

    /**
     * @param int $userId
     */
    public function setUserID($userId)
    {
        $this->userId = $userId;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param string $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }
}

持久化逻辑 UserMapper

<?php

namespace DesignPattern\Structural\DataMapper;

use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\DriverManager;

/**
 * 数据映射类
 * 数据库的连接与修改,这里用的是扩展包DBAL适配器,用于连接各种数据库并进行操作  composer require doctrine/dbal:3.0.0 进行下载安装
 */
class UserMapper
{
    protected $table = 'users';
    protected $adapter;

    /**
     * UserMapper constructor.
     * @throws DBALException
     */
    public function __construct()
    {
        $connectionParams = array(
            'dbname' => 'design_pattern',
            'user' => 'root',
            'password' => '123456',
            'host' => '127.0.0.1:3316',
            'driver' => 'pdo_mysql',
        );

        $adapter = DriverManager::getConnection($connectionParams);
        $this->adapter = $adapter;
    }


    /**
     * 将用户对象保存到数据库
     *
     * @param User $user
     * @return bool
     * @throws DBALException
     */
    public function save(User $user)
    {
        // $data的键名对应数据库表字段
        $data = array(
            'user_id' => $user->getUserId(),
            'name' => $user->getName(),
        );
        // 如果没有指定ID则在数据库中创建新纪录,否则更新已有记录
        if (null === ($id = $user->getUserId())) {
            unset($data['user_id']);
            $this->adapter->insert($this->table, $data);
            return true;
        } else {
            $this->adapter->update($this->table, $data, array('user_id ' => $id));
            return true;
        }
    }

    /**
     * 基于ID在数据库中查找用户并返回用户实例
     * @param $id
     * @return mixed
     * @throws DBALException
     * @throws \InvalidArgumentException
     */
    public function findById($id)
    {
        $result = $this->adapter->executeQuery("select * from ".$this->table." where user_id = ".$id)->fetch();

        if (empty($result)) {
            throw new \InvalidArgumentException("User #$id not found");
        }
        return $this->mapObject($result);
    }

    /**
     * 获取数据库所有记录并返回用户实例数组
     * @return array
     * @throws DBALException
     */
    public function findAll()
    {
        $resultSet = $this->adapter->executeQuery("select * from ".$this->table)->fetchAll();
        $entries = array();

        foreach ($resultSet as $row) {
            $entries[] = $this->mapObject($row);
        }

        return $entries;
    }

    /**
     * 映射表记录到对象
     *
     * @param array $row
     *
     * @return User
     */
    protected function mapObject(array $row)
    {
        $entry = new User();
        $entry->setUserID((int)$row['user_id']);
        $entry->setName($row['name']);

        return $entry;
    }

}

单元测试:

<?php

namespace DesignPattern\Tests;

use DesignPattern\Structural\DataMapper\User;
use DesignPattern\Structural\DataMapper\UserMapper;
use PHPUnit\Framework\InvalidArgumentException;
use PHPUnit\Framework\TestCase;

/**
 * 测试数据映射模式
 * Class DataMapperTest
 * @package Creational\Singleton\Tests
 */
class DataMapperTest extends TestCase
{

    public function getNewUser()
    {
        return array(array(new User(null, 'Sylvia')));
    }


    public function getExistingUser()
    {
        return array(array(new User(1, 'Sylvia1')));
    }

    /**
     * @param User $user
     *
     * @dataProvider getNewUser
     *
     * @throws \Doctrine\DBAL\DBALException
     */
    public function testCreate(User $user)
    {
        $result = (new UserMapper())->save($user);
        $this->assertIsBool($result);
    }

    /**
     * @param User $user
     * @dataProvider getExistingUser
     * @throws \Doctrine\DBAL\DBALException
     */
    public function testUpdate(User $user)
    {
        $updateResult = (new UserMapper())->save($user);
        $this->assertIsBool($updateResult);
    }

    /**
     * @param User $existing
     * @dataProvider getExistingUser
     * @throws \Doctrine\DBAL\DBALException
     */
    public function testFindById(User $existing)
    {
        $user = (new UserMapper())->findById(1);
        $this->assertEquals($existing, $user);
    }


    /**
     * @dataProvider getExistingUser
     * @throws \Doctrine\DBAL\DBALException
     */
    public function testFindAll()
    {

        $users = (new UserMapper())->findAll();
        $this->assertIsArray($users);
        foreach ($users as $user) {
            $this->assertIsObject($user);
        }
    }

}

参考文档:https://laravelacademy.org/post/2739.html
教程源码:https://github.com/SylviaYuan1995/DesignPatternDemo

上一篇下一篇

猜你喜欢

热点阅读