PHP: 重构为面向对象 -- 如何建模连接

5

假设我有两个模型:

class Book
{
    public $book_id;
    public $book_author_id;
    public $title;
}

class Author
{
    public $author_id;
    public $author_name;
}

我习惯写这样的代码:

$DB->query("SELECT book_id, title, author_name 
            FROM book 
            LEFT JOIN author 
                ON book_author_id = author_id
           ");

假设我不想为此关联创建单独的查询,应该如何处理?以下是我听到的一些建议:
  • 创建一个JOIN的MySQL VIEW
  • 创建一个VIEW的模型类
我正在处理一个涉及数十个表的应用程序,并且在过程式代码中进行了高度优化(例如几乎没有SELECT *)。我正在重构以使其更易于维护(我也是原始创建者),但我希望在需要时具有使用JOIN的灵活性,而不会影响我的文件和DB调用结构。
我可能有一个相关的问题,与包括其他模型相关:
class Author
{
    public $author_id;
    public $author_name;

    /* @var Book */ //<--don't really fully understand this but I've seen something like it somewhere
    public (array) $authors_books;
}

我仍在寻找答案,但如果你能发送一个链接,我会非常感激。

我同意。不过不确定这对你来说意味着什么。 - Stephane
1
/* @var .. 注释是用于自动文档的。像 PHPDoc 这样的工具可以扫描您的代码并为您生成 API 文档。它还被一些 IDE 用于生成代码完成提示。 - Spudley
3个回答

3
你所称之为“模型”实际上是领域对象。它们应该负责处理领域业务逻辑,与存储无关。
与存储相关的逻辑和交互应由另一组对象处理。最明智的解决方案之一是使用数据映射器。每个映射器可以处理多个表和复杂的SQL。
至于你的查询,这样的查询结果应包含适合传递给领域对象集合的信息。
顺便说一下,这个查询是相当无用的。你忘记了每本书都可能有多个作者。以这本书为例 - 它有4个不同的作者。要使此查询有用,你必须基于author_idbook_id进行GROUP_CONCAT()
在实现这样的JOIN语句时,数据库响应很可能是一个集合:
$mapper = $factory->buildMapper('BookCollection');
$collection = $factory->buildCollection('Book');

$collection->setSomeCondition('foobar');
$mapper->fetch( $collection );
foreach ( $collection as $item )
{
    $item->setSomething('marker');
}
$mapper->store( $collection );

P.S. 你的代码示例似乎存在泄漏抽象的问题。让其他结构直接访问对象的变量是一种不好的实践。

P.P.S. 看起来你对MVC模型部分的理解与我的看法相当不同。


这可能仍然是同一件事,但我试图将一个表的查询整合到一个地方,而不是所有的查询。 - Stephane
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/13969/discussion-between-stephane-and-teresko - Stephane
@tereško,您的示例很有趣,但可惜您没有提供类代码 - 没有它很难理解。 - Hexodus
@Hexodus,哪部分是“难以理解”的部分?此外,提供实现而不是API将适得其反,因为它将取决于项目的精确要求。 - tereško
@tereško 实现部分。 - Hexodus
显示剩余9条评论

2

数据库连接是关系型数据库的产物,你不需要对其进行建模。你需要对数据本身及其行为进行建模,例如在你的Author实例中可能会有一个getBooks()方法,或者在你的Book类中可能会有一个静态的getByAuthor()方法(一般来说,$author->getBooks()应该被实现为Book::getByAuthor($this),因此你的Author类不应该关心Book的实现细节)。并不总是将所有相关数据自动实例化是一个好主意(例如为给定的Author实例实例化Book实例,就像你似乎正在考虑的$author_books属性),因为这可能很容易降级为“为每个请求将整个数据库加载到内存中”的情况。


Book::getByAuthor($this->author_id)不是面向对象编程(OOP)。那么重构的意义在哪里呢? - tereško
@tereško:哎呀,我在过度简化问题时有点失态了。 - lanzz
我认为这个想法仍然适用。如果一个作者有书,我希望作者类也具备这个属性,那么我会有一个getBooks()方法,对吧?在这个方法中,我需要找到一些方法将Book对象放入我的Author类内的数组中,是吗? 这一切都需要使用依赖注入,以避免使代码混乱不堪。 - Stephane
我认为你不应该独立为这个方法注入依赖项。作者与他的书籍的所有交互都通过同一个依赖项进行,而不仅仅是为了找到它们。我在很大程度上同意tereško的文章; 你不应该为每个表格和相反情况下都有一个对应的模型。不要根据数据库架构对数据和类结构进行建模,这会在许多方面限制你,而且不会给你任何显着的优势。 - lanzz
请务必详细注释您的代码;面向对象编程越抽象,没有注释就越难追踪调用堆栈,并且当您注入的依赖可能来自代码库完全不同的部分时,编写自我注释代码很快就变得不可能。 - lanzz
显示剩余4条评论

1
如果您正在尝试使用类来建模数据库,那么这个问题已经有了解决方案。
我建议您尝试使用Doctrine框架,它是一个完整的PHP ORM框架。
希望能对您有所帮助。

谢谢。我会看看这个,但这似乎是一个相当简单明了的问题。即使最终我使用与我的项目无关的东西,我仍然想要理解如何做到这一点。 - Stephane
这实际上是一个令人惊讶的复杂问题,比听起来更加复杂。如所描述的简单的父子关系可能看起来很简单,但 SQL 可以生成一些非常复杂的关系,其中许多在类结构中映射可能非常困难。 - Spudley
同意,我不介意在一些更复杂的查询中直接调用(实际上,我对MySQL比PHP应用程序的其他方面更熟练)。我只是想知道当人们遇到这种情况时是否有常见的做法(因为每个站点可能都有类似的关联)。 - Stephane
人们通常会考虑使用经过深思熟虑和经过验证的方法...比如Doctrine。由于源代码是开放的,为什么不给Spudley点赞并深入了解这个美丽的框架呢?但实际上:它并不像听起来那么简单。 - Bart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接