Symfony 2:从Repository创建服务

18

我正在学习Symfony,尝试创建一个使用repository的服务。我已经通过generate:entity创建了我的repositories和entities,所以它们应该是好的。

到目前为止,在我的services.yml中,我得到了:

parameters:
    mytest.entity: TestTestBundle:Brand
    mytest.class:  Test\TestBundle\Entity\Brand
    default_repository.class: Doctrine\ORM\EntityRepository

services:
     myservice:
          class: %default_repository.class%
          factory-service: doctrine.orm.default_entity_manager
          factory-method: getRepository
          arguments:
            - %mytest.entity%

但是当我尝试调用该服务时,出现以下错误:

Catchable Fatal Error: Argument 2 passed to Doctrine\ORM\EntityRepository::__construct() must be an instance of Doctrine\ORM\Mapping\ClassMetadata, none given, called in 

然后我尝试只使用实体来创建服务。我的services.yml看起来像:

services:
     myservice:
          class: %mytest.class%
          factory-service: doctrine.orm.default_entity_manager
          factory-method: getRepository
          arguments:
            - %mytest.entity%

但是,就为了这个,我得到了:

Error: Call to undefined method 
                Test\TestBundle\Entity\Brand::findAll

有人知道我做错了什么吗?

谢谢

7个回答

44

弃用警告: 不再使用factory_servicefactory_method。自Symfony 2.6以来应该这样做(对于Symfony 3.3+,请查看下面):

parameters:
    entity.my_entity: "AppBundle:MyEntity"

services:
    my_entity_repository:
        class: AppBundle\Repository\MyEntityRepository
        factory: ["@doctrine", getRepository]
        arguments:
            - %entity.my_entity%

新的setFactory()方法在Symfony2.6中引入。请参考旧版本以获取2.6之前工厂的语法。

http://symfony.com/doc/2.7/service_container/factories.html

编辑:看起来他们一直在更改这个,所以从Symfony 3.3开始有了新的语法:

# app/config/services.yml
services:
    # ...

    AppBundle\Email\NewsletterManager:
        # call the static method
        factory: ['AppBundle\Email\NewsletterManagerStaticFactory', createNewsletterManager]

看一下这个链接:http://symfony.com/doc/3.3/service_container/factories.html


这个很好用,谢谢。但是我想更进一步:如果我还想在这个仓库中注入一个服务(比如说一个Paginator服务),该怎么办呢?我试着添加更多的参数,但是不起作用,因为Symfony并没有“期望”我提供的任何参数。 - userfuser
@userfuser 你尝试过重写构造函数吗?你可以在那里添加所有新的依赖项,只需记得调用 parent::__construct($arg) 来提供Doctrine所期望的内容。 - Francesco Casula
@FrancescoCasula 我考虑过这个问题,但是我还没有准备好将其作为所有存储库的全局更改,因为只有少数情况需要进行此注入。最终,我使用设置器注入来针对此特定情况进行操作,就像文档中所定义的那样:http://symfony.com/doc/current/service_container/injection_types.html#setter-injection - userfuser

24

以下是我们在KnpRadBundle中的实现方式:https://github.com/KnpLabs/KnpRadBundle/blob/develop/DependencyInjection/Definition/DoctrineRepositoryFactory.php#L9

最终结果应该是:

my_service:
    class: Doctrine\Common\Persistence\ObjectRepository
    factory_service: doctrine # this is an instance of Registry
    factory_method: getRepository
    arguments: [ %mytest.entity% ]

更新

自2.4版本以来,Doctrine允许覆盖默认的存储库工厂。

以下是在Symfony中实现它的一种可能方式:https://gist.github.com/docteurklein/9778800


如果要实例化Doctrine\Common\Persistence\ObjectRepository,应该怎么做?工厂服务:doctrine # 这是Registry的一个实例 你的意思是它是doctrine.orm.default_entity_manager吗? - Strnm
不,这是 https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Persistence/ManagerRegistry.php 的实现。您可以使用 EntityManager 实例完成相同的操作。实际上,“class:” 在这里并不重要,因为它是一个工厂服务,容器本身不会实例化给定的类,而是请求服务的方法来完成实例化。 - Florian Klein
谢谢Florian。不,它没有,或者我做错了什么,但正如我所说:FatalErrorException:错误:无法实例化Doctrine \ Common \ Persistence \ ObjectRepository接口。 - Strnm
你能否粘贴你的服务转储结果(位于 app/cache/dev/appDevDebugProjectContainer.php)?该方法应命名为:getMyserviceService(根据你的示例)。 - Florian Klein
5
哈哈!我懂了 :) 应该是 factory_service 而不是 factory-service。注意下划线 _ - Florian Klein
显示剩余4条评论

10

您可能使用了错误的YAML键。对我来说,您的第一项配置可以正常工作,只需使用

  • factory_service而不是factory-service
  • factory_method而不是factory-method

这对我非常有用,谢谢。我还发现了一些相关的笔记,发布于2012年12月 - 我会在这里留下链接,以防其他人也在寻找类似的东西。 - Ivan Kolmychek

2
自2017年和Symfony 3.3+版本以来,这变得更加容易了。
注意:尽量避免使用像generate:entity这样的通用命令。它们是为初学者设计的,可以快速使项目运行起来。它们往往会带来不良实践,并且需要很长时间才能改变。
请查看我的文章如何在Symfony中将Repository与Doctrine作为服务一起使用以获取更一般的描述。
对于您的代码:
1. 更新您的配置注册以使用基于PSR-4的自动注册
# app/config/services.yml
services:
    _defaults:
        autowire: true

    Test\TestBundle\:
        resource: ../../src/Test/TestBundle

2. 组合优于继承 - 创建自己的仓库,而不直接依赖Doctrine

<?php

namespace Test\TestBundle\Repository;

use Doctrine\ORM\EntityManagerInterface;

class BrandRepository
{
    private $repository;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->repository = $entityManager->getRepository(Brand::class);
    }

    public function findAll()
    {
        return $this->repository->findAll();
    }
}

3. 通过构造函数注入在任何服务或控制器中使用

use Test\TestBundle\Repository\BrandRepository;

class MyController
{
    /**
     * @var BrandRepository
     */
    private $brandRepository;

    public function __construct(BrandRepository $brandRepository)
    {
        $this->brandRepository = $brandRepository;
    }

    public function someAction()
    {
        $allBrands = $this->brandRepository->findAll();
        // ...
    }

}

这确实用自动装配实例化了一个服务,但是该服务并不是存储库本身,这并没有解决问题。我认为通过将服务重命名为Test\TestBundle\Entity\Brand,删除classfactory_servicefactory_method配置,并添加factory:'Doctrine\ORM\EntityManagerInterface:getRepository',可能会使原始配置能够使用自动装配。也许需要对参数进行引号包装。我目前正在进行类似配置的项目重构,因此尚不能确定,但至少我成功清除了缓存。 - iisisrael
1
还有一件事 - 我还在定义Doctrine\ORM\EntityManagerInterface:{alias: doctrine.orm.default_entity_manager} - iisisrael

0

Symfony 3.3doctrine-bundle 1.8 中有一个 Doctrine\Bundle\DoctrineBundle\Repository\ContainerRepositoryFactory 可以帮助我们将 repository 作为服务进行创建。

示例

我们想要什么

$rep = $kernel->getContainer()
    ->get('doctrine.orm.entity_manager')
    ->getRepository(Brand::class);

ORM描述

# Brand.orm.yaml
...
repositoryClass: App\Repository\BrandRepository
...

服务描述

# service.yaml

App\Repository\BrandRepository:
    arguments:
      - '@doctrine.orm.entity_manager'
      - '@=service("doctrine.orm.entity_manager").getClassMetadata("App\\Entity\\Brand")'
    tags:
        - { name: doctrine.repository_service }
    calls:
        - method: setDefaultLocale
          arguments:
              - '%kernel.default_locale%'
        - method: setRequestStack
          arguments:
              - '@request_stack'

0
我将service.yml转换为service.xml,并更新了DependencyInjection Extension,一切都正常工作。我不知道为什么,但yml配置会抛出Catchable Fatal Error。您可以尝试使用xml配置来进行服务配置。

service.yml:

services:
    acme.demo.apikey_userprovider:
        class: Acme\DemoBundle\Entity\UserinfoRepository
        factory-service: doctrine.orm.entity_manager
        factory-method: getRepository
        arguments: [ AcmeDemoBundle:Userinfo ]

    acme.demo.apikey_authenticator:
        class: Acme\DemoBundle\Security\ApiKeyAuthenticator
        arguments: [ "@acme.demo.apikey_userprovider" ]

service.xml:

<services>
    <service id="acme.demo.apikey_userprovider" class="Acme\DemoBundle\Entity\UserinfoRepository"  factory-service="doctrine.orm.entity_manager" factory-method="getRepository">
        <argument>AcmeDemoBundle:Userinfo</argument>
    </service>

    <service id="acme.demo.apikey_authenticator" class="Acme\DemoBundle\Security\ApiKeyAuthenticator">
        <argument type="service" id="acme.demo.apikey_userprovider" />
    </service>
</services>

1
你的yml键名有误,factory-service 应该改成 factory_service (下划线)。 - Eddie Jaoude

-2

sf 2.6+

parameters:
    mytest.entity: TestTestBundle:Brand
    mytest.class:  Test\TestBundle\Entity\Brand
    default_repository.class: Doctrine\ORM\EntityRepository

services:
     myservice:
          class: %default_repository.class%
          factory: ["@doctrine.orm.default_entity_manager", "getRepository"]
          arguments:
            - %mytest.entity%

欢迎来到SO。我认为如果您能添加一些解释说明为什么这个方法可以解决问题,那将会受到赞赏。 - René Vogt
Symfony\Component\DependencyInjection\Definition::setFactoryMethod(getRepository)自2.6版本起已被弃用,并将在3.0中删除。请使用Definition::setFactory()。 - user3665895

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