我正在开发一个用于报告数据的intranet站点。我为Customer、Order、Item、Invoice等开发了各种类,它们都以某种方式相互关联。每个类构造函数查询一个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;
}
}
我可能只在摘要行中使用一些对象属性,但当我深入查看订单并更详细地查看订单时,order类已经准备好了我需要的所有属性,非常整洁。
通常处理这种情况的最佳方式是什么?对于我的摘要查询,我是否应该尽量避免实例化类,并在一个独立于类的查询中获取所有内容?我担心这会导致我每次进行结果集查询时都必须手动在类中做很多后台工作。我宁愿在一个地方进行更改,并将其反映在我正在查询受影响的类/属性的所有页面上。
还有什么其他适当的处理方法?
这个问题非常开放,所以我要给出一个相当宽泛的答案,尽量不要太忘乎所以。我首先要说的是,像Doctrine、Propel或Symfony这样的ORM解决方案可能是管理关系对象的最理想解决方案,但要快速或干净地实现并不总是实用的(学习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的简单性,并且只在需要时进行优化,那么它可以非常好地工作。希望这能有所帮助。