Mapper类中的findById($id)可以从数据库中取出指定id的一条数据,映射成一个对象返回。很多时候我们需要返回一个数据集合(findAll),那我们就需要一种数据结构来保存这些数据,在需要时映射成对象。既然一条数据映射成一个对象,那么一个数据集合就需要一个对象集合。可以把数据集合和对象集合放在一个类中,这样就方便处理数据到对象的映射了。我们把这个类命名为Collection,为了能更好好的访问集合对象,Collection子类都实现了Iterator接口,使用foreach可以方便访问。
Collection的类结构:
\demo\mapper\Collection:
namespace demo\mapper; use demo\base\AppException; use demo\domain\DomainObject; use demo\mapper\Mapper; abstract class Collection { // 保存数据库取出的行数据 protected $raws; // 保存已映射的对象 protected $objects = array(); // 用于$raws[]到$objects的映射 protected $mapper; // 当前指针 private $pointer = 0; // 对象数组总数 private $total = 0; /** * @param array $raws 未处理过的数据库数据 * @param Mapper $mapper 用于把$raws映射成对象(createObject) */ public function __construct(array $raws = null, Mapper $mapper = null) { if (!is_null($raws) && !is_null($mapper)) { $this->raws = $raws; $this->total = count($raws); } $this->mapper = $mapper; } /** * 返回指定$num的数据对象 * @param int $num */ public function getRow($num) { if ($num < 0 || $num >= $this->total) { return null; } // 延迟加载 $this->notifyAccess(); if (isset($this->objects[$num])) { return $this->objects[$num]; } if (isset($this->raws[$num])) { $obj = $this->mapper->createObject($this->raws[$num]); $this->objects[$num] = $obj; return $obj; } return null; } /** * 添加对象 * @param DomainObject $obj * @throws AppException */ public function add(DomainObject $obj) { // 类型安全检查 $targetClass = $this->getTargetClass(); if (!($obj instanceof $targetClass)) { throw new AppException("Object must be {$targetClass}"); } // $this->notifyAccess(); $this->objects[$this->pointer++] = $obj; } public function current() { return $this->getRow($this->pointer); } public function next() { $obj = $this->getRow($this->pointer); if (!is_null($obj)) { $this->pointer++; } return $obj; } public function key() { return $this->pointer; } public function rewind() { $this->pointer = 0; } public function valid() { return !is_null($this->current()); } /** * 延迟加载 */ protected function notifyAccess() { // 暂时留空 } protected abstract function getTargetClass(); }
\demo\domain:
namespace demo\domain; use \demo\domain\DomainObject; interface ClassroomCollection extends \Iterator { public function add(DomainObject $obj); } interface StudentCollection extends \Iterator { public function add(DomainObject $obj); } interface ScoreCollection extends \Iterator { public function add(DomainObject $obj); }\demo\mapper:
namespace demo\mapper; class ClassroomCollection extends Collection implements \demo\domain\ClassroomCollection { protected function getTargetClass() { return '\demo\domain\Classroom'; } } class StudentCollection extends Collection implements \demo\domain\StudentCollection { protected function getTargetClass() { return '\demo\domain\Student'; } } class ScoreCollection extends Collection implements \demo\domain\ScoreCollection { protected function getTargetClass() { return '\demo\domain\Score'; } }为什么需要为domain包还需要一个Collection接口呢?因为domain包需要用到Collection来保存数据,为了让domain包不依赖于mapper包的Collection,所以创建了一个接口。而\demo\domain\mapper\Collection则会实现这个接口。
现在的结构开始有点复杂了,为了能管理好Mapper和Collection的具体子类,我们可以使用抽象工厂来管理对象的创建。来看看类图:
\demo\mapper\PersistanceFatory
namespace demo\mapper; /** * 持久化工厂 */ abstract class PersistanceFactory { public static function getFactory($targetClass) { switch ($targetClass) { case '\demo\domain\Classroom': return new ClassroomPersistanceFactory(); case '\demo\domain\Student': return new StudentPersistanceFactory(); case '\demo\domain\Score': return new ScorePersistanceFactory(); } } public abstract function getMapper(); public abstract function getCollection(array $raws = null); } class ClassroomPersistanceFactory extends PersistanceFactory { public function getMapper() { return new ClassroomMapper(); } public function getCollection(array $raws = null) { return new ClassroomCollection($raws, $this->getMapper()); } } class StudentPersistanceFactory extends PersistanceFactory { public function getMapper() { return new StudentMapper(); } public function getCollection(array $raws = null) { return new StudentCollection($raws, $this->getMapper()); } } class ScorePersistanceFactory extends PersistanceFactory { public function getMapper() { return new ScoreMapper(); } public function getCollection(array $raws = null) { return new ScoreCollection($raws, $this->getMapper()); } }使用这样的工厂模式可以很方便地创建指定的Mapper和Collection子类了,同时这种方式也可以方便以后新功能的添加。
domain包中同样需要Collection对象,但需要注意和mapper中的Collection分离开来。我们可以在domain包中创一个HelperFactory类来当做domain访问mapper的桥梁。
namespace demo\domain; use demo\mapper\PersistanceFactory; class HelperFactory { public static function getCollection($targetClass) { $fact = PersistanceFactory::getFactory($targetClass); return $fact->getCollection(); } public static function getFinder($targetClass) { $fact = PersistanceFactory::getFactory($targetClass); return $fact->getMapper(); } }
这样就把domain包和mapper包分离开来了。
Collection有了,那么就来实现Mapper的findAll()吧。
namespace demo\mapper; use demo\base\AppException; use \demo\base\ApplicationRegistry; /** * Mapper */ abstract class Mapper { //... /** * 返回Collection */ public function findAll() { $pStmt = $this->getSelectAllStmt(); $pStmt->execute(array()); $raws = $pStmt->fetchAll(\PDO::FETCH_ASSOC); $collection = $this->getCollection($raws); return $collection; } /** * 返回子类Collection * @param array $raw */ public function getCollection(array $raws) { return $this->getFactory()->getCollection($raws); } /** * 返回子类持久化工厂对象 */ public function getFactory() { return PersistanceFactory::getFactory($this->getTargetClass()); } //.... }
例子:
$fact = PersistanceFactory::getFactory('\demo\domain\Classroom'); $mapper = $fact->getMapper(); $classrooms = $mapper->findAll(); foreach ($classrooms as $elem) { var_dump($elem); }
Colletion能方便管理$raws[]到$objects[]的映射。
PersistanceFactory能管理好mapper包中类对象的创建。
HelperFactory把mapper包和domain包分离开来。