2013-08-21 65 views
1

我正在開發一個用於報告數據的Intranet站點。我爲客戶,訂單,物品,發票等開發了各種各樣的類,這些類都以某種方式彼此相關。每個類構造函數查詢MySQL數據庫(多個查詢)並相應地填充屬性。實例化結果集中的對象...最佳實踐?

目前,爲了保持代碼的方便性和一致性,我依賴於在報告中實例化類。問題是,每個類構造函數可能會在其中包含一堆MySQL查詢,從各種來源提取或計算相關屬性。由於循環中的所有查詢都會導致性能下降。這是可用的,我不會一次有大量的用戶,但它可能會更快。

例如,假設我列出了來自客戶的最後50個訂單。我現在這樣做的方式,我通常會編寫一個簡單的查詢,該查詢僅爲該客戶返回50個訂單ID。然後在循環結果的同時,我會爲每個結果實例化一個新的順序。有時我甚至可能會深入一層,然後爲訂單中的每個項目實例化一個新對象。

一些僞給予的想法....

$result = mysql_query("SELECT DISTINCT id FROM orders WHERE customer = 1234 LIMIT 50"); 
while ($row = mysql_fetch_array($result, MYSQL_NUM)) { 
    $order = new Order($row['id']); // more MySQL queries happen in constructor 
    // All of my properties are pre-calculated and formatted 
    // properly within the class, preventing me from having to redo it manually 
    // any time I perform queries on orders 
    echo $order->number; 
    echo $order->date; 
    echo $order->total; 
    foreach($order->items as $oitem) { 
     $item = new Item($oitem); // even more MySQL queries happen here 
     echo $item->number; 
     echo $item->description; 
    } 
} 

我可以只使用幾個對象屬性的摘要行,但是當我深入和更詳細地查看訂單時,訂單類具有我需要準備好的所有屬性,非常整齊。

這是通常處理的最佳方式是什麼?對於我的彙總查詢,我是否應該儘量避免實例化類並將所有內容都放在一個與類不同的查詢中?我擔心這會導致我不得不手動進行很多後臺工作,而這些工作通常是在我每次執行結果集查詢時在類中做的。我寧願在一個地方做出改變,讓它反映在我查詢受影響的類/屬性的所有頁面上。

什麼是其他方法來適當地處理這個問題?

回答

1

這個問題是相當開放的,所以我會給出一個相當廣泛的答案,並儘量不要太過分。我會首先說ORM解決方案像Doctrine's,Propel's或Symfony's可能是管理關係對象最理想的方法,但對於快速或乾淨地實現並不總是切實可行的(可能需要一段時間才能學習ORM,然後轉換現有的代碼)。這是我採取更輕量化的方法。

首先,它可能有助於將數據庫查詢從類構造函數中提取出來,以便您可以更好地控制訪問數據庫的時間。一種策略是將靜態方法添加到您的類中以獲取結果。另外,您可以提供選項來「預取」子對象,以便可以批量執行查詢。因此,要進入你的例子中,外部API會是這個樣子:

$orders = Order::getOrders(array(
    'items' => true 
)); 

這裏的想法是要拿到訂單的陣列與getOrders()方法,並告訴getOrders()獲取item子對象的同時。從Order課程以外,這非常簡單:只需傳入一個'items'密鑰設置爲true即可。然後Order類中:

class Order 
{ 

    public $items = null; 

    public static function getOrders(array $options = array()) 
    { 
     $orders = array(); 

     $result = mysql_query("SELECT DISTINCT id FROM orders WHERE customer = 1234 LIMIT 50"); 
     while ($row = mysql_fetch_array($result, MYSQL_NUM)) { 
      $order = new Order($row); 
      $orders[$order->id] = $order; 
     } 

     // if we're asked to fetch items, fetch them in bulk 
     if (isset($options['items']) && $options['items'] === true) { 
      $this->items = array(); 
      $items = Item::getItemsForOrders($orders); 
      foreach ($items as $item) { 
       $orders[$item->orderId]->items[] = $items; 
      } 
     } 

     return $orders 
    } 

    public function __construct(array $data = array()) 
    { 
     // set up your object using the provided data 
     // rather than fetching from the database 
     // ... 
    } 

    public function getItems() 
    { 
     if ($this->items === null) { 
      $this->items = Item::getItemsForOrders(array($this)) 
     } 

     return $items; 
    } 
} 

而在你Item類:現在

class Item 
{ 
    public $orderId = null; 

    public static function getItemsForOrders(array $orders, array $options = array()) 
    { 
     // perform query for ALL orders at once using 
     // an IN statement, returning an array of Item objects 
     // ... 
    } 
} 

,如果你知道你需要的物品,當你得到你的訂單,通過在true'items'選項:

$orders = Order::getOrders(array(
    'items' => true 
)); 

或者,如果你不需要的東西,不指定任何:

$orders = Order::getOrders(); 

不管怎樣,當你通過你的命令循環,該API是訪問項目相同:

// the following will perform only 2 database queries 
$orders = Order::getOrders(array(
    'items' => true 
)); 
foreach ($orders as $order) { 
    $items = $order->getItems(); 
} 

// the following will perform 1 query for orders 
// plus 1 query for every order 
$orders = Order::getOrders(); 
foreach ($orders as $order) { 
    $items = $order->getItems(); 
} 

正如你所看到的,提供'items'選項可以導致更有效地使用數據庫,但如果你只需要orders而不是在items左右,你也可以這樣做。

而且因爲我們要getOrders()提供一組選項中,我們可以很容易地擴展我們的功能,包括標記爲其他子對象(或其他任何東西,應該是「可選」):

$orders = Order::getOrders(array(
    'items' => true, 
    'tags' => true, 
    'widgets' => true, 
    'limit' => 50, 
    'page' => 1 
)); 

...你如果需要,可以代理到子對象的選擇:

// ... 
// in the `Order::getOrders()` method, when getting items... 
$items = Item::getItemsForOrders($orders, array(
    'tags' => (isset($options['tags']) && $options['tags'] === true) 
)); 

如果你不是明智的什麼應該或不應該是可選的獲取對象時,這種方法可以變得臃腫和艱鉅的維護,但如果你保持你的API簡單,只在需要時進行優化,它可以非常好地工作。希望這可以幫助。

+0

這正是我所期待的!我已經開始按照您的建議重新組織我的課程,並且我能夠使用您的概念將更大的靈活性與更快的結果結合起來。使用你在這裏提出的建議,我不僅傳遞了選項,比如是否要加載項目,還傳遞了其他選項以各種方式過濾主要getOrders()查詢。幾乎正是我所期待的。非常感謝! – user2702162

+0

很高興能幫到你! – Divey