什么是使PHP网站接近数据库面向对象的好方法?

时间:2013-11-19 08:16:32

标签: php mysql oop

请注意我不是在寻找'使用框架'的答案。我正在尝试从结构上改进我编写网站和从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,用于处理与数据库的任何交互。

3 个答案:

答案 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应用程序,以及如何以某种方式为您实现这些模式。