苦苦挣扎与OOP概念

时间:2013-11-22 02:17:33

标签: 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'];
    }
}

简单,棒极了。

现在,我们有一个“group”类,它从与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类):< / p>

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表一起加入{{1带有groups表和groups_users表的表。

...如果我创建一个“city”类,在构造函数中我需要将users表与cities表和cities_communities表一起加入带有communities表的communities_groups表,groups表带有groups_users表。

多么彻底的灾难!

我是否必须在具有一百万个查询VS的漂亮OOP代码之间进行选择。 1为每个超集手动查询和编写这些连接?是否没有自动化系统?

我正在使用CodeIgniter,并且正在研究无数其他MVC,以及在其中构建的项目,并且找不到使用模型的任何人的一个好例子,而不采用我概述的两种有缺陷的方法之一。 / p>

以前从未做过这件事。

我的一位同事正在编写一个完全符合这一要求的框架 - 您创建了一个包含数据模型的类。其他更高模型可以包括该单一模型,并且它可以制作并自动化表连接以创建包含对象实例化的更高模型较低模型,全部位于单个查询中。他声称他以前从未见过这样做的框架或系统。

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

而且,当然我从不在构造函数中加载数据,而且我只调用我在实际需要数据时创建的load()方法。然而,这与此问题无关,正如在这个思想实验中(以及在我需要自动执行此操作的现实生活中),我总是需要急切加载的数据所有孩子的子集尽可能远的线,并且根据需要在某个未来的某个时间点加载它们。思想实验很简洁 - 它不遵循最佳实践是一个没有实际意义的点,而试图解决其布局的答案同样缺乏重点。

编辑:为清晰起见,这是一个数据库架构。

users

(另请注意,我最初使用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的概念,但这是愚蠢的,这个例子更加清晰。)

SOLUTION:

我最终找到了一个完全符合这个要求的PHP ORM。它是Laravel's Eloquent。您可以指定模型之间的关系,并使用以下语法智能地为急切加载构建优化查询:

car

这是一个绝对的救命。我没有必要写一个查询。它也不能使用连接,它可以根据外键智能地编译和选择。

3 个答案:

答案 0 :(得分:9)

  

假设您有一个“wheel”类,它从其构造函数

中的wheels表加载其数据

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

  

现在,我们有一个“car”类,它从与cars_wheels表连接的cars表中加载数据,并从返回的wheel_ids创建wheel对象:

没有。这有两个问题。

您的Car类不应包含用于实现“汽车逻辑”和“持久性逻辑”的代码。否则你正在打破SRP。轮子是类的依赖,这意味着轮子应该作为构造函数的参数注入(很可能 - 作为轮子的集合,或者可能是数组)。

相反,你应该有一个mapper类,它可以从数据库中检索数据并将其存储在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中提取数据。

  

我是否真的必须在具有一百万个查询VS的漂亮OOP代码之间进行选择。 1查询和恶心,非OOP代码?

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

  

..如果我创建一个“城市”类,在其构造函数中,我需要将cities_dealerships表与citiessdealerships表一起加入customerships_cars表,其中包括带有wheel_wheels表的汽车表。表

Jut,因为您需要有关“莫斯科每个经销商的可用护理”的数据并不意味着您需要创建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。:另外,如果您关心代码质量,则应该开始阅读PoEAA本书。或至少开始观看lectures listed here

我的2美分

答案 1 :(得分:2)

Rails中的ActiveRecord实现了延迟加载的概念,即在实际需要数据之前推迟数据库查询。因此,如果您实例化my_car = Car.find(12)对象,它只会查询cars表中的那一行。如果以后你想要my_car.wheels,那么它会查询wheel表。

我对上面的伪代码的建议是不加载构造函数中的每个关联对象。汽车构造函数应该只查询汽车,并且应该有一个方法来查询它的所有轮子,另一个查询它的经销商,经销商只询问经销商并推迟收集所有其他经销商的汽车,直到你具体说出一些东西比如my_car.dealership.cars

<强>后记

ORM是数据库抽象层,因此必须调整它们以便于查询而不是微调。它们允许您快速构建查询。如果稍后您决定需要对查询进行微调,那么您可以切换到发出原始sql命令或尝试以其他方式优化您要获取的对象数。当你开始进行性能调优时,这是Rails中的标准做法 - 查找在使用原始sql时更高效的查询,并且还需要在需要之前寻找避免对象的急切加载(与延迟加载相反)的方法。

答案 2 :(得分:0)

一般来说,我建议使用一个构造函数来有效地查询查询行或更大查询的一部分。这怎么做取决于你的ORM。这样,您可以获得有效的查询,但您可以在事后构建其他模型对象。

一些ORM(django的模型,我相信一些ruby ORM)试图巧妙地构建查询,并且可以为您自动执行此操作。诀窍是弄清楚何时需要自动化。我对PHP ORM没有个人熟悉。