php - 与 OOP 概念斗争

标签 php database codeigniter oop model-view-controller

我真的在为重复出现的 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来自返回的对象 user_id s:
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;
        }
    }
}

...但是,当然,如果我创建一个“社区”类,在它的构造函数中我需要加入 communitiescommunities_groups 的表与 groups 的表与 groups_users 的表与 users 的表 table 。

...如果我创建一个“城市”类,在它的构造函数中我需要加入 citiescities_communities 的表与 communities 的表与 communities_groups 的表与 groups 的表与 groups_users 的表与 users 的表 table 。

多么深不可测的灾难啊!

我是否必须在具有一百万次查询的漂亮 OOP 代码 VS.OOP 代码之间进行选择? 1 查询并为每个超集手动编写这些连接?没有系统可以自动执行此操作吗?

我正在使用 CodeIgniter,并查看了无数其他 MVC 以及在其中构建的项目,并且找不到任何人在不使用我概述的两种有缺陷的方法之一的情况下使用模型的良好示例。

似乎以前从未这样做过。

我的一位同事正在编写一个框架来完成此任务 - 您创建一个包含数据模型的类。其他,更高 型号可以包括 那个单一的模型,它制作和自动化表连接以创建 更高 包含 的对象实例化的模型下 模型,全部在一个 单个查询 .他声称他以前也从未见过这样做的框架或系统。

请注意:
我确实总是使用单独的类来实现逻辑和持久性。 (VO 和 DAO - 这是 MVC 的全部内容)。为了简单起见,我只是在这个思想实验中将两者结合起来,在类似 MVC 的架构之外。请放心,无论逻辑和持久性如何分离,此问题仍然存在。我相信this article由 James 在这个问题下面的评论中介绍给我,似乎表明我提出的解决方案(我已经遵循了多年)实际上是开发人员目前为解决这个问题所做的。然而,这个问题试图找到的方法。自动化 那个确切的解决方案,所以它并不总是需要为每个超集手动编码。从我所见,PHP 以前从未这样做过,我同事的框架将是第一个这样做的,除非有人可以指出我这样做的框架。

而且,当然,我从不在构造函数中加载数据,我只在实际需要数据时调用我创建的 load() 方法。然而,这与这个问题无关,因为在这个思想实验中(以及在我需要自动化的现实生活中),我 总是 需要预先加载 的数据 child 的所有子集尽其所能,以及 不是 根据需要在 future 某个时间点延迟加载它们。思想实验很简洁——它不遵循最佳实践是一个有争议的问题,试图解决其布局的答案同样没有捕获要点。

编辑:为了清楚起见,这是一个数据库架构。
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,
)

(还要注意,我最初使用了 wheel s 和 car s 的概念,但这是愚蠢的,这个例子更清晰。)

解决方案:

我最终找到了一个完全可以做到这一点的 PHP ORM。是Laravel's Eloquent .您可以指定模型之间的关系,它会使用如下语法智能地构建优化查询以进行预加载:
Group::with('users')->get();

它是绝对的救星。我不必编写一个查询。它也不能使用连接工作,它根据外键智能地编译和选择。

最佳答案

Say you have a "wheel" class, which loads its data from the wheels table in its constructor


构造函数不应该做任何工作。相反,它们应该只包含作业。否则,您将很难测试实例的行为。

Now, we have a "car" class, which loads its data from the cars table joined with the cars_wheels table and creates wheel objects from the returned wheel_ids:


不,这有两个问题。
您的 Car类不应同时包含用于实现“汽车逻辑”和“持久性逻辑”的代码。否则你就是在破坏 SRP .轮子是类的依赖项,这意味着轮子应该作为构造函数的参数注入(inject)(最有可能 - 作为轮子的集合,或者可能是数组)。
相反,您应该有一个映射器类,它可以从数据库中检索数据并将其存储在 WheelCollection 中。实例。还有一个用于汽车的映射器,它将数据存储在 Car实例。
$car = new Car;
$car->setId( 42 );
$mapper = new CarMapper( $pdo );
if ( $mapper->fetch($car) ) //if there was a car in DB
{
    $wheels = new WheelCollection;
    $otherMapper = new WheelMapper( $pdo );

    $car->addWheels( $wheels );
    
    $wheels->setType($car->getWheelType());
    // I am not a mechanic. There is probably some name for describing 
    // wheels that a car can use
    $otherMapper->fetch( $wheels );
}
像这样的东西。在这种情况下,映射器负责执行查询。并且您可以为它们提供多个来源,例如:拥有一个检查缓存的映射器,只有在失败时才从 SQL 中提取数据。

Do I really have to choose between beautiful OOP code with a million queries VS. 1 query and disgusting, un-OOP code?


不,丑陋来自active record模式仅适用于最简单的用例(其中几乎没有关联的逻辑,具有持久性的美化值对象)。对于任何重要的情况,最好应用 data mapper图案。

..and if I make a "city" class, in its constructor I'll need to join the cities table with the cities_dealerships table with the dealerships table with the dealerships_cars table with the cars table with the cars_wheels table with the wheels table.


只是因为您需要有关“莫斯科每个经销商的可用护理”的数据并不意味着您需要创建 Car实例,你绝对不会关心那里的轮子。网站的不同部分将具有不同的运营规模。
另一件事是您应该停止将类视为表抽象。 没有规则说“类和表之间必须是 1:1 的关系” .
Car再举个例子。如果你看它,有单独的 Wheel (甚至 WheelSet )类只是愚蠢的。相反,您应该只有一个 Car已经包含所有部分的类。
$car = new Car;
$car->setId( 616 );

$mapper = new CarMapper( $cache );
$mapper->fetch( $car );
映射器不仅可以轻松地从“Cars”表中获取数据,还可以从“Wheel”和“Engines”以及其他表中轻松获取数据并填充 $car目的。
底线:停止使用事件记录。

P.S.: also, if you care about code quality, you should start reading PoEAA book. Or at least start watching lectures listed here.


我的 2 美分

关于php - 与 OOP 概念斗争,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20135765/

相关文章:

php - 从 2 个带条件的表进行 SQL 查询

jquery - 在 codeigniter 中将二维数组从 Controller 传递到模型

php - 如何在 php/mysql 中以 12 小时时间格式使用 >= AND <= 获取数据?

php - 如何在网站上显示不同货币的价格?

php - 直接访问 wordpress 表有危险吗?

php - 尝试在 codeigniter 中获取 Assets CSS 时出现 404 错误?

php - 在 PHP 中从 ipa 中提取嵌入式配置文件

php - 保护 JQuery AJAX 代码的指南?

PHP、MySQL 和 JQuery - 更新表而不刷新页面

mysql - CodeIgniter 搜索结果出乎意料; where() 和 or_like() 坏了?