[php]领域模型和数据映射器

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

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

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

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

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

        sql脚本:

CREATE TABLE `classroom` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  PRIMARY KEY (`id`)
) 

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `cid` int(11) NOT NULL,
  `name` varchar(16) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `cs_id` (`cid`),
  CONSTRAINT `cs_id` FOREIGN KEY (`cid`) REFERENCES `classroom` (`id`)
) 

CREATE TABLE `score` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sid` int(11) NOT NULL,
  `course_name` varchar(32) NOT NULL,
  `score` tinyint(4) NOT NULL,
  PRIMARY KEY (`id`,`sid`,`course_name`),
  KEY `sc_id` (`sid`),
  CONSTRAINT `sc_id` FOREIGN KEY (`sid`) REFERENCES `student` (`id`)
) 

        

 

        领域模型抽象基类:DomainObject

namespace demo\domain;

/**
 * 领域模型抽象基类
 */
abstract class DomainObject {
	protected  $id;
	
	public function __construct($id = null) {
		if (is_null($id)) {
			$id = -1;
		} else {
			$this->id = $id;
		}
	}
	
	public function getId() {
		return $this->id;
	}
	
	public function setId($id) {
		$this->id = $id;
	}
	
	// 现在比较简单,之后还会扩展
	// ......
}


         Score类(对应Score表):

namespace demo\domain;

use demo\domain\Student;

/**
 * Score 
 * 对应表score
 */
class Score extends DomainObject {
	private $score;
	private $courseName;
	// Student对象引用
	private $student;
	
	public function __construct($id = null, $score = 0, $courseName = 'unknown') {
		parent::__construct($id);
		$this->score = $score;
		$this->courseName = $courseName;
	}
	
	public function getScore() {
		return $this->score;
	}
	
	public function setScore($score) {
		$this->score = $score;
	}
	
	public function setCourseName($courseName) {
		$this->courseName = $courseName;
	}
	
	public function getCourseName() {
		return $this->courseName;
	}
	
	public function getStudent() {
		return $this->student;
	}
	
	public function setStudent(Student $student) {
		$this->student = $student;
	}
}


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

 

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

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

        来看看Mapper的类层次图吧:

        

        Mapper抽象基类:

namespace demo\mapper;

use demo\base\AppException;
use \demo\base\ApplicationRegistry;

/**
 * Mapper
 */
abstract  class Mapper {
	// PDO
	protected static $PDO;
	// config
	protected static  $dsn, $dbUserName, $dbPassword;
	// PDO选项
	protected static $options = array(
    	\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
    	\PDO::ATTR_ERRMODE,
    	\PDO::ERRMODE_EXCEPTION,
	); 
	
	public function __construct() {
		if (!isset(self::$PDO)) {
			// ApplicationRegistry获取数据库连接信息
			$appRegistry = ApplicationRegistry::getInstance();
			self::$dsn = $appRegistry->getDsn();
			self::$dbUserName = $appRegistry->getDbUserName();
			self::$dbPassword = $appRegistry->getDbPassword();
			
			if (!self::$dsn || !self::$dbUserName || !self::$dbPassword) {
				throw  new AppException('Mapper init failed!');
			}
			
			self::$PDO = new \PDO(self::$dsn, self::$dbUserName, self::$dbPassword, self::$options);
		}
	}
	
	/**
	 * 查找指定ID
	 * @param int $id
	 */
	public function findById($id) {
		$pStmt = $this->getSelectStmt();		
		$pStmt->execute(array($id));	
		$data = $pStmt->fetch();		
		$pStmt->closeCursor();		
		
		$obj = null;
		if (!is_array($data) || !isset($data['id'])) {
			return $obj;
		}
		
		$obj = $this->createObject($data);
		return $obj;
	}
	
	/**
	 * 插入数据
	 * @param \demo\domain\DomainObject $obj
	 */
	public function insert(\demo\domain\DomainObject $obj) {
		return $this->doInsert($obj);
	}
	
	/**
	 * 删除指定ID
	 * @param int $id
	 */
	public function deleteById($id) {
		$pStmt = $this->getDeleteStmt();
		$flag = $pStmt->execute(array($id));
		
		return $flag;
	}
	
	/**
	 * 生成一个$data中值属性的对象
	 * @param array $data
	 */
	public function createObject(array $data) {
		$obj = $this->doCreateObject($data);
		return $obj;
	}
	
	public abstract function update(\demo\domain\DomainObject $obj);
	
	protected abstract function doInsert(\demo\domain\DomainObject $obj);
	
	protected abstract function doCreateObject(array $data);
	
	protected abstract function getSelectStmt();
	
	protected abstract function getDeleteStmt();
}


        一个具体Mapper子类ScoreMapper:

namespace demo\mapper;

use demo\base\AppException;
use \demo\domain\DomainObject;
use \demo\domain\Score;
use \demo\mapper\StuStudentMapper;

/**
 * ScoreMapper
 */
class ScoreMapper  extends Mapper {
	private  static $selectStmt;
	private  static $insertStmt;
	private  static $updateStmt;
	private  static $deleteStmt;
	private  static $init = false;
	
	public function __construct() {
		if (!self::$init) {
			parent::__construct();
		
			$selectSql = 'select * from score where id = ?';
			$insertSql = 'insert into score (sid, course_name, score) values (?, ?, ?)';
			$updateSql = 'update score set sid = ?, course_name = ?, score = ? where id = ?';
			$deleteSql = 'delete from score where id = ?';
			
			// 预编译生成prepareStatement对象
			self::$selectStmt = self::$PDO->prepare($selectSql);
			self::$insertStmt = self::$PDO->prepare($insertSql);
			self::$updateStmt = self::$PDO->prepare($updateSql);
			self::$deleteStmt = self::$PDO->prepare($deleteSql);
			self::$init = true;
		}
	}
	
	public function update(DomainObject $obj) {
		// 类型安全检查
		// if (!($obj instanceof Score)) {
		//	  throw new AppException('Object is not instance of Student');	
		// }
		
		$data = array($obj->getStudent()->getId()
				, $obj->getCourseName(), $obj->getScore(), $obj->getId());
		$flag = self::$updateStmt->execute($data);
		return $flag;
	}	
	
	protected function doInsert(DomainObject $obj) {
		$data = array($obj->getStudent()->getId() , $obj->getCourseName(), $obj->getScore());
		$flag = self::$insertStmt->execute($data);
			
		// 数据行返回设置对象
		if ($flag) {
			$lastId = self::$PDO->lastInsertId();
			$obj->setId($lastId);
		}
		
		return $flag;
	}

	protected  function doCreateObject(array $data) {
		$obj = new Score($data['id']);
		$obj->setScore($data['score']);
		$obj->setCourseName($data['course_name']);
		// setStudent()
		$stuMapper = new StudentMapper();
		$stuObj = $stuMapper->findById($data['sid']);
		$obj->setStudent($stuObj);
		
		return $obj;
	}
	
	protected  function getSelectStmt() {
		return self::$selectStmt;
	}
	
	protected function getDeleteStmt() {
		return self::$deleteStmt;
	}
}


        使用的例子:

$score = new Score(0);
$scoreMapper = new ScoreMapper();
$score->setCourseName('Math');
// 插入
$scoreMapper->insert($score);
// 查找
$score = $scoreMapper->findById($score->getId());
var_dump($score);
$score->setCourseName('English');
// 更新
$scoreMapper->update($score);
// 删除
$scoreMapper->deleteById($score->getId());


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

 

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

 

posted on 2013-10-16 12:00  云编程的梦  阅读(355)  评论(0编辑  收藏  举报

导航