如何使用对象来表示关系数据库中的数据而不进行过多的数据库查询?

问题描述 投票:0回答:1

我正在尝试设计我的第一个复杂的面向对象的 PHP 应用程序,但我对如何使用对象来表示数据库中的数据感到困惑。更具体地说,我对如何在不进行大量数据库查询的情况下组合多个对象感到困惑。

一个简单的例子:假设我有两个数据库表:

products: - product_id
          - manufacturer_id
          - name
          - price

manufacturers: - manufacturer_id
               - name
               - factory_address
               - factory_zipcode
               - factory_country
               - // [50 additional columns]

我使用两个类来表示这些:

class Product {}
class Manufacturer {}

现在假设我想创建一个列出 1000 个产品的页面。

我使用

Product
类或类似类创建一个包含 1000 个
ProductFactory
对象的数组,如下所示:

class ProductFactory {
    function allProducts() {
        $results = db_query("SELECT * FROM products");
        foreach ($results as $result) {
            $product_objects[] = new Product($result);
        }
        return $product_objects;
    }
}

没关系。这只是一个数据库查询,现在我拥有所有产品数据 - 太棒了!

但是,如果我想创建一个稍微不同的页面,该页面也显示 1000 个产品,但也包含制造商表中的数据,该怎么办?

当然,我可以更改查询以使用

JOIN
从制造商表中返回列作为我的产品查询的一部分,但这对我来说似乎不太灵活,因为:

  1. 我可能并不总是需要制造商数据,因此如果我只在单个页面上而不是其他地方使用它,将其加载到
    ProductFactory
    中似乎很浪费。
  2. 制造商数据可能非常广泛,当我需要的只是制造商名称时,我不希望 50 个额外的列使我的查询结果膨胀。
  3. 如果我在单个查询中获取产品制造商数据,我不知道如何从查询结果中优雅地创建两个单独的对象(
    Product
    Manufacturer
    )而不显得超级混乱。

我想我可以向我的 ProductFactory 类添加第二个方法,例如:

function allProductsWithManufacturerData() {
    // SELECT * FROM products JOIN manufacturers...
}

但这似乎很老套且不灵活。使用我的代码的人不会知道,如果他们想在 Product 对象中包含制造商数据,他们需要专门调用此方法。

或者,我可以仅在明确请求时加载制造商数据:

echo $productObject->getManufacturer()->name;

对我来说,这似乎是一种相当灵活的方法。但在上面的示例中,这将导致 1000 个额外查询(页面上的每个产品一个),这对性能来说将是可怕的。

所以看来我被迫做出选择:

  1. 选择获取您可能需要的所有数据(使用单个 JOIN 查询)
  2. 或者...选择获取最少的数据,然后使用额外的查询来根据需要获取数据。

这些是我唯一的选择还是我错过了什么?是否有我不知道的设计模式可以用来提高灵活性?

php oop design-patterns datamapper select-n-plus-1
1个回答
0
投票

这是我解决这个问题的方法:)

假设您已经获得了 1000 种产品,制造商只有 ID(您通过仅从

products
表中选择来获得)。

$manufacturers = []; 
// I would actually use a Collection class, but that's not important here

foreach ($products as $product) {
    $manufacturers[] = $product->getManufacturer();
}

$manufacturerMapper->populate($manufacturers);

然后,制造商的数据映射器使用

WHERE manufacturer_id IN (....)
条件进行单个选择,并使用设置器“填充”所有对象。

这中继的是以下行为:

$foo = new Thing;
$bar = $foo;
$bar->setStuff('blah');
var_dump($foo->getStuff() === 'blah'); /// TRUE

您传递给映射器的制造商列表与产品实体中已存在的相同制造商。

缺陷

这不是一个完美的解决方案,因为你必须处理“制造商重复”的情况。当您处理 1:1 的情况时,这并不是那么棘手(您只需在映射器内创建一个“索引”即可了解具有匹配 PK 的所有条目)。

但这实际上并没有那么好。

“更正确”的方法是使用称为“身份映射”的东西,它在映射器或存储库中充当运行时缓存。我第一次遇到这个概念是当我阅读有关如何制作“正确的存储库”时。 解决方案的核心是创建此行为:

$first = $repo->get(45); $second = $repo->get(45); $first->setTitle('Lorem Ipsum'); var_dump($second->getTitle() === 'Lorem Ipsum'); // TRUE

这看起来很像前一点,但重要的是,存储库没有返回具有
相同数据

的实体,而是返回相同的实体 我个人在映射器级别实现了此行为,而不是在存储库本身中,而是在 YMMV 中实现。

如果您刚刚开始使用 OOP,请忽略这个“过度杀伤”的解决方案并坚持使用第一部分:)这个扩展版本需要一些样板代码。

© www.soinside.com 2019 - 2024. All rights reserved.