2

更新: 09/02/2009 - 修订问题,提供更好的示例,增加赏金。


嗨,
我正在使用数据库和实体(域对象)之间的数据映射器模式构建一个 PHP 应用程序。我的问题是:

封装通常执行的任务的最佳方法是什么?

例如,一项常见任务是从站点映射器检索一个或多个站点实体,并从页面映射器检索它们的关联(主页)页面实体。目前,我会这样做:

$siteMapper = new Site_Mapper();
$site = $siteMapper->findByid(1);

$pageMapper = new Page_Mapper();
$site->addPage($pageMapper->findHome($site->getId()));

现在这是一个相当微不足道的例子,但实际上它变得更加复杂,因为每个站点也有一个关联的语言环境,并且页面实际上有多个修订版(尽管出于本任务的目的,我只对最近的一个感兴趣)。

我将需要在我的应用程序中的多个位置执行此操作(获取站点和关联的主页、区域设置等),并且我想不出封装此任务的最佳方式/位置,所以我不会必须到处重复。理想情况下,我希望得到这样的结果:

$someObject = new SomeClass();
$site = $someObject->someMethod(1); // or
$sites = $someObject->someOtherMethod();

生成的站点实体已创建关联实体并可供使用。

保存这些对象时也会出现同样的问题。假设我有一个站点实体和关联的主页实体,并且它们都已被修改,我必须执行以下操作:

$siteMapper->save($site);
$pageMapper->save($site->getHomePage());

同样,微不足道,但这个例子被简化了。重复代码仍然适用。

在我看来,拥有某种可以处理的中心对象是有意义的:

  • 检索一个(或多个)站点和所有必要的关联实体
  • 使用新的关联实体创建新的站点实体
  • 获取一个(或多个站点)并保存它和所有关联实体(如果它们已更改)

所以回到我的问题,这个对象应该是什么?

  • 现有的映射器对象?
  • 基于存储库模式的东西?*
  • 基于工作模式的东西?*
  • 还有什么?

*正如您可能猜到的那样,我不完全理解其中任何一个。

有没有解决这个问题的标准方法,有人可以提供他们如何实现它的简短描述吗?我不是在寻找任何人来提供一个完全有效的实现,只是理论。

谢谢,
杰克

4

5 回答 5

2

使用存储库/服务模式,您的存储库类将为您的每个实体提供一个简单的 CRUD 接口,然后服务类将是一个附加层,它执行附加逻辑,如附加实体依赖项。然后,您的应用程序的其余部分仅使用服务。您的示例可能如下所示:

$site = $siteService->getSiteById(1); // or
$sites = $siteService->getAllSites();

然后在 SiteService 类中,您将拥有如下内容:

function getSiteById($id) {
  $site = $siteRepository->getSiteById($id);
  foreach ($pageRepository->getPagesBySiteId($site->id) as $page)
  {
    $site->pages[] = $page;
  }
  return $site;
}

我不太了解PHP,所以如果语法上有问题,请原谅。

于 2009-02-09T19:55:13.057 回答
1

[编辑:此条目试图解决这样一个事实,即编写自定义代码来直接处理情况通常比尝试将问题融入模式更容易。]

模式在概念上很好,但它们并不总是“映射”。经过多年的高端 PHP 开发,我们已经确定了一种非常直接的方式来处理此类问题。考虑一下:

文件:Site.php

class Site
{
   public static function Select($ID)
   {
      //Ensure current user has access to ID
      //Lookup and return data
   }

   public static function Insert($aData)
   {
      //Validate $aData
      //In the event of errors, raise a ValidationError($ErrorList)

      //Do whatever it is you are doing

      //Return new ID
   }

   public static function Update($ID, $aData)
   {
      //Validate $aData
      //In the event of errors, raise a ValidationError($ErrorList)

      //Update necessary fields
   }

然后,为了(从任何地方)调用它,只需运行:

$aData = Site::Select(123);

Site::Update(123, array('FirstName' => 'New First Name'));

$ID = Site::Insert(array(...))

关于 OO 编程和 PHP 要记住的一件事...... PHP 不会在请求之间保持“状态”,因此创建一个对象实例只是为了立即销毁它通常没有意义。

于 2009-02-09T00:39:12.803 回答
0

我可能会首先将常见任务提取到某个辅助方法中,然后等待查看设计要求的内容。感觉现在说还为时过早。

你会给这个方法起什么名字?该名称通常暗示方法所属的位置。

于 2009-02-06T07:05:22.137 回答
0
class Page {

  public $id, $title, $url;

  public function __construct($id=false) {
    $this->id = $id;
  }

  public function save() {
    // ...
  }

}

class Site {

  public $id = '';
  public $pages = array();

  function __construct($id) {
     $this->id = $id;
     foreach ($this->getPages() as $page_id) {
       $this->pages[] = new Page($page_id);
     }
  }

  private function getPages() {
    // ...
  }

  public function addPage($url) {
    $page = ($this->pages[] = new Page());
    $page->url = $url;
    return $page;
  }

  public function save() {
    foreach ($this->pages as $page) {
      $page->save();
    } 
    // ..
  }

}

$site = new Site($id);
$page = $site->addPage('/');
$page->title = 'Home';
$site->save();
于 2009-02-09T01:19:03.723 回答
0

使您的 Site 对象成为Aggregate Root以封装复杂的关联并确保一致性。

然后创建一个SiteRepository,负责检索站点聚合并填充其子项(包括所有页面)。

您将不需要单独的 PageRepository(假设您不将 Page 设为单独的聚合根),并且您的 SiteRepository 也应该负责检索 Page 对象(在您的情况下,使用现有的映射器)。

所以:

$siteRepository = new SiteRepository($myDbConfig);
$site = $siteRepository->findById(1); // will have Page children attached

然后该findById方法还负责查找站点的所有页面子项。这将具有与 CodeMonkey1 给出的答案类似的结构,但是我相信您将通过使用聚合和存储库模式受益更多,而不是为此任务创建特定的服务。站点聚合的任何其他检索/查询/更新,包括其任何子对象,都将通过相同的 SiteRepository 完成。

编辑:这是一个简短的 DDD 指南,可以帮助您了解术语,但如果您想要了解全貌,我真的建议您阅读Evans 。

于 2009-02-10T01:04:31.363 回答