php领域模型和数据映射器

业务逻辑层使用的是领域模型,因为它能使用数据映射器中的大部分模式。

“万物皆对象”,领域模型就是对于项目中各种个体的抽象表达,就是一个类。它常常被描述为一组属性及附加的操作。它们是做某些相关事的某个东西。

领域模型的复杂性主要来自于尝试使模型纯粹(pure),即将领域模型从应用中其他层中分离出来。把领域模型的参与者从表现层分离出来不难,但将这些参与者从数据层中分离出来则不太容易。在理想情形下,领域模型应该只包含它要表达和解决的问题,但在现实中领域模型很难完全去除数据库操作。

领域模型常常映射到数据库结构上。通过将模型与数据库分离,整个层会更加容易测试,而且不会受到数据库结构的改变的影响,也不会受到存储机制的影响。领域模型只关心要完成的核心工作和承担的责任。领域模型设计的简单还是复杂取决于业务逻辑的复杂度。

先来个简单例子(之后都是用这个例子):一个Classroom有多个Student,每个Student有个Score。

sql脚本:

[sql][/sql] view plaincopy

  1. CREATE TABLE `classroom` (
  2.   `id` int(11) NOT NULL AUTO_INCREMENT,
  3.   `name` varchar(32) NOT NULL,
  4.   PRIMARY KEY (`id`)
  5. )
  6. CREATE TABLE `student` (
  7.   `id` int(11) NOT NULL AUTO_INCREMENT,
  8.   `cid` int(11) NOT NULL,
  9.   `name` varchar(16) NOT NULL,
  10.   PRIMARY KEY (`id`),
  11.   KEY `cs_id` (`cid`),
  12.   CONSTRAINT `cs_id` FOREIGN KEY (`cid`) REFERENCES `classroom` (`id`)
  13. )
  14. CREATE TABLE `score` (
  15.   `id` int(11) NOT NULL AUTO_INCREMENT,
  16.   `sid` int(11) NOT NULL,
  17.   `course_name` varchar(32) NOT NULL,
  18.   `score` tinyint(4) NOT NULL,
  19.   PRIMARY KEY (`id`,`sid`,`course_name`),
  20.   KEY `sc_id` (`sid`),
  21.   CONSTRAINT `sc_id` FOREIGN KEY (`sid`) REFERENCES `student` (`id`)
  22. )

 

领域模型抽象基类:DomainObject

[php][/php] view plaincopy

  1. namespace demo\domain;
  2. /**
  3.  * 领域模型抽象基类
  4.  */
  5. abstract class DomainObject {
  6.     protected  $id;
  7.     public function __construct($id = null) {
  8.         if (is_null($id)) {
  9.             $id = -1;
  10.         } else {
  11.             $this->id = $id;
  12.         }
  13.     }
  14.     public function getId() {
  15.         return $this->id;
  16.     }
  17.     public function setId($id) {
  18.         $this->id = $id;
  19.     }
  20.     // 现在比较简单,之后还会扩展
  21.     // ……
  22. }

Score类(对应Score表):

[php][/php] view plaincopy

  1. namespace demo\domain;
  2. use demo\domain\Student;
  3. /**
  4.  * Score
  5.  * 对应表score
  6.  */
  7. class Score extends DomainObject {
  8.     private $score;
  9.     private $courseName;
  10.     // Student对象引用
  11.     private $student;
  12.     public function __construct($id = null, $score = 0, $courseName = ‘unknown’) {
  13.         parent::__construct($id);
  14.         $this->score = $score;
  15.         $this->courseName = $courseName;
  16.     }
  17.     public function getScore() {
  18.         return $this->score;
  19.     }
  20.     public function setScore($score) {
  21.         $this->score = $score;
  22.     }
  23.     public function setCourseName($courseName) {
  24.         $this->courseName = $courseName;
  25.     }
  26.     public function getCourseName() {
  27.         return $this->courseName;
  28.     }
  29.     public function getStudent() {
  30.         return $this->student;
  31.     }
  32.     public function setStudent(Student $student) {
  33.         $this->student = $student;
  34.     }
  35. }

之前说到领域模型最复杂的是映射到数据库结构。我们可以使用数据映射器模式。

 

数据映射器是一个负责将数据库中的一行数据映射到一个对象的类。

有个概念叫“对象关系阻抗不匹配”,指的是对象和关系数据库性质上的差异,比如对象可以有复杂的继承层次,对象中还可以包含另一个对象(关系型数据库的表不行吧),而表可以通过外键表示与其他表之间的关联等。

来看看Mapper的类层次图吧:

Mapper抽象基类:

[php][/php] view plaincopy

  1. namespace demo\mapper;
  2. use demo\base\AppException;
  3. use \demo\base\ApplicationRegistry;
  4. /**
  5.  * Mapper
  6.  */
  7. abstract  class Mapper {
  8.     // PDO
  9.     protected static $PDO;
  10.     // config
  11.     protected static  $dsn, $dbUserName, $dbPassword;
  12.     // PDO选项
  13.     protected static $options = array(
  14.         \PDO::MYSQL_ATTR_INIT_COMMAND => ‘SET NAMES utf8’,
  15.         \PDO::ATTR_ERRMODE,
  16.         \PDO::ERRMODE_EXCEPTION,
  17.     );
  18.     public function __construct() {
  19.         if (!isset(self::$PDO)) {
  20.             // ApplicationRegistry获取数据库连接信息
  21.             $appRegistry = ApplicationRegistry::getInstance();
  22.             self::$dsn = $appRegistry->getDsn();
  23.             self::$dbUserName = $appRegistry->getDbUserName();
  24.             self::$dbPassword = $appRegistry->getDbPassword();
  25.             if (!self::$dsn || !self::$dbUserName || !self::$dbPassword) {
  26.                 throw  new AppException(‘Mapper init failed!’);
  27.             }
  28.             self::$PDO = new \PDO(self::$dsn, self::$dbUserName, self::$dbPassword, self::$options);
  29.         }
  30.     }
  31.     /**
  32.      * 查找指定ID
  33.      * @param int $id
  34.      */
  35.     public function findById($id) {
  36.         $pStmt = $this->getSelectStmt();
  37.         $pStmt->execute(array($id));
  38.         $data = $pStmt->fetch();
  39.         $pStmt->closeCursor();
  40.         $obj = null;
  41.         if (!is_array($data) || !isset($data[‘id’])) {
  42.             return $obj;
  43.         }
  44.         $obj = $this->createObject($data);
  45.         return $obj;
  46.     }
  47.     /**
  48.      * 插入数据
  49.      * @param \demo\domain\DomainObject $obj
  50.      */
  51.     public function insert(\demo\domain\DomainObject $obj) {
  52.         return $this->doInsert($obj);
  53.     }
  54.     /**
  55.      * 删除指定ID
  56.      * @param int $id
  57.      */
  58.     public function deleteById($id) {
  59.         $pStmt = $this->getDeleteStmt();
  60.         $flag = $pStmt->execute(array($id));
  61.         return $flag;
  62.     }
  63.     /**
  64.      * 生成一个$data中值属性的对象
  65.      * @param array $data
  66.      */
  67.     public function createObject(array $data) {
  68.         $obj = $this->doCreateObject($data);
  69.         return $obj;
  70.     }
  71.     public abstract function update(\demo\domain\DomainObject $obj);
  72.     protected abstract function doInsert(\demo\domain\DomainObject $obj);
  73.     protected abstract function doCreateObject(array $data);
  74.     protected abstract function getSelectStmt();
  75.     protected abstract function getDeleteStmt();
  76. }

一个具体Mapper子类ScoreMapper:

[php][/php] view plaincopy

  1. namespace demo\mapper;
  2. use demo\base\AppException;
  3. use \demo\domain\DomainObject;
  4. use \demo\domain\Score;
  5. use \demo\mapper\StuStudentMapper;
  6. /**
  7.  * ScoreMapper
  8.  */
  9. class ScoreMapper  extends Mapper {
  10.     private  static $selectStmt;
  11.     private  static $insertStmt;
  12.     private  static $updateStmt;
  13.     private  static $deleteStmt;
  14.     private  static $init = false;
  15.     public function __construct() {
  16.         if (!self::$init) {
  17.             parent::__construct();
  18.             $selectSql = ‘select * from score where id = ?’;
  19.             $insertSql = ‘insert into score (sid, course_name, score) values (?, ?, ?)’;
  20.             $updateSql = ‘update score set sid = ?, course_name = ?, score = ? where id = ?’;
  21.             $deleteSql = ‘delete from score where id = ?’;
  22.             // 预编译生成prepareStatement对象
  23.             self::$selectStmt = self::$PDO->prepare($selectSql);
  24.             self::$insertStmt = self::$PDO->prepare($insertSql);
  25.             self::$updateStmt = self::$PDO->prepare($updateSql);
  26.             self::$deleteStmt = self::$PDO->prepare($deleteSql);
  27.             self::$init = true;
  28.         }
  29.     }
  30.     public function update(DomainObject $obj) {
  31.         // 类型安全检查
  32.         // if (!($obj instanceof Score)) {
  33.         //    throw new AppException(‘Object is not instance of Student’);
  34.         // }
  35.         $data = array($obj->getStudent()->getId()
  36.                 , $obj->getCourseName(), $obj->getScore(), $obj->getId());
  37.         $flag = self::$updateStmt->execute($data);
  38.         return $flag;
  39.     }
  40.     protected function doInsert(DomainObject $obj) {
  41.         $data = array($obj->getStudent()->getId() , $obj->getCourseName(), $obj->getScore());
  42.         $flag = self::$insertStmt->execute($data);
  43.         // 数据行返回设置对象
  44.         if ($flag) {
  45.             $lastId = self::$PDO->lastInsertId();
  46.             $obj->setId($lastId);
  47.         }
  48.         return $flag;
  49.     }
  50.     protected  function doCreateObject(array $data) {
  51.         $obj = new Score($data[‘id’]);
  52.         $obj->setScore($data[‘score’]);
  53.         $obj->setCourseName($data[‘course_name’]);
  54.         // setStudent()
  55.         $stuMapper = new StudentMapper();
  56.         $stuObj = $stuMapper->findById($data[‘sid’]);
  57.         $obj->setStudent($stuObj);
  58.         return $obj;
  59.     }
  60.     protected  function getSelectStmt() {
  61.         return self::$selectStmt;
  62.     }
  63.     protected function getDeleteStmt() {
  64.         return self::$deleteStmt;
  65.     }
  66. }

使用的例子:

[php][/php] view plaincopy

  1. $score = new Score(0);
  2. $scoreMapper = new ScoreMapper();
  3. $score->setCourseName(‘Math’);
  4. // 插入
  5. $scoreMapper->insert($score);
  6. // 查找
  7. $score = $scoreMapper->findById($score->getId());
  8. var_dump($score);
  9. $score->setCourseName(‘English’);
  10. // 更新
  11. $scoreMapper->update($score);
  12. // 删除
  13. $scoreMapper->deleteById($score->getId());

数据映射器的好处是消除了领域层和数据库操作之间的耦合,Mapper可以应用各种对象关系映射。比如insert、update的传递的参数是DomainObject对象,保存到数据库的是数据行;findById把数据库的数据行转换成DomainObject对象。 而它的缺点是需要创建大量的具体的映射器类,不过大部分都是相似的代码,也可以通过反射机制来生成这些相似的代码。

 

findById是获取一条数据,而findAll是获取一个数据集,那么需要一个什么对象来保持和数据集的映射才比较好呢?接下来介绍Collection对象。

标签