【发布时间】:2013-11-19 08:16:32
【问题描述】:
请注意,我不是在寻找“使用框架”的答案。我正在尝试从结构上改进我使用 PHP 编写网站和处理数据库的方式。
我正在从头开始构建 Web 服务,没有任何框架。我正在使用 LAMP 堆栈,并且正在尝试学习一些 PHP 的 OO 功能。我以前只用OO做移动应用。
我已经做了几个月了(按计划进行,不用担心)。在此过程中,我遇到了一些结构性问题,这让我想知道使代码面向对象的最佳方法是什么。
几乎所有问题都以某种方式涉及数据库。假设我们有一个类DB 和一个类User。在大多数情况下,我只需要从数据库中获取单个用户的信息。我认为处理它的一个好方法是拥有一个全局 $_db 变量并让 User 对象像这样查询数据库(过于简单):
class User {
function __construct($id) {
global $_db;
$q = $_db->query("SELECT name, mail FROM user WHERE id = ?", $id);
$this->loadProperties($q);
}
}
现在假设我们有一个显示用户列表的页面。我仍然想为每个人创建User 对象,但我不想为每个单独的用户查询数据库。
所以,我扩展了User 类以将对象作为参数:
class User {
function __construct($id) {
if(is_object($id))
$q = $id;
else {
global $_db;
$q = $_db->query("SELECT name, mail FROM user WHERE id = ?", $id);
}
$this->loadProperties($q);
}
}
现在我可以创建一个列表,例如最近创建和活跃的 100 个帐户:
$user_list = [];
$q = $_db->query("SELECT name, mail FROM user WHERE banned = 0 ORDER BY date_created DESC LIMIT 100");
while($a = $_db->fetch($q))
$user_list[] = new User($a);
这一切都很好,除了一个很大的缺点:表user 的数据库查询不再在一个地方,这是一种制作意大利面条的代码。这就是我开始怀疑这是否可以更有效地完成的地方。
所以也许我需要扩展我的DB 对象而不是我的User 对象,例如:
class DB {
public function getUsers($where) {
$q = $this->query("SELECT name, mail FROM user WHERE ".$where);
$users = [];
while($a = $this->fetch($q))
$users[] = new User($a);
}
}
现在我将按如下方式创建用户列表:
$user_list = $_db->getUsers("banned = 0 ORDER BY date_created DESC LIMIT 100");
但现在我使用各种 SQL 查询在各个地方调用 getUsers() 方法,但什么也没解决。我也不想每次都加载相同的属性,所以我的getUsers() 方法必须将整个 SQL 查询作为参数。无论如何,你明白了。
说到加载不同的属性,还有一件事一直困扰着我用 PHP 编写 OO。假设我们的 PHP 对象至少具有数据库行所具有的所有属性。说我有一个方法User::getName():
class User {
public function getName() {
return $this->name;
}
}
此函数将假定已从数据库加载适当的字段。但是,每次创建对象时都预加载所有用户的属性是低效的。有时我只需要用户名。另一方面,此时进入数据库加载这个属性也是低效的。
我必须确保我使用的每种方法都已经加载了相应的属性。从性能的角度来看,这完全有道理,但从 OO 的角度来看,这意味着您必须事先知道要使用哪些方法,这会使其动态性大大降低,并且再次允许意大利面条式代码。
我遇到的最后一件事(至少目前是这样)是如何将实际的新用户与new User 区分开来。我想我会使用一个名为Registration 的单独类(同样,过于简单化了):
class Registration {
function createUser() {
$form = $this->getSubmittedForm();
global $_db;
$_db->query("INSERT INTO user (name, mail) VALUES (?, ?)", $form->name, $form->mail);
if($_db->hasError)
return FALSE;
return $_db->insertedID;
}
}
但这意味着我必须为每个数据库表创建两个单独的类,并且我有不同的类访问同一个表。更不用说还有一个处理登录会话的第三类也访问用户表。
总之,我觉得以上所有方法都可以更有效地完成。最重要的是我想要漂亮的代码。我觉得我错过了从 OO 角度处理数据库的方法。但是我怎样才能在不失去 SQL 查询的动态和强大功能的情况下做到这一点呢?
我期待阅读您在该领域的经验和想法。
更新
似乎你们中的大多数人都谴责我使用global $_db。尽管您已经说服我这不是最好的方法,但对于这个问题的范围,无论我是通过参数、全局还是单例来提供数据库都无关紧要。它仍然是一个单独的类 DB 来处理与数据库的任何交互。
【问题讨论】:
-
第 1 步。避免使用
global。 -
第 2 步。了解Dependency Injection
-
@cwallenpoole 我不认为应该完全避免
global。不同的对象将如何访问登录用户的信息? -
@Robbert 有很多方法。由于您使用的是 OOP,因此您可能有一个单例来管理它,或者至少有一个带有静态变量的方法。
-
@Robbert 至少,将您的 DB 变量包装在一个类或函数中意味着以后不可能意外重新分配,这是一件非常好的事情。