我正在使用Zend Framework并实现域模型。我有模型,Mappers和DbTables。
假设我们应该从数据库中获取多行或获取表单数据,我们应该创建多个模型并从数据库行或表单中填充这些模型。
我应该在Mapper中实现获取和创建模型,然后从Controller调用该方法吗?或者我应该在模型中实现它?
可以在Controller中初始化Mapper吗?
答案 0 :(得分:1)
简短回答是YES!
如果您需要从数据库中获取任何内容,您几乎可以在某处使用映射器,因为理想情况下,域模型应该根本不了解数据库,或者确实存在映射器事件。
我确信有许多使用和访问域模型和映射器的策略和模式。我也相信每个人都会对如何最好地利用这些资源有不同的看法。最重要的是你必须使用你知道如何使用的东西,你可以在以后知道更多时重构。
仅作为示例,我将包括基本映射器和基本实体(域)模型。
<?php
/**
* Base mapper model to build concrete data mappers around.
* Includes identity map functionallity.
*/
abstract class My_Application_Model_Mapper
{
protected $_tableGateway = NULL;
protected $_map = array();
/**
* Will accept a DbTable model passed or will instantiate
* a Zend_Db_Table_Abstract object from table name.
*
* @param Zend_Db_Table_Abstract $tableGateway
*/
public function __construct(Zend_Db_Table_Abstract $tableGateway = NULL) {
if (is_null($tableGateway)) {
$this->_tableGateway = new Zend_Db_Table($this->_tableName);
} else {
$this->_tableGateway = $tableGateway;
}
}
/**
* @return Zend_Db_Table_Abstract
*/
protected function _getGateway() {
return $this->_tableGateway;
}
/**
* @param string $id
* @param object $entity
*/
protected function _setMap($id, $entity) {
$this->_map[$id] = $entity;
}
/**
* @param string $id
* @return string
*/
protected function _getMap($id) {
if (array_key_exists($id, $this->_map)) {
return $this->_map[$id];
}
}
/**
* findByColumn() returns an array of rows selected
* by column name and column value.
* Optional orderBy value.
*
* @param string $column
* @param string $value
* @param string $order
* @return array
*/
public function findByColumn($column, $value, $order = NULL) {
$select = $this->_getGateway()->select();
$select->where("$column = ?", $value);
if (!is_null($order)) {
$select->order($order);
}
$result = $this->_getGateway()->fetchAll($select);
$entities = array();
foreach ($result as $row) {
$entity = $this->createEntity($row);
$this->_setMap($row->id, $entity);
$entities[] = $entity;
}
return $entities;
}
/**
* findById() is proxy for find() method and returns
* an entity object. Utilizes fetchRow() because it returns row object
* instead of primary key as find() does.
* @param string $id
* @return object
*/
public function findById($id) {
//return identity map entry if present
if ($this->_getMap($id)) {
return $this->_getMap($id);
}
$select = $this->_getGateway()->select();
$select->where('id = ?', $id);
//result set, fetchRow returns a single row object
$row = $this->_getGateway()->fetchRow($select);
//create object
$entity = $this->createEntity($row);
//assign object to odentity map
$this->_setMap($row->id, $entity);
return $entity;
}
/**
* findAll() is a proxy for the fetchAll() method and returns
* an array of entity objects.
* Optional Order parameter. Pass order as string ie. 'id ASC'
* @param string $order
* @return array
*/
public function findAll($order = NULL) {
$select = $this->_getGateway()->select();
if (!is_null($order)) {
$select->order($order);
}
$rowset = $this->_getGateway()->fetchAll($select);
$entities = array();
foreach ($rowset as $row) {
$entity = $this->createEntity($row);
$this->_setMap($row->id, $entity);
$entities[] = $entity;
}
return $entities;
}
/**
* Abstract method to be implemented by concrete mappers.
*/
abstract protected function createEntity($row);
}
这是基本域对象:
<?php
/**
* Base domain object
* includes lazy loading of foreign key objects.
*/
abstract class My_Application_Model_Entity_Abstract
{
protected $_references = array();
/**
* Accepts an array to instantiate the object, else use
* __set() when creating objects
* @param array $options
*/
public function __construct(array $options = NULL) {
if (is_array($options)) {
$this->setOptions($options);
}
}
/**
* @param array $options
* @return \My_Application_Model_Entity_Abstract
*/
public function setOptions(array $options) {
$methods = get_class_methods($this);
foreach ($options as $key => $value) {
$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
}
}
return $this;
}
/**
* Map the setting of non-existing fields to a mutator when
* possible, otherwise use the matching field
*/
public function __set($name, $value) {
$property = '_' . strtolower($name);
if (!property_exists($this, $property)) {
throw new \InvalidArgumentException("Setting the property '$property'
is not valid for this entity");
}
$mutator = 'set' . ucfirst(strtolower($name));
if (method_exists($this, $mutator) && is_callable(array($this, $mutator))) {
$this->$mutator($value);
} else {
$this->$property = $value;
}
return $this;
}
/**
* Map the getting of non-existing properties to an accessor when
* possible, otherwise use the matching field
*/
public function __get($name) {
$property = '_' . strtolower($name);
if (!property_exists($this, $property)) {
throw new \InvalidArgumentException(
"Getting the property '$property' is not valid for this entity");
}
$accessor = 'get' . ucfirst(strtolower($name));
return (method_exists($this, $accessor) && is_callable(array(
$this, $accessor))) ? $this->$accessor() : $this->$property;
}
/**
* Get the entity fields.
*/
public function toArray() {
//TODO
}
/**
* set and get for _references array, allows the potential to lazy load
* foreign objects.
*/
public function setReferenceId($name, $id) {
$this->_references[$name] = $id;
}
public function getReferenceId($name) {
if (isset($this->_references[$name])) {
return $this->_references[$name];
}
}
}
我参考了许多教程和书籍,最终了解了这些概念和技巧
如果您需要从调用映射器的DB中提取对象,则需要使用这些对象,如果需要使用表单数据(或其他一些数据)构建对象,则可以直接调用该对象。
我希望这会有所帮助。祝你好运!
答案 1 :(得分:1)
我在所有ZF工作中使用相同的架构和设计模式。您的映射器应该是整个系统中访问数据库的唯一类。这确保了良好的Separation of Concerns。
我在模型中使用薄包装方法玩弄了一些,例如:
class Application_Model_Foo {
public function save() {
$mapper = $this->_getMapper();
$mapper->save($this);
}
}
这使我可以调用类似的东西:
$foo = new Application_Model_Foo();
$foo->setBar('baz-bum');
$foo->save();
但是这使得测试变得更加复杂,并且当涉及到SoC时,水会变得混乱。最近我一直在从代码中删除这些事件,转而直接调用mapper,如:
$foo = new Application_Model_Foo();
$foo->setBar('baz-bum');
$mapper = new Application_Model_FooMapper();
$mapper->save($foo);
后一个例子意味着我的控制器方法中还有一行代码,但为了简化测试,我认为这是值得的。