请注意我不是在寻找'使用框架'的答案。我正在尝试从结构上改进我编写网站和从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
,用于处理与数据库的任何交互。
答案 0 :(得分:3)
有一个单独的类来处理SQL查询并保留所获取的数据是很常见的事情。事实上,它是单一责任原则的真正应用。
我通常做的是保留一个类,其中包含有关数据的所有信息,在您的情况下为User类,并将所有用户信息作为字段。
然后是业务层,例如UserDataManager(虽然不推荐使用“Manager”作为后缀,你最好在每个场景中找到一个更合适的名称),它在构造函数中采用pdo对象来避免使用全局变量并拥有所有SQL方法。因此你有方法registerNewUser,findUserById,unsuscribeUser等等(方法中使用“User”可以通过类名暗示并省略)。
希望它有所帮助。
答案 1 :(得分:2)
我喜欢使用data mapper模式(或者至少我认为我是这样做的)。我已经为Silex构建的一些网站做了这个,虽然它适用于没有框架,因为Silex非常轻量级,并且不会对你如何构建代码施加太大的影响。事实上,我建议您查看Symfony2 / Silex,以获得设计代码的方法。
无论如何,我使用过像UserMapper
这样的课程。由于我使用的是Doctrine DBAL库,因此我使用依赖注入为每个映射器提供$db
。但据我所知,DBAL几乎是PDO
类的包装器,所以你可以注入它。
现在你有一个负责CRUD操作的UserMapper
。因此,我使用LoadUser($id)
和LoadAllUsers()
等方法解决了您的第一个问题。然后我将根据数据库中的数据在new User
上设置所有属性。您可以同样拥有CreateUser(User $user)
。请注意,在“create”中,我实际上传递了一个User
对象并将其映射到数据库。您可以将其称为PersistUser(User $user)
以使此区别更清晰。现在所有的SQL查询都在一个地方,而User
类只是一个数据集合。 User
不需要来自数据库,您可以创建测试用户或其他任何未经任何修改的用户。 “用户”的所有持久性都封装在另一个类中。
我不确定加载用户的所有属性是总是不好,但是如果你想解决这个问题,那么制作LoadUsername($id)
或其他方法就不难了。或者您可以使用一组要加载的属性来LoadUser($id, array $properties)
。如果您的命名是一致的,那么很容易设置如下属性:
// in a foreach, $data is the associative array returned by your SQL
$setter = 'set'.$property;
$user->$setter($data[$property]);
或者(和?)你可以用Proxy对象解决这个问题。我没有这样做,但想法是返回一堆UserProxy
个对象,它们扩展User
。但是他们注入了Mapper
,他们会覆盖get
来调用Mapper
以选择更多内容。也许当您在代理上访问一个属性时,它将select
通过映射器(一个名为populateUser($id)
的方法?)以及随后的get
方法的所有内容只能访问内存中的属性。例如,如果您选择所有用户然后需要访问子集上的数据,这可能会有所帮助。但我认为一般来说,选择一切可能更容易。
public function getX()
{
if (!isset($this->x)) {
$this->mapper->populateUser($this);
}
return $this->x;
}
对于新用户,我说只需$user = new User...
并设置好所有内容,然后调入$mapper->persist($user)
。你可以把它包装在另一个类中,比如UserFactory->Create($data)
,它可以返回(持久化的)User
。或者,如果您愿意,可以将该课程称为Registration
。
我是否提到您应该使用Dependency Injection
来处理所有这些服务(例如Mappers
和其他类似Factories
)?也许只是从Silex抓住DIC,称为Pimple。或者自己实现轻量级(这并不难)。
我希望这会有所帮助。这是我通过编写大量PHP并使用Syfmony2 / Silex获得的一些内容的高级概述。祝你好运,很高兴看到像你这样的PHP程序员真的想“做正确的事”!请评论我是否可以在任何地方详细说明。希望这个答案可以帮助你。
答案 2 :(得分:0)
首先应该将一个类作为数据库对象的包装器开始,这对于一个全局变量来说会更干净(如果你不了解它,请阅读Singleton模式,并且有很多例子网络上的Singleton Database Wrapper)。然后,您将更好地了解应该实现的体系结构。
最好是将数据与数据库中的事务分开,这意味着您可以为用户提供两个类;一个只发送查询和获取响应,另一个将管理数据,这要归功于对象的属性和方法。有时候,也可能有一些不需要与数据库交互的动作,也可以在这些类中实现。
最后但同样重要的是,查看MVC框架以及它们如何工作(即使您不想使用它)也是一个好主意;这将使您了解如何构建Web应用程序,以及如何以某种方式为您实现这些模式。