【问题标题】:PHP: refactoring to OO -- How to model JoinsPHP:重构为 OO——如何为连接建模
【发布时间】:2012-07-15 18:51:10
【问题描述】:

假设我有两个模型:

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 *)。我正在重构以使其更易于维护(我也是最初的创建者),但我希望能够在需要时灵活地使用连接,而不会影响我的文件和数据库调用的结构。

我的一个可能相关的问题与包含其他模型有关:

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;
}

我仍在寻找答案,但如果您能以我的方式发送链接,将不胜感激。

【问题讨论】:

  • 我同意。不过,不确定这对您意味着什么。
  • /* @var .. 评论用于自动记录。 PHPDoc 之类的工具可以扫描您的代码并为您生成 API 文档。一些 IDE 也使用它来生成代码完成提示。

标签: php oop model-view-controller join


【解决方案1】:

你所说的“模型”实际上是domain objects。他们应该负责处理领域业务逻辑,与存储无关。

与存储相关的逻辑和交互应由单独的对象组处理。最明智的解决方案之一是使用data mappers。每个mapper可以处理多个表和复杂的SQL。

对于您的查询,此类查询的结果将包含适合传递给域对象集合的信息。

顺便说一句,该查询毫无用处。您忘记了每本书可以有多个作者。以this book 为例——它有 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模型部分的理解和how i see it有很大的不同。

【讨论】:

  • 很难解释应用程序是如何工作的。我确定我有一些误解,但我也无法完全解释我是如何使用这些组件的。我的模型对 MySQL 一无所知。除了文件系统存储等,我正在进一步抽象,但我还没有。域对象是我的管理者,数据映射器是我的模型。我的模型可能太依赖于我正在使用的表(及其字段),所以感谢您帮助我。我遇到的问题是,如果我真的将模型用作对象,我如何确定哪些属性去哪个存储区域?
  • 你是说你粘贴了AuthorBook“models”的完整代码?!
  • 我试图通过讨论我的特定品牌的应用程序来避免混淆这个问题,但对我来说,模型包括与对象相关的属性和方法(例如验证)。管理器包括与存储和检索相关的属性和方法。因此,例如,我的模型被传递给我的经理,以将 PDO 结果集检索到一个对象中。它们还用于检查模型中是否存在字段。最后一点可能需要重新考虑。
  • 该查询不是真实的。我试图保持简单。在您的 buildCollection 示例中,您将提取一本书所需的所有数据,包括作者姓名、标题等。因此这意味着您的数据库调用很可能分散在许多类中。我试图避免这种情况,但也许这是不可能的。这是我的问题的主要基础,我意识到。我只是假设这是其他人所做的。
  • 你看,在OOP中我们有一个叫做SOLID principles的东西,首先是SRP(单一责任原则)。它指出,每个班级应该只有一个责任。或者换句话说:每个班级应该只有一个改变的理由。如果您将所有 SQL 查询合并到一个类中,您将基本上实现 god object 反模式。
【解决方案2】:

数据库连接是关系数据库的产物,您不需要对其建模。您需要对您的数据什么以及它如何表现进行建模,例如你的Author 实例中可能有一个getBooks() 方法,或者你的Book 类中有一个静态getByAuthor() 方法(通常,$author-&gt;getBooks() 应该实现为Book::getByAuthor($this),所以你的Author 类应该'不关心Book 的实现细节)。自动实例化所有相关数据并不总是一个好主意(例如,通过给定的 Author 实例为所有书籍实例化 Book 实例,正如您似乎正在考虑使用您的 $author_books 属性),因为这可能很容易退化为“为每个请求将整个数据库加载到内存”场景。

【讨论】:

  • 原则上我明白了。让我问两个相关的问题:如果我在做构造函数依赖注入,我需要独立注入这个方法吗?第二个问题:现在,我很高兴我的模型(实际上是经理)拥有给定表的所有查询。对此有何建议?我应该只依赖我的 IDE 的搜索功能而不是希望事情井井有条吗?或者我应该在模型(管理器)中添加注释,指出文件外部的存储位置...?
  • Book::getByAuthor($this-&gt;author_id) 不是 OOP。那么重构的意义何在?
  • @tereško:哎呀,有点过于简单化了。
  • 我认为这个想法仍然有效。如果作者有书,而我希望 Author 类有这个,我会有一个方法 getBooks() 不是吗?在那里,我需要找到某种方法将 Book 对象拉入我的 Author 类中的数组中,对吗?所有这一切都与 DI 一起,所以我不会把这弄得一团糟……
  • 我认为您不应该为这种方法独立注入。作者将通过相同的依赖关系与他的书籍进行所有交互,而不仅仅是为了找到它们。我基本同意tereško's article;你不应该为每个表都有一个相应的模型,反之亦然。不要在你的数据库架构上建模你的数据和类结构,这会在很多方面限制你,同时不会给你任何显着的优势。
【解决方案3】:

如果您尝试在类中建模您的数据库,这是一个已经解决的问题。

我建议你试试Doctrine 框架,它是一个完整的 PHP 的 ORM 框架。

希望对您有所帮助。

【讨论】:

  • 谢谢。我会看看这个,但这似乎是一个相当直截了当的问题。即使我最终使用了独立于我的项目的东西,我仍然想了解如何去做。
  • 这实际上是一个非常复杂的问题;比听起来更重要。描述的简单父子关系可能看起来很简单,但 SQL 可以生成一些非常复杂的关系,其中许多关系可能很难映射到类结构中。
  • 同意,而且我不介意直接调用我的一些更复杂的查询(我实际上比 PHP 应用程序的几乎任何其他方面都更精通 MySQL)。我只是想知道人们在遇到这种情况时是否通常会做一些事情(因为每个网站都可能有类似的关联)。
  • 人们通常会考虑使用经过深思熟虑和经过验证的方法...例如Doctrine。既然源头是开放的,何不为斯普德利竖起大拇指,一探究竟呢?真的:它并不像听起来那么简单。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-30
  • 1970-01-01
  • 2012-10-16
  • 1970-01-01
  • 2023-03-12
  • 2015-04-09
  • 1970-01-01
相关资源
最近更新 更多