【发布时间】:2013-12-06 19:06:49
【问题描述】:
我真的为反复出现的 OOP/数据库概念而苦恼。
请允许我用伪 PHP 代码解释问题。
假设您有一个“用户”类,它在其构造函数中从users 表中加载其数据:
class User {
public $name;
public $height;
public function __construct($user_id) {
$result = Query the database where the `users` table has `user_id` of $user_id
$this->name= $result['name'];
$this->height = $result['height'];
}
}
简单,很棒。
现在,我们有一个“组”类,它从groups 表中加载其数据,并与groups_users 表连接,并根据返回的user_ids 创建user 对象:
class Group {
public $type;
public $schedule;
public $users;
public function __construct($group_id) {
$result = Query the `groups` table, joining the `groups_users` table,
where `group_id` = $group_id
$this->type = $result['type'];
$this->schedule = $result['schedule'];
foreach ($result['user_ids'] as $user_id) {
// Make the user objects
$users[] = new User($user_id);
}
}
}
一个组可以有任意数量的用户。
美丽、优雅、令人惊叹……在纸上。然而,实际上,创建一个新的组对象...
$group = new Group(21); // Get the 21st group, which happens to have 4 users
...执行 5 个查询而不是 1 个。(组 1 个,每个用户 1 个。)更糟糕的是,如果我创建一个 community 类,其中有很多组,每个组中有很多用户,运行了非常多的查询!
不适合我的解决方案
多年来,我解决这个问题的方法是不以上述方式编码,而是在制作group 时,例如,我会将groups 表加入groups_users 表也到users 表,并在group 对象中创建一个类似用户对象的数组数组(从不使用/触摸user 类):
class Group {
public $type;
public $schedule;
public $users;
public function __construct($group_id) {
$result = Query the `groups` table, joining the `groups_users` table,
**and also joining the `users` table,**
where `group_id` = $group_id
$this->type = $result['type'];
$this->schedule = $result['schedule'];
foreach ($result['users'] as $user) {
// Make user arrays
$users[] = array_of_user_data_crafted_from_the_query_result;
}
}
}
...但是,当然,如果我创建一个“社区”类,在其构造函数中,我需要将communities 表与communities_groups 表与groups 表与@ 连接起来987654344@ 表与users 表。
...如果我创建一个“城市”类,在其构造函数中,我需要将cities 表与cities_communities 表与communities 表与communities_groups 表与groups 表与groups_users 表与users 表。
真是一场彻头彻尾的灾难!
我必须在具有一百万个查询的漂亮 OOP 代码 VS 之间做出选择吗? 1 查询并为每个超集手动编写这些连接?没有系统可以自动执行此操作吗?
我正在使用 CodeIgniter,并研究了无数其他 MVC,以及在其中构建的项目,如果不求助于我概述的两种有缺陷的方法之一,我找不到任何使用模型的好例子。
这似乎是以前从未做过的。
我的一位同事正在编写一个框架来实现这一点 - 您创建一个包含数据模型的类。其他更高模型可以包含该单个模型,它会制作和自动化表连接以创建包含对象实例化的更高模型较低的模型,全部在一个单个查询中。他声称他以前也从未见过这样做的框架或系统。
请注意: 我确实总是使用单独的类来实现逻辑和持久性。 (VO 和 DAO - 这是 MVC 的全部要点)。为了简单起见,我只是在这个思想实验中将两者结合起来,在类似 MVC 的架构之外。请放心,无论逻辑和持久性如何分离,此问题都会持续存在。我相信 this article,由 James 在这个问题下面的 cmets 中介绍给我,似乎表明我提出的解决方案(我多年来一直在关注)实际上是开发人员目前为解决这个问题所做的。然而,这个问题试图找到自动化该精确解决方案的方法,因此并不总是需要为每个超集手动编码。据我所知,这在 PHP 之前从未做过,我同事的框架将是第一个这样做的,除非有人能指出我这样做的。
而且,当然,我从不在构造函数中加载数据,我只在实际需要数据时调用我创建的 load() 方法。然而,这与这个问题无关,因为在这个思想实验中(以及在我需要自动化的现实情况中),我总是需要预先加载 的数据所有孩子的子集尽可能远,并且不根据需要在未来某个时间点延迟加载它们。思想实验很简洁——它没有遵循最佳实践是一个有争议的问题,试图解决其布局的答案同样没有抓住重点。
编辑:为清楚起见,这是一个数据库架构。
CREATE TABLE `groups` (
`group_id` int(11) NOT NULL, <-- Auto increment
`make` varchar(20) NOT NULL,
`model` varchar(20) NOT NULL
)
CREATE TABLE `groups_users` ( <-- Relational table (many users to one group)
`group_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL
)
CREATE TABLE `users` (
`user_id` int(11) NOT NULL, <-- Auto increment
`name` varchar(20) NOT NULL,
`height` int(11) NOT NULL,
)
(还要注意,我最初使用了wheels 和cars 的概念,但那是愚蠢的,这个例子更清楚。)
解决方案:
我最终找到了一个可以做到这一点的 PHP ORM。它是Laravel's Eloquent。您可以指定模型之间的关系,它会使用如下语法智能地构建优化查询以进行预加载:
Group::with('users')->get();
这是一个绝对的救生员。我不必编写一个查询。它也不能使用连接,它可以智能地根据外键进行编译和选择。
【问题讨论】:
-
如果你仔细想想,你可能只有 3 个查询。 1 获得城市经销商,2 获得经销商汽车,1 获得车轮。如何检索数据取决于您,但您仍然可以使用优雅的 OOP 代码来反映对象。
-
@KennyThompson 我怀疑经销商实际上更接近于类似repository 的结构。仅仅因为您的数据库包含 10,000 辆汽车,并不意味着将它们全部转化为对象是可行的。
-
@Leng 这就是所谓的 N+1 问题,这是 ORM 的经典问题。您可以使用更有效的查询来拥有特定于组的 DAO。这是一篇关于该主题的文章,专门针对 PHP。您可以查找的其他概念是惰性加载和急切加载。 webadvent.org/2011/a-stitch-in-time-saves-nine-by-paul-jones
-
@James Perfect,你已经回答了我的问题......查询和缝合正是我在上述问题的解决方案中提出的,这也是我多年来一直在做的事情。我想当我的同事发布他正在设计的这个框架时,他将是第一个创建真正的 OOP 渴望获取对象的模型(您不必自己构建连接和缝合 - 一旦您创建了子类,所有父类与之相关的类可以进行连接以急切地获取所有相关的孩子。)
-
@Leng 如果想添加延迟加载,我听说 Java 中的 Hibernate 框架使用代理设计模式。
标签: php database codeigniter oop model-view-controller