Symfony - 如何访问实体的存储库

13

在Symfony2的控制器或服务中,我们可以通过几种方式访问实体的存储库,每种方式都有其优点和缺点。首先我在这里列出它们,然后询问是否有更好的解决方案,或者这些是我们唯一的选择,我们应该根据自己的喜好选择其中的一个或几个。我还想知道最近我开始使用的第五种方法是否可行,是否符合规则,是否有任何副作用。

基本方法:在控制器中使用实体管理器或将其注入到服务中,然后访问我想要的任何存储库。这是访问控制器或服务中存储库的基本方式。

class DummyController
{
    public function dummyAction($id)
    {
        $em = $this->getDoctrine()->getManager();
        $em->getRepository('ProductBundle:Product')->loadProduct($id);
    }
}

但是,这种方法存在一些问题。第一个问题是,例如在loadProduct函数上无法使用Ctrl + 单击直接进入其实现(除非有我不知道的方法)。另一个问题是,我最终会反复重复这部分代码。

方法2:另一种方法就是在我的服务或控制器中定义一个getter来访问我的存储库。

class DummyService
{
    protected $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    } 

    public function dummyFunction($id)
    {
        $this->getProductRepository()->loadProduct($id);
    }

    /**
     * @return \ProductBundle\Entity\Repository\ProductRepository
     */
    public function getProductRepository()
    {
        return $this->em->getRepository('ProductBundle:Product');
    }
}
该方法解决了第一个问题,某种程度上也解决了第二个问题,但仍然需要在我的服务或控制器中重复所有需要的getter,而且我将有多个getter在我的服务和控制器中,仅用于访问存储库。
方法3:另一种方法是将存储库注入到我的服务中,这很好,特别是如果我们对代码有很好的控制,并且不与将整个容器注入到您的服务中的其他开发人员一起工作。
class DummyService
{
    protected $productRepository;

    public function __construct(ProductRepository $productRepository)
    {
        $this->productRepository = $productRepository;
    } 

    public function dummyFunction($id)
    {
        $this->productRepository->loadProduct($id);
    }
}

这种方法可以解决第一和第二个问题,但如果我的服务很大并且需要处理很多存储库,那么将10个存储库注入到我的服务中就不是一个好主意。

方法4:另一种方法是创建一个服务来包装所有的存储库,并将这个服务注入到其他服务中。

class DummyService
{
    protected $repositoryService;

    public function __construct(RepositoryService $repositoryService)
    {
        $this->repositoryService = $repositoryService;
    } 

    public function dummyFunction($id)
    {
        $this->repositoryService->getProductRepository()->loadProduct($id);
    }
}

仓库服务:

class RepositoryService
{
    protected $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    } 

    /**
     * @return \ProductBundle\Entity\Repository\ProductRepository
     */
    public function getProductRepository()
    {
        return $this->em->getRepository('ProductBundle:Product');
    }

    /**
     * @return \CmsBundle\Entity\Repository\PageRepository
     */
    public function getPageRepository()
    {
        return $this->em->getRepository('CmsBundle:Page');
    }
}

这种方法也解决了第一个和第二个问题。但是当我们有200个实体时,RepositoryService可能会变得非常大。

方法5:最后,我可以在每个实体中定义一个静态方法来返回其存储库。

class DummyService
{
    protected $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    } 

    public function dummyFunction($id)
    {
        Product::getRepository($this->em)->loadProduct($id);
    }
}

我的实体:

/**
 * Product
 *
 * @ORM\Table(name="saman_product")
 * @ORM\Entity(repositoryClass="ProductBundle\Entity\ProductRepository")
 */
class Product
{
    /**
     *
     * @param \Doctrine\ORM\EntityManagerInterface $em
     * @return \ProductBundle\Entity\ProductRepository
     */
    public static function getRepository(EntityManagerInterface $em)
    {
        return $em->getRepository(__CLASS__);
    }   
}

这种方法解决了第一个和第二个问题,而且我不需要定义服务来访问存储库。我最近使用过它,到目前为止,这是对我最好的方法。我认为这种方法不会违反实体规则,因为它在类范围内定义,并且非常精简。但是我仍然不确定它是否有任何副作用。

3个回答

9
在Doctrine的世界里,实体只是一个getter和setter(以及添加或删除)的无血统模型,因此注入存储库是错误的做法。
这一切取决于您想要与Doctrine耦合的程度。如果您可以接受在各处传递@doctrine服务,则可以使用以下内容:
$this->repository = $doctrine->getRepository('CmsBundle:Page');

但是,如前所述,这将需要您将 @doctrine 服务传递给每个对象。这意味着如果您因任何原因决定不使用Doctrine,则需要重构所有代码以适应新的方法(无论它是什么),但这对您可能不是一个问题。此外,存储库将被类型提示,因此除了在代码中检查其是否为正确的类之外,没有其他保证可以保证它是正确的服务。

在我看来,最干净的方法是创建一个名为:

XML

<service id="cms.page_repository"
    class="Acme\CmsBundle\Repository\PageRepository">
    <factory service="doctrine" method="getRepository" />
    <argument>AcmeDemoBundle:ExampleRepository</argument>
</service>

YAML

cms.page_repository:
    class: Acme\CmsBundle\Repository\PageRepository
    factory: [ @doctrine, 'getRepository' ]

使用这种方法,您可以传递您的存储库服务到任何需要它的地方,而无需在实际代码中使用Doctrine服务。如果您决定远离Doctrine,您只需要更改服务定义,而不需要重构所有内容。此外,由于您正在创建特定存储库的服务,因此您可以在 __construct 中使用类型提示来确保正确的服务被注入,例如:

public function __construct(PageRepository $repository)
{
    $this->repository = $repository;
}

1
我不想将存储库注入到我的实体中,方法5中的函数是一个静态函数,它不在对象和实体范围内。我同意你的观点,最好的方式是定义一个存储库服务,但如果你在一个庞大而混乱的项目中工作,这样做很困难,但如果我要开始一个新项目,我肯定会遵循这种模式。 - Saman
这似乎是你的外部服务做了太多事情的迹象。话虽如此...即使你调用Doctrine并以这种方式获取存储库,你仍然在传递10个服务,只是不太确定它们是什么。 - qooplmao
没有服务仓库,我的服务只有5个注入点,规模相当小,但如果我想注入额外的5个服务作为仓库,那么我就有了10个注入点,这并不好。 - Saman
1
如果您追求服务定义的“美观”,那么您不应该采取这种方法。但是,大型服务定义的问题不仅仅在于它们很大,而且意味着您的服务可能做得太多、知道得太多,将这些依赖关系的解决方案移动到类内部并不能解决问题,只会将问题隐藏在其他地方。 - user2268997
感谢user2268997的建议,我已将第一个示例从服务更改为控制器,因为它会误导问题。问题主要是关于如何访问存储库的。我同意你关于服务设计的看法。 - Saman
显示剩余2条评论

2
对我来说,你的所有建议都不正确。因为我不明白为什么你需要创建一个实体的服务。如果你需要访问这个实体,唯一需要做的就是访问Doctrine。而Doctrine已经有一个服务(@doctrine)。你只需要在构造函数中准备好只访问该实体即可。
静态方法很容易被忘记: 您提交的第五种方法是不正确的,因为您的Product实体已经通过ProductRepository的getEntityManager()方法访问entityManager。

2
我使用带有接口的服务来管理我的代码库。这样可以减少我的代码与Doctrine直接耦合的情况,特别是在不必要的时候。一个服务/对象只需要获取符合接口的存储库,而不是获取Doctrine来获取存储库(这也没有类型检查,因此可能没有任何自定义方法)。 - qooplmao
1
你绝对不需要Doctrine。这就像买一头牛,只为了喝点牛奶。 - user2268997
方法1在Symfony书和食谱中使用,你的意思是它们都不正确吗?我没有为我的实体创建一个服务,ProductService只是一个例子,我已经将其更改为DummyService。你提到的关于静态方法的问题是正确的,但在这种情况下(方法5)是不同的。"你的Product实体已经通过ProductRepository使用getEntityManager()方法访问entityManager",我不知道你具体指的是什么意思。 - Saman

1
我建议您使用第四种方法,因为它符合单一职责原则,只做一件事:为您提供访问所有存储库的权限。此服务将主要作为其他服务的依赖项。对于控制器,我建议您创建一个自定义控制器基类,其中包含相同的辅助函数。
关于代码重复,特征可能是解决方案。即使使用“类别”特征也可以处理大量的方法。

1
谢谢Yassine,我在控制器中也可以使用同样的服务,而不需要拥有基础控制器。 - Saman
如果您更喜欢使用$this->getMyRepository()而不是$this->get('app.repositories')->getMyRepository() :) - Yassine Guedidi
我不需要一个基础控制器。抱歉,我的意思是我不需要在基础控制器中重复同样的辅助函数。 - Saman
啊,好的,我看到的不是代码... 我只看到一个 getMyRepository() 函数来获取服务的代码。 - Yassine Guedidi
所以,我会重写我的第一个评论:为了你在控制器代码中更喜欢$this->getMyRepository()而不是$this->getRepositories()->getMyRepository(),我会重复辅助函数。 :) - Yassine Guedidi
显示剩余4条评论

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