PHP 设计模式 - 结构型 - 数据映射模式(Data Map
1. 模式定义
数据映射:是持久化数据存储层(通常是关系型数据库)和驻于内存得数据表现层直接进行双向数据传输得数据访问层。
数据映射模式的目的:让持久化数据存储层、驻于内存的数据表现层、以及数据映射本身三者相互独立、互不依赖。
这个数据访问层由一个或多个映射器(或者数据访问对象)组成,用于实现数据传输。通用的数据访问层可以处理不同的实体类型,而专用的则处理一个或几个。
2. 数据映射模式 ( Data Mapper ) VS 活动记录模式(Active Record)
首先我们将 ORM 模型拆分开来就是两个功能
- 数据操作 - 对数据对象做变更,就是我们常说的业务逻辑。
- 数据持久化 - 将数据落地,比如存储到 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.png4. 示例代码
业务逻辑 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