两年来的大部分时间里,我一直在使用 ZendFramework 1.1 开发一个应用程序,因此从我学习或尝试新事物开始,它已经看到了几个不同的重构阶段。在目前的状态下,我觉得我的结构非常好,因为我可以快速完成工作,但肯定可以在某些领域进行一些改进——我觉得有很多臃肿和尴尬的依赖关系。
当我从我的应用程序中放下一些示例代码时,请耐心等待。我将使用一个对象的示例,该Order
对象具有OrderItem
也必须保存的实例。我将解释实例化和保存的所有必要部分。
就我的理解而言,我在这里所做的更符合ActiveRecord设计模式而不是Domain Models,尽管我认为我在这两个方面都有实践......
class Order extends BaseObject {
/** @var OrderItem array of items on the order */
public $items = array();
public function __construct($data = array()){
// Define the attributes for this model
$schema = array(
"id" => "int", // primary key
"order_number" => "string", // user defined
"order_total" => "float", // computed
// etc...
);
// Get datamapper and validator classes
$mf = MapperFactory::getInstance();
$mapper = $mf->get("Order");
$validator = new Order_Validator();
$table = new Application_DbTable_Order();
// Construct parent
parent::__construct($schema, $mapper, $validator, $table);
// If data was provided then parse it
if(count($data)){
$this->parseData($data);
}
// return the instance
return $this;
}
// Runs before a new instance is saved, does some checks
public function addPrehook(){
$orderNumber = $this->getOrderNumber();
if($this->mapper->lookupByOrderNumber($orderNumber)){
// This order number already exists!
$this->addError("An order with the number $orderNumber already exists!");
return false;
}
// all good!
return true;
}
// Runs after the primary data is saved, saves any other associated objects e.g., items
public function addPosthook(){
// save order items
if($this->commitItems() === false){
return false;
}
// all good!
return true;
}
// saves items on the order
private function commitItems($editing = false){
if($editing === true){
// delete any items that have been removed from the order
$existingOrder = Order::getById($this->getId());
$this->deleteRemovedItems($existingOrder);
}
// Iterate over items
foreach($this->items as $idx => $orderItem){
// Ensure the item's order_id is set!
$orderItem->setOrderId($this->getId());
// save the order item
$saved = $orderItem->save();
if($saved === false){
// add errors from the order item to this instance
$this->addError($orderItem->getErrors());
// return false
return false;
}
// update the order item on this instance
$this->items[$idx] = $saved;
}
// done saving items!
return true;
}
/** @return Order|boolean The order matching provided ID or FALSE if not found */
public static function getById($id){
// Get the Order Datamapper
$mf = MapperFactory::getInstance();
$mapper = $mf->get("Order");
// Look for the primary key in the order table
if($mapper->lookup($id)){
return new self($mapper->fetchObjectData($id)->toArray());
}else{
// no order exists with this id
return false;
}
}
}
BaseObject 中存在数据的解析、保存以及几乎所有适用于所有模型(更合适的术语可能是实体)的任何其他内容,如下所示:
class BaseObject {
/** @var array Array of parsed data */
public $data;
public $schema; // valid properties names and types
public $mapper; // datamapper instance
public $validator; // validator instance
public $table; // table gateway instance
public function __construct($schema, $mapper, $validator, $table){
// raise an error if any of the properties of this method are missing
$this->schema = $schema;
$this->mapper = $mapper;
$this->validator = $validator;
$this->table = $table;
}
// parses and validates $data to the instance
public function parseData($data){
foreach($data as $key => $value){
// If this property isn't in schema then skip it
if(!array_key_exists($key, $this->schema)){
continue;
}
// Get the data type of this
switch($this->schema[$key]){
case "int": $setValue = (int)$value; break;
case "string": $setValue = (string)$value; break;
// etc...
default: throw new InvalidException("Invalid data type provided ...");
}
// Does our validator have a handler for this property?
if($this->validator->hasProperty($key) && !$this->validator->isValid($key, $setValue)){
$this->addError($this->validator->getErrors());
return false;
}
// Finally, set property on model
$this->data[$key] = $setValue;
}
}
/**
* Save the instance - Inserts or Updates based on presence of ID
* @return BaseObject|boolean The saved object or FALSE if save fails
*/
public function save(){
// Are we editing an existing instance, or adding a new one?
$action = ($this->getId()) ? "edit" : "add";
$prehook = $action . "Prehook";
$posthook = $action . "Posthook";
// Execute prehook if its there
if(is_callable(array($this, $prehook), true) && $this->$prehook() === FALSE){
// some failure occured and errors are already on the object
return false;
}
// do the actual save
try{
// mapper returns a saved instance with ID if creating
$saved = $this->mapper->save($this);
}catch(Exception $e){
// error occured saving
$this->addError($e->getMessage());
return false;
}
// run the posthook if necessary
if(is_callable(array($this, $posthook), true) && $this->$posthook() === FALSE){
// some failure occured and errors are already on the object
return false;
}
// Save complete!
return $saved;
}
}
基类对,和DataMapper
有非常简单的实现,因为是按对象定义的,所以它们永远不会重载。我觉得这有点不稳定,但我猜它有效吗?子类本质上只是提供特定于域的查找器功能,例如,或其他类似的东西。save
insert
update
$schema
BaseMapper
lookupOrderByNumber
findUsersWithLastName
class BaseMapper {
public function save(BaseObject $obj){
if($obj->getId()){
return $this->update($obj);
}else{
return $this->insert($obj);
}
}
private function insert(BaseObject $obj){
// Get the table where the object should be saved
$table = $obj->getTable();
// Get data to save
$saveData = $obj->getData();
// Do the insert
$table->insert($saveData);
// Set the object's ID
$obj->setId($table->getAdapter()->getLastInsertId());
// Return the object
return $obj;
}
}
我觉得我所拥有的不一定是可怕的,但我也觉得这里有一些不太好的设计。我的担心主要是:
模型有一个非常严格的结构,它与数据库表模式紧密耦合,使得从模型或数据库表中添加/删除属性完全是一件麻烦事!我觉得给我所有保存到数据库 a$table
和$mapper
构造函数中的对象是一个坏主意......我怎样才能避免这种情况?我能做些什么来避免定义$schema
?
验证似乎有点古怪,因为它与模型上的属性名称紧密相关,模型上的属性名称也对应于数据库中的列名称。这使进行任何数据库或模型更改变得更加复杂!有没有更适合验证的地方?
DataMappers除了提供一些复杂的查找器功能外,并没有做太多的事情。保存复杂对象完全由对象类本身处理(例如,Order
我的示例中的类。除了“复杂对象”之外,这种类型的对象还有适当的术语吗?我说我的Order
对象是“复杂的”,因为它有OrderItem
它还必须保存的对象。DataMapper 是否应该处理当前存在于Order
类中的保存逻辑?
非常感谢您的时间和投入!