在结果集中实例化对象......最佳实践?

时间:2013-08-21 05:11:28

标签: php mysql class oop loops

我正在开发一个用于报告数据的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类具有我需要准备好的所有属性,很好又整洁。

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

有哪些其他方法可以妥善处理?

1 个答案:

答案 0 :(得分: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
        // ...
    }
}

现在,如果您在收到订单时知道需要商品,请为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简单并且只在需要时进行优化,那么它可以真正起作用好。希望这可以帮助。