私は ZendFramework 1.1 を利用したアプリケーションを 2 年間にわたって開発してきましたが、新しいことを学んだり試したりするために、リファクタリングのいくつかの異なる段階を見てきました。現在の状態では、私の構造は、物事を迅速に完了できるという点でかなり優れていると感じていますが、肥大化してぎこちない依存関係がたくさんあると感じている特定の領域でいくつかの改善を使用できることは確かです.
私のアプリケーションからサンプルコードをいくつか書き出すので、ここで我慢してください。保存する必要があるインスタンスをOrder
持つオブジェクトの例を使用します。OrderItem
インスタンス化と保存に必要なすべての部分を説明します。
私の理解が及ぶ限り、ここで行っていることは、 Domain ModelsよりもActiveRecord設計パターンに沿っていますが、両方の慣行があると思います...
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;
}
}
私が持っているものは必ずしもひどいものではないように感じますが、ここにはあまり良くないデザインがいくつかあるようにも感じます. 私の懸念は主に次のとおりです。
モデルは、データベース テーブル スキーマに密接に結合された非常に厳格な構造を持っているため、モデルまたはデータベース テーブルからプロパティを追加/削除するのは非常に面倒です! $table
データベースに保存するすべてのオブジェクトを$mapper
コンストラクターで指定するのは悪い考えだと思います...どうすればこれを回避できますか? を定義しないようにするにはどうすればよい$schema
ですか?
検証は、データベースの列名にも対応するモデルのプロパティ名に非常に密接に結び付けられているため、少し奇妙に思えます。これにより、データベースやモデルの変更がさらに複雑になります。検証のためのより適切な場所はありますか?
DataMappersは、いくつかの複雑な検索機能を提供する以外に、実際にはあまり機能しません。複雑なオブジェクトの保存は、オブジェクト クラス自体によって完全に処理されます (たとえば、Order
私の例ではクラスです。「複雑なオブジェクト」以外に、このタイプのオブジェクトを表す適切な用語はありますかOrder
?OrderItem
また、保存する必要があるオブジェクトDataMapper は、現在Order
クラスに存在する保存ロジックを処理する必要がありますか?
お時間とご意見をお寄せいただきありがとうございます。