简体   繁体   中英

Symfony2: How to access to service from repository

I have class ModelsRepository:

class ModelsRepository extends EntityRepository
{}

And service

container_data:
 class:        ProjectName\MyBundle\Common\Container
 arguments:    [@service_container]

I want get access from ModelsRepository to service container_data. I can't transmit service from controller used constructor.

Do you know how to do it?

IMHO, this shouldn't be needed since you may easily break rules like SRP and Law of Demeter

But if you really need it, here's a way to do this:

First, we define a base "ContainerAwareRepository" class which has a call "setContainer"

services.yml

services:
    # This is the base class for any repository which need to access container
    acme_bundle.repository.container_aware:
        class: AcmeBundle\Repository\ContainerAwareRepository
        abstract: true
        calls:
            - [ setContainer, [ @service_container ] ]

The ContainerAwareRepository may looks like this

AcmeBundle\\Repository\\ContainerAwareRepository.php

abstract class ContainerAwareRepository extends EntityRepository
{
    protected $container;

    public function setContainer(ContainerInterface $container)
    {
        $this->container = $container;
    }
}

Then, we can define our Model Repository.
We use here, the doctrine's getRepository method in order to construct our repository

services.yml

services:
    acme_bundle.models.repository:
        class: AcmeBundle\Repository\ModelsRepository
        factory_service: doctrine.orm.entity_manager
        factory_method:  getRepository
        arguments:
            - "AcmeBundle:Models"
        parent:
            acme_bundle.repository.container_aware

And then, just define the class

AcmeBundle\\Repository\\ModelsRepository.php

class ModelsRepository extends ContainerAwareRepository
{
    public function findFoo()
    {
        $this->container->get('fooservice');
    }
}

In order to use the repository, you absolutely need to call it from the service first.

$container->get('acme_bundle.models.repository')->findFoo(); // No errors
$em->getRepository('AcmeBundle:Models')->findFoo(); // No errors

But if you directly do

$em->getRepository('AcmeBundle:Models')->findFoo(); // Fatal error, container is undefined

You should never pass container to the repository, just as you should never let entities handle heavy logic. Repositories have only one purpose - retrieving data from the database. Nothing more (read: http://docs.doctrine-project.org/en/2.0.x/reference/working-with-objects.html ).

If you need anything more complex than that, you should probably create a separate (container aware if you wish) service for that.

I tried some versions. Problem was solved follows

ModelRepository:

class ModelRepository extends EntityRepository
{
    private $container;

    function __construct($container, $em) {
        $class = new ClassMetadata('ProjectName\MyBundle\Entity\ModelEntity');
        $this->container = $container;

        parent::__construct($em, $class);
    }
}

security.yml:

providers:
    default:
        id: model_auth

services.yml

model_auth:
    class: ProjectName\MyBundle\Repository\ModelRepository
    argument

As a result I got repository with ability use container - as required. But this realization can be used only in critical cases, because she has limitations for Repository. Thx 4all.

Are you sure that is a good idea to access service from repo?

Repositories are designed for custom SQL where, in case of doctrine, doctrine can help you with find() , findOne() , findBy() , [...] "magic" methods.

Take into account to inject your service where you use your repo and, if you need some parameters, pass it directly to repo's method.

I would suggest using a factory service:

http://symfony.com/doc/current/components/dependency_injection/factories.html

//Repository
class ModelsRepositoryFactory
{
    public static function getRepository($entityManager,$entityName,$fooservice)
    {
        $em     = $entityManager;
        $meta   = $em->getClassMetadata($entityName);

        $repository = new ModelsRepository($em, $meta, $fooservice);
        return $repository;
    }
}

//service
AcmeBundle.ModelsRepository:
        class: Doctrine\ORM\EntityRepository
        factory: [AcmeBundle\Repositories\ModelsRepositoryFactory,getRepository]
        arguments:
            - @doctrine.orm.entity_manager
            - AcmeBundle\Entity\Models
            - @fooservice  

I strongly agree that this should only be done when absolutely necessary. Though there is a quite simpler approach possible now (tested with Symfony 2.8).

  1. Implement in your repository "ContainerAwareInterface"
  2. Use the "ContainerAwareTrait"
  3. adjust the services.yml

RepositoryClass:

namespace AcmeBundle\Repository;

use Doctrine\ORM\EntityRepository;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use AcmeBundle\Entity\User;

class UserRepository extends EntityRepository implements ContainerAwareInterface
{

    use ContainerAwareTrait;

    public function findUserBySomething($param)
    {
        $service = $this->container->get('my.other.service');
    }

}

services.yml:

acme_bundle.repository.user:
    lazy: true
    class: AcmeBundle\Repository\UserRepository
    factory: ['@doctrine.orm.entity_manager', getRepository]
    arguments:
        - "AcmeBundle:Entity/User"
    calls:
        - method: setContainer
          arguments:
            - '@service_container'

Extending Laurynas Mališauskas answer, to pass service to a constructor make your repository a service too and pass it with arguments:

models.repository:
      class: ModelsRepository
      arguments: ['@service_you_want_to_pass']

the easiest way is to inject the service into repository constructor.

class ModelsRepository extends EntityRepository
{
  private $your_service;

  public function __construct(ProjectName\MyBundle\Common\Container $service) {
    $this->your_service = $service;
  } 
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM