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


我们可以通过多种方式访问​​Symfony2控制器或服务中的实体存储库,这些控制器或服务各有优缺点。首先我在这里列出它们然后询问是否有更好的解决方案或者这些是我们唯一的选择,我们应该根据我们的偏好选择一个或一些。我还想知道方法5(我最近开始使用它)是否良好并且不会破坏任何规则或有任何副作用。

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

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

但是这个方法存在一些问题。第一个问题是我无法按Ctrl +单击例如loadProduct函数并直接进入其实现(除非有一种我不知道的方法)。另一个问题是我将一遍又一遍地重复这部分代码。

方法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: 另一种方法是为我的服务注入一个存储库,这很好,特别是如果我们对我们的代码有一个很好的控制,并且我们没有参与将整个Container注入您的服务的其他开发人员。

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

RepositoryService:

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

这个方法解决了第一个和第二个问题,我也不需要定义一个 服务访问存储库。我最近用过它,到目前为止它是我最好的方法。我不认为这种方法会破坏实体的规则,因为它是在类范围中定义的,也是如此。但我仍然不确定它是否有任何副作用。


7258
2017-08-12 03:55


起源



答案:


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

8
2017-08-12 09:25



如果您的项目太乱,无法创建和注入服务,那么您遇到的问题远远超过最佳实践。 - qooplmao
我不想将存储库注入我的实体,方法5中的函数是静态函数,它不在对象和实体范围内。我同意你的看法,最好的方法是定义一个存储库服务,但是如果你在一个大而混乱的项目中工作很难做到这一点,但是如果我想要一个新工作,我肯定会遵循这个模式。 - Saman Shafigh
例如,我的一个服务需要访问5个存储库+另外5个服务,然后我的服务中有10个服务注入。我不认为这是个好主意。 - Saman Shafigh
这似乎表明你的外部服务做得太多了。话虽这么说......即使你正在调用学说并以这种方式获取存储库,你仍然会传递10个服务,但你不太确定它们是什么。 - qooplmao
如果你的服务定义的“好看”是你想要的,那么你不应该采取这种方法。但是大服务定义的问题不在于它们只是大,这意味着你的服务可能做得太多而且知道得太多,并且在你的课程中移动这些依赖项的解决方案并不能解决问题,它'我只是把它隐藏在其他地方。 - user2268997


答案:


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

8
2017-08-12 09:25



如果您的项目太乱,无法创建和注入服务,那么您遇到的问题远远超过最佳实践。 - qooplmao
我不想将存储库注入我的实体,方法5中的函数是静态函数,它不在对象和实体范围内。我同意你的看法,最好的方法是定义一个存储库服务,但是如果你在一个大而混乱的项目中工作很难做到这一点,但是如果我想要一个新工作,我肯定会遵循这个模式。 - Saman Shafigh
例如,我的一个服务需要访问5个存储库+另外5个服务,然后我的服务中有10个服务注入。我不认为这是个好主意。 - Saman Shafigh
这似乎表明你的外部服务做得太多了。话虽这么说......即使你正在调用学说并以这种方式获取存储库,你仍然会传递10个服务,但你不太确定它们是什么。 - qooplmao
如果你的服务定义的“好看”是你想要的,那么你不应该采取这种方法。但是大服务定义的问题不在于它们只是大,这意味着你的服务可能做得太多而且知道得太多,并且在你的课程中移动这些依赖项的解决方案并不能解决问题,它'我只是把它隐藏在其他地方。 - user2268997


对我来说,你的建议都不正确。
因为我不明白为什么你需要创建你的实体服务。
如果您需要访问此实体,您唯一需要的是访问学说。
而且学说有服务(@doctrine)。
您可以在构造中准备只能访问该实体。

静态遗忘:

而您在方法5中提交的内容不正确,您的Product实体已经通过ProductRepository使用getEntityManager()方法访问entityManager。


2
2017-08-12 07:03



我使用带有接口的存储库服务。当没有必要时,它会减少我的代码与Doctrine直接耦合的数量。所有服务/对象需要的是它获得一个适合接口的存储库,而不是获取Doctrine,获取存储库(也没有进行类型检查,因此可能没有任何自定义方法)。 - qooplmao
你绝对不需要“教条”。当你需要的只是一些牛奶时,就像得到一头牛。 - user2268997
你是对的@ user2268997 - Roukmoute
方法1用于Symfony书籍和食谱,你的意思是不正确的。我没有为我的Entity创建服务,ProductService只是一个例子,我已将其更改为DummyService。您提到的静态方法的问题是正确的,但在这种情况下它是不同的(方法5)。 “您的产品实体已经通过ProductRepository使用getEntityManager()方法访问entityManager”我不知道您对这句话的确切含义。 - Saman Shafigh


我建议您使用方法4,您的服务将遵循Single Resposability Principe,因为它只做一件事:让您访问所有存储库。

该服务主要作为其他服务的依赖。对于控制器,我建议您使用相同的辅助函数创建自定义控制器基类。

关于代码重复,特征可能是解决方案。即使你使用“类别”特征的方法数量


1
2017-08-12 22:40



谢谢Yassine,在我的控制器中我也可以使用相同的服务,我不需要有一个基本控制器。 - Saman Shafigh
以防万一你更喜欢 $this->getMyRepository() 过度 $this->get('app.repositories')->getMyRepository() :) - Yassine Guedidi
class baseController {public function getRepositories(){return $ this-> get('app.repositories'); class myController extends baseController {public function dummyAction(){$ this-> getRepositories() - > getProductRepo() - > loadProduct()}} - Saman Shafigh
我的意思是你的控制器代码。你的代码错了, $this->get('app.repositories') 将返回服务,而不是存储库。 - Yassine Guedidi
我知道:)如果你看看我上面的代码,我在我的baseController(getRepositories)中有一个getter,它返回我的存储库服务,它聚合了对其他存储库的访问。 - Saman Shafigh