简体   繁体   English

实例化结果集中的对象……最佳实践?

[英]Instantiating objects within result sets… best practices?

I'm developing an intranet site for reporting on data. 我正在开发一个Intranet网站来报告数据。 I've developed various classes for Customer, Order, Item, Invoice, etc that all relate to each other in one way or another. 我为客户,订单,物料,发票等开发了各种类,它们以一种或另一种方式相互关联。 Each class constructor queries a MySQL database (multiple queries) and populates the properties accordingly. 每个类构造函数查询一个MySQL数据库(多个查询)并相应地填充属性。

Currently, to try and keep code convenient and consistent, I'm relying a lot on instantiating classes in my reporting. 当前,为了使代码保持方便和一致,我在报告中非常依赖实例化类。 The problem is, each class constructor may have a bunch of MySQL queries within them, pulling or calculating relevant properties from various sources. 问题是,每个类构造函数中可能都有一堆MySQL查询,它们从各种来源提取或计算相关属性。 This causes a performance hit because of all the queries within loops. 由于循环内的所有查询,这会导致性能下降。 It's usable, and I wont ever have a ton of users at once.. but it could be much faster. 它是可用的,而且我一次也不会拥有大量用户。.但是它可能要快得多。

For example, let's say I'm listing the last 50 orders from a customer. 例如,假设我要列出一个客户的最近50个订单。 The way I'm doing it now, I'll typically write a simple query that returns the 50 order ID's alone for that customer. 现在,我通常会编写一个简单的查询,该查询仅返回该客户的50个订单ID。 Then while looping through the results, I'll instantiate a new order for each results. 然后,在遍历结果的同时,我将为每个结果实例化一个新顺序。 Sometimes I may even go one level deeper and then instantiate a new object for each item within the order. 有时我什至可以更深入一点,然后为订单中的每个项目实例化一个新对象。

Some pseudocode to give the idea.... 一些伪代码给出了这个想法。

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

I may only use a few of the object properties in a summary row, but when I drill down and view an order in more detail, the Order class has all the properties I need ready to go, nice and tidy. 我可能只在摘要行中使用一些对象属性,但是当我向下钻取并更详细地查看订单时,Order类具有我需要准备就绪的所有属性,既美观又整洁。

What's the best way this is typically handled? 通常最好的处理方式是什么? For my summary queries, should I try to avoid instantiating classes and get everything in one query that's separate from the class? 对于我的摘要查询,是否应该尝试避免实例化类,并在与该类分开的一个查询中获取所有内容? I'm worried that will cause me to have to manually do a lot of background work I typically do within the class every single time I do a result set query. 我担心这将导致我每次执行结果集查询时都必须手动执行很多通常在该类中执行的后台工作。 I'd rather make a change in one spot and have it reflect on all the pages that I'm querying the effected classes/properties. 我宁愿在一处进行更改,并将其反映在我查询受影响的类/属性的所有页面上。

What are other ways to handle this appropriately? 还有其他适当处理此问题的方法吗?

This question is pretty open-ended, so I'm going to give a fairly broad answer and try to not get too carried away. 这个问题是开放式的,因此我将给出一个相当广泛的答案,并尽量不要太过分。 I'll start out by saying that an ORM solution like Doctrine's, Propel's, or Symfony's is probably the most ideal for managing relational objects, but not always practical to implement quickly or cleanly (it can take a while to learn the ORM and then convert existing code). 我首先要说的是,像Doctrine's,Propel's或Symfony's这样的ORM解决方案可能是管理关系对象的最理想方法,但并非总是快速或干净地实现(在学习ORM然后转换之前可能要花一些时间)现有代码)。 Here's my take on a more light-weight approach. 这是我采取的一种更轻量级的方法。

To start out, it might help to take your database queries out of the class constructors so that you can have better control over when you access your database. 首先,将数据库查询从类构造函数中删除可能会有所帮助,以便您可以更好地控制何时访问数据库。 One strategy is to add static methods to your class(es) for fetching results. 一种策略是将静态方法添加到您的类中以获取结果。 In addition, you can provide options to "prefetch" child objects so that you can perform your queries in bulk. 此外,您可以提供“预取”子对象的选项,以便可以批量执行查询。 So to go into your example, the external API would look something like this: 因此,以您的示例为例,外部API如下所示:

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

The idea here is that you want to get an array of orders with the getOrders() method and tell getOrders() to fetch item child objects at the same time. 这里的想法是,您希望使用getOrders()方法获取订单数组,并告诉getOrders()同时获取item子对象。 From outside the Order class, it's pretty simple: just pass in an array with the 'items' key set to true . Order类之外,这非常简单:只需将'items'键设置为true的数组传递即可。 Then within the Order class: 然后在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;
    }
}

And in your Item class: 在您的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
        // ...
    }
}

Now, if you know that you will need items when you're getting your orders, pass in a true for the 'items' option: 现在,如果您知道在收到订单时需要物品,请为'items'选项传递true

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

Or if you don't need items, don't specify anything: 或者,如果您不需要物品,则不要指定任何内容:

$orders = Order::getOrders();

Either way, when you are looping through your orders, the API is identical for accessing items: 无论哪种方式,当您遍历订单时,用于访问项目的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(); 
}

As you can see, providing the 'items' option can lead to more efficient use of the database, but if you just need orders without messing around items , you can do that too. 如您所见,提供'items'选项可以更有效地使用数据库,但是如果您只需要orders而又不会弄乱items ,那么您也可以这样做。

And because we provide an array of options to getOrders() , we can easily extend our functionality to include flags for additional child objects (or anything else that should be 'optional'): 并且由于我们为getOrders()提供了一系列选项,因此我们可以轻松地扩展功能,以包括其他子对象(或其他任何应为“可选”)的标志:

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

...and you can proxy those options to child objects if needed: ...并且您可以根据需要将这些选项代理到子对象:

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

If you aren't judicious about what should or should not be optional when fetching objects, this approach can become bloated and daunting to maintain, but if you keep your API simple and only optimize when you need to, it can work really well. 如果您在获取对象时不确定应该选择什么或不应该选择什么,这种方法可能会变得ated肿,难以维护,但是如果您保持API简单且仅在需要时进行优化,它就可以很好地工作。 Hope this helps. 希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM