Alastor
Alastor

Reputation: 357

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?

Upvotes: 10

Views: 30463

Answers (8)

con
con

Reputation: 2410

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'

Upvotes: 4

DonCallisto
DonCallisto

Reputation: 29922

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.

Upvotes: 4

Piers
Piers

Reputation: 41

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  

Upvotes: 4

Touki
Touki

Reputation: 7525

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

Upvotes: 14

Itako
Itako

Reputation: 2069

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.

Upvotes: 7

Alastor
Alastor

Reputation: 357

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.

Upvotes: 7

S3Mi
S3Mi

Reputation: 488

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']

Upvotes: 0

Laurynas Mališauskas
Laurynas Mališauskas

Reputation: 1919

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

Upvotes: 0

Related Questions