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;
    }
}

我可能只在摘要行中使用了一些对象属性,但是当我深入查看订单并更详细地查看订单时,Order 类具有我需要准备好的所有属性,既美观又整洁。

通常处理这种情况的最佳方式是什么?对于我的摘要查询,我是否应该尽量避免实例化类并在一个与类分开的查询中获取所有内容?我担心这会导致我每次执行结果集查询时都必须手动执行我通常在课堂上执行的大量后台工作。我宁愿在一个地方进行更改,并让它反映在我正在查询受影响的类/属性的所有页面上。

还有什么其他方法可以适当地处理这个问题?

4

1 回答 1

1

这个问题非常开放,所以我将给出一个相当广泛的答案,并尽量不要太过分。首先,我会说像 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
        // ...
    }
}

现在,如果您知道在收到订单时需要物品,请传入 atrue选项'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 简单并且只在需要时进行优化,它就可以很好地工作。希望这可以帮助。

于 2013-08-21T06:51:39.980 回答