将业务逻辑与PHP Doctrine 2分离

7

我使用Symfony 2.3和PHP Doctrine 2。

程序有以下模型:

  • entity Order - a typical customer order
  • entity BadOrderEntry(fields: id, order - unidirectional one-to-one relationship with Order, createdAt)
  • factory BadOrderEntryFactory for creation entity BadOrderEntry
  • repository BadOrderEntryRepository for search methods of entity BadOrderEntry
  • manager BadOrderEntryManager for save/edit/delete methods of entity BadOrderEntry
  • AND MAIN CLASS BadOrderList - list of bad orders, code of this class:

    private $factory;
    private $repository;
    private $manager;
    
    public function __construct(
        BadOrderEntryFactory $f,
        BadOrderEntryRepository $r,
        BadOrderEntryManager $m
    ) {
        $this->factory = $f;
        $this->repository = $r;
        $this->manager = $m;
    }
    
    public function has(Order $order)
    {
        return $this->repository->existsByOrder($order);
    }
    
    public function add(Order $order)
    {
        if (! $this->has($order)) {
            $entry = $this->factory->create($order);
            $this->manager->save($entry);
        }
    }
    
    public function remove(Order $order)
    {
        $entry = $this->repository->findOneByOrder($order);
        if ($entry !== null) {
            $this->manager->delete($entry);
        }
    }
    

我很喜欢这个类的设计。我认真思考了很多。一切都很棒。但是!有一个问题:在add和remove方法中的操作必须在事务中执行。

在PHP Doctrine 2中,事务代码如下:

<?php
$em->getConnection()->beginTransaction();
try {
    //... do some work
    $em->getConnection()->commit();
} catch (Exception $e) {
    $em->getConnection()->rollback();
    throw $e;
}

但是我该如何在BadOrderList中调用这段代码呢?

我花费了很多时间,移除了对数据库(以及相应的PHP Doctrine 2)的依赖,然后再次创建它? 现在依赖关系被隐藏在BadOrderEntryRepository和BadOrderEntryManager类中。

如何在BadOrderList类中隐藏事务机制的依赖关系?


Manager::adddelete 添加事务管理,我建议您重新考虑您的设计。它不太好看。使您的模型具有持久性独立性。 - Ziumin
@Ziumin 我该如何在 Manager::add(或delete)中添加事务管理?有哪些设计问题?Manager只是对Doctrine对象管理器的另一层抽象,它既不好也不坏,但可以提供更多的控制。 - stalxed
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Ziumin
  1. 你是否提供连接注入(doctrine.dbal.default_connection)?这个操作会对类\Doctrine\DBAL\Connection产生依赖。我的问题是,如何避免它。
  2. 这里的MAIN仅指这个问题的上下文。我只创建了一个关于单个问题的主题,并展示了与之相关的代码。
- stalxed
  1. 你可以创建一个扩展了Manager的ORMManager,你可以使用一些抽象的UnitOfWork/Persister/Storage等或其接口与最终的ORM类。无论如何,你需要在模型之外的某个地方适配ORM。你的问题是“如何隐藏BadOrderList类中对事务机制的依赖?”
  2. 为什么? :)
- Ziumin
我不想扩展ORM的功能。我想要隐藏ORM。实际上,在这里需要一些适配器来隐藏PHP Doctrine的事务机制。现在我认为我需要一个类,例如BusinessTransaction,它实现了beginTransaction、commit和rollback方法。最有趣的方法是BusinessTransastion :: beginTransastion,它必须猜测EntityManager使用什么:如果没有参数 - 使用默认值,或者如果调用BusinessTransastion :: beginTransastion(get_class($ order))则自动检测最合适的。你对此有何看法? - stalxed
2个回答

4
在我们的讨论中,我得到了你问题的答案。问题实际上不是“如何在类BadOrderList中隐藏对事务机制的依赖?”,而是“如何将模型与持久化层解耦?”(在这种情况下使用Doctrine2)。我尝试用一些代码来说明我的建议。
class BadOrderEntry
// Bad - is too bad word to describe an order here. Why is it bad? Is it Declined? Cancelled?
{
   private $order;
   // some code
}
class BadOrderEntryFactory 
{ 
   // If there is not to much processing to build BadOrderEntry better use factory method like BadOrderEntry::fromOrder($order); 
}
class BadOrderEntryRepository 
{ 
   // here is some read model 
}
class BadOrderEntryManager  
// ITS a part of our model and shouldn't be coupled to ORM
{
  public function save(BadEntry $be) 
  {
    // some model events, model actions
    $this->doSave($be); // here we should hide our storage manipulation
  }

  protected function doSave($be) // it can be abstract, but may contain some basic storage actions  
  { 
  }

  // similar code for delete/remove and other model code
}
class ORMBadOrderEntryManager extends BadOrderEntryManager 
// IT'S NOT the part of your model. There is no business logic. There is only persistent logic and transaction manipulation
{ 
  protected $entityManager;

  // some constructor to inject doctrine entitymanager

  protected doSave($be)
  {
    $em = $this->entityManager;
    $em->getConnection()->beginTransaction(); // suspend auto-commit
    try {
      $em->persist($be);
      $em->flush();
      $em->getConnection()->commit();
    } catch (Exception $e) {
      $em->getConnection()->rollback();
      throw $e;
    }
  }
}
// You can also implement ODMBadOrderEntryManager, MemcacheBadOrderEntryManager etc.

如果我们谈论目录结构,所有的模型都可以移出捆绑包,并在任何地方使用。您的捆绑包结构将如下所示:

BadEntryBundle
|
+ Entity
| |
| --- BadOrderEntryEntity.php
|
+ ORM
| |
| --- ORMBadOrderEntryManager.php 

然后,您只需将ORMBadOrderEntryManager注入到您的BadOrderEntryList中即可。


这真是一个很棒的解决方案!我在JMSPaymentCoreBundle中看到过类似的解决方案。但是我没有想到过...你开了我的眼界!非常感谢你! - stalxed

1
你可以将你的类转换为服务,并在内部注入服务容器后随意调用它。关于依赖注入,你可以在这里找到更多信息:
$injectedContainerOfService->get("id_of_your_service")

谢谢。这是一个非常简单和显而易见的解决方案。但它不是实用的/可测试的/可维护的解决方案。 - Ziumin
这个内容是关于编程的,以下是翻译内容:它很实用且可测试,但为此,您应该解耦控制器并将其转换为服务进行测试。 - lsroudi
你在哪里看到控制器? - Ziumin
为什么它不可测试?我想说,如果你的类,比如控制器或其他类,不可测试,那可能是一个设计问题。 - lsroudi

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