我正在尝试设计我的第一个复杂的面向对象的 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
从制造商表中返回列作为我的产品查询的一部分,但这对我来说似乎不太灵活,因为:
ProductFactory
中似乎很浪费。Product
和Manufacturer
)而不显得超级混乱。我想我可以向我的 ProductFactory 类添加第二个方法,例如:
function allProductsWithManufacturerData() {
// SELECT * FROM products JOIN manufacturers...
}
但这似乎很老套且不灵活。使用我的代码的人不会知道,如果他们想在 Product 对象中包含制造商数据,他们需要专门调用此方法。
或者,我可以仅在明确请求时加载制造商数据:
echo $productObject->getManufacturer()->name;
对我来说,这似乎是一种相当灵活的方法。但在上面的示例中,这将导致 1000 个额外查询(页面上的每个产品一个),这对性能来说将是可怕的。
所以看来我被迫做出选择:
这些是我唯一的选择还是我错过了什么?是否有我不知道的设计模式可以用来提高灵活性?
这是我解决这个问题的方法:)
假设您已经获得了 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,请忽略这个“过度杀伤”的解决方案并坚持使用第一部分:)这个扩展版本需要一些样板代码。