Wie füge ich ein Repository in einen Dienst in Symfony ein?

78

Ich muss zwei Objekte injizieren ImageService. Eine davon ist eine Instanz Repository/ImageRepository, die ich so bekomme:

$image_repository = $container->get('doctrine.odm.mongodb')
    ->getRepository('MycompanyMainBundle:Image');

Wie deklariere ich das in meiner services.yml? Hier ist der Service:

namespace Mycompany\MainBundle\Service\Image;

use Doctrine\ODM\MongoDB\DocumentRepository;

class ImageManager {
    private $manipulator;
    private $repository;

    public function __construct(ImageManipulatorInterface $manipulator, DocumentRepository $repository) {
        $this->manipulator = $manipulator;
        $this->repository = $repository;
    }

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

    public function createThumbnail(ImageInterface $image) {
        return $this->manipulator->resize($image->source(), 300, 200);
    }
}
ChocoDeveloper
quelle
Werfen
simshaun
@simshaun Danke, das hat mir geholfen, herauszufinden, wie es in yml geht: symfony.com/doc/master/components/dependency_injection/…
ChocoDeveloper

Antworten:

105

Hier ist eine bereinigte Lösung für diejenigen, die wie ich von Google kommen:

Update: Hier ist die Symfony 2.6 (und höher) Lösung:

services:

    myrepository:
        class: Doctrine\ORM\EntityRepository
        factory: ["@doctrine.orm.entity_manager", getRepository]
        arguments:
            - MyBundle\Entity\MyClass

    myservice:
        class: MyBundle\Service\MyService
        arguments:
            - "@myrepository"

Veraltete Lösung (Symfony 2.5 und weniger):

services:

    myrepository:
        class: Doctrine\ORM\EntityRepository
        factory_service: doctrine.orm.entity_manager
        factory_method: getRepository
        arguments:
            - MyBundle\Entity\MyClass

    myservice:
        class: MyBundle\Service\MyService
        arguments:
            - "@myrepository"
Matthieu Napoli
quelle
2
Während Sie MongoDB verwenden, verwenden Sie doctrine.odm.mongodb.document_managerals factory_service
Pratyush
Dies funktioniert fantastisch, macht aber alle Repositorys, die Sie auf diese Weise hinzufügen, über Controller mit zugänglich $this->get('myrepository'). Gibt es eine Möglichkeit, das Repository als Argument zu definieren / zu übergeben, myserviceohne es als Service selbst definieren zu müssen?
Andy
1
@Andy Sie können Dienste definieren als private, was bedeutet, dass sie injiziert werden können (in der YAML-Konfiguration), aber nicht abgerufen werden mit->get()
Matthieu Napoli
2
DEPRECATION WARNUNG: Nicht mehr factory_serviceund factory_methodseit Symfony 2.6 . So sollte es jetzt gemacht werden: stackoverflow.com/a/31807608/828366
Francesco Casula
1
Beachten Sie, dass Sie seit Symfony 3.0 für einige YAML-Konfigurationen Anführungszeichen verwenden sollten . Hier sollten Sie also verwenden factory: ["@doctrine.orm.entity_manager", getRepository], sonst werden Sie von einer hübschen ParseException begrüßt.
Czechnology
45

Ich habe diesen Link gefunden und das hat bei mir funktioniert:

parameters:
    image_repository.class:            Mycompany\MainBundle\Repository\ImageRepository
    image_repository.factory_argument: 'MycompanyMainBundle:Image'
    image_manager.class:               Mycompany\MainBundle\Service\Image\ImageManager
    image_manipulator.class:           Mycompany\MainBundle\Service\Image\ImageManipulator

services:
    image_manager:
        class: %image_manager.class%
        arguments:
          - @image_manipulator
          - @image_repository

    image_repository:
        class:           %image_repository.class%
        factory_service: doctrine.odm.mongodb
        factory_method:  getRepository
        arguments:
            - %image_repository.factory_argument%

    image_manipulator:
        class: %image_manipulator.class%
ChocoDeveloper
quelle
6
DEPRECATION WARNING : Kein Fabrikservice und keine Fabrikmethode mehr seit Symfony 2.6
Luchaninov,
Es gibt keine Standardfabriken, aber Symfony 3.4 unterstützt eine Möglichkeit, eigene Fabriken zu erstellen.
Dimitrios Desyllas
40

Falls Sie nicht jedes Repository als Service definieren möchten, 2.4können Sie ab der folgenden Version Folgendes tun ( defaultist ein Name des Entitätsmanagers):

@=service('doctrine.orm.default_entity_manager').getRepository('MycompanyMainBundle:Image')
b.b3rn4rd
quelle
3
Wie würde dies in XML-Servicedateien aussehen?
Jonny
1
Dies basiert auf der Ausdruckskomponente: symfony.com/doc/current/book/…
HenningCash
6
Mit Symfony 2.7 konnte ich das Repository mit kürzerer Syntax @=service('doctrine').getRepository('AppBundle:EntityX')
abrufen
Dies ist perfekt übersetzt als "$ this-> get (" Doctrine ") -> getRepository (" AppBundle: EntityX ")" in * Container.php ", liebe diese Verknüpfung!
Thomas Decaux
@Jonny hier ist die XML-Version:<service id="image_manager" class="MyCompany\MainBundle\ImageManager"> <argument type="expression">service('doctrine.orm.default_entity_manager').getRepository('MycompanyMainBundle:Image')</argument> </service>
Emilie
17

Symfony 3.3, 4 und 5 machen dies viel einfacher.

Eine allgemeinere Beschreibung finden Sie in meinem Beitrag Verwendung von Repository mit Doctrine as Service in Symfony .

Für Ihren Code müssen Sie lediglich die Komposition über die Vererbung verwenden - eines der SOLID-Muster.

1. Erstellen Sie ein eigenes Repository ohne direkte Abhängigkeit von Doctrine

<?php

namespace MycompanyMainBundle\Repository;

use Doctrine\ORM\EntityManagerInterface;
use MycompanyMainBundle\Entity\Image;

class ImageRepository
{
    private $repository;

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

    // add desired methods here
    public function findAll()
    {
        return $this->repository->findAll();
    }
}

2. Fügen Sie eine Konfigurationsregistrierung mit PSR-4-basierter Autoregistrierung hinzu

# app/config/services.yml
services:
    _defaults:
        autowire: true

    MycompanyMainBundle\:
        resource: ../../src/MycompanyMainBundle

3. Jetzt können Sie jede Abhängigkeit überall über die Konstruktorinjektion hinzufügen

use MycompanyMainBundle\Repository\ImageRepository;

class ImageService
{
    public function __construct(ImageRepository $imageRepository)
    {
        $this->imageRepository = $imageRepository;
    }
}
Tomáš Votruba
quelle
Ist dies für Symfony 4.1 noch aktuell?
Isengo
Ja, die Konstruktionsinjektionsmechanik sollte nicht auf Symfony 5 umgestellt werden. Welche Probleme haben Sie?
Tomáš Votruba
Ich habe einen Dienst im Dienstordner namens UserManager erstellt und möchte dort mein UsersRepository verwenden. "Klasse UsersRepository erweitert ServiceEntityRepository"
Isengo
Das ist ein anderer Ansatz, gegen den ich mich in der Post ausspreche. Es wird eine riesige Anbietersperre für fast alle Ihre datenbankbezogenen Eingabedienste für Symfony und Doctrine gleichzeitig erstellt. Siehe den Beitrag für mehr
Tomáš Votruba
-1

In meinem Fall basiert die Antwort von @ Tomáš Votruba und diese Frage auf folgende Ansätze:

Adapteransatz

Ohne Vererbung

  1. Erstellen Sie eine generische Adapterklasse:

    namespace AppBundle\Services;
    use Doctrine\ORM\EntityManagerInterface;
    
    class RepositoryServiceAdapter
    {
        private $repository=null;
    
        /**
        * @param EntityManagerInterface the Doctrine entity Manager
        * @param String $entityName The name of the entity that we will retrieve the repository
        */
        public function __construct(EntityManagerInterface $entityManager,$entityName)
        {
            $this->repository=$entityManager->getRepository($entityName)
        }
    
        public function __call($name,$arguments)
        {
          if(empty($arrguments)){ //No arguments has been passed
            $this->repository->$name();
          } else {
            //@todo: figure out how to pass the parameters
            $this->repository->$name(...$argument);
          }
        }
    }
    
  2. Dann für jede Entität Definieren Sie einen Dienst, zum Beispiel in meinem Fall, um einen zu definieren (ich benutze PHP, um Symfony-Dienste zu definieren):

     $container->register('ellakcy.db.contact_email',AppBundle\Services\Adapters\RepositoryServiceAdapter::class)
      ->serArguments([new Reference('doctrine'),AppBundle\Entity\ContactEmail::class]);
    

Mit Vererbung

  1. Gleicher Schritt 1 wie oben erwähnt

  2. Erweitern Sie die RepositoryServiceAdapterKlasse zum Beispiel:

    namespace AppBundle\Service\Adapters;
    
    use Doctrine\ORM\EntityManagerInterface;
    use AppBundle\Entity\ContactEmail;
    
    class ContactEmailRepositoryServiceAdapter extends RepositoryServiceAdapter
    {
      public function __construct(EntityManagerInterface $entityManager)
      {
        parent::__construct($entityManager,ContactEmail::class);
      }
    }
    
  3. Registrierungsservice:

    $container->register('ellakcy.db.contact_email',AppBundle\Services\Adapters\RepositoryServiceAdapter::class)
      ->serArguments([new Reference('doctrine')]);
    

Entweder, wenn Sie eine gute testbare Möglichkeit haben, Ihre Datenbank zu testen, hilft es Ihnen auch beim Verspotten, falls Sie Ihren Dienst einem Unit-Test unterziehen möchten, ohne sich zu viele Gedanken darüber machen zu müssen. Nehmen wir zum Beispiel an, wir haben den folgenden Service:

//Namespace definitions etc etc

class MyDummyService
{
  public function __construct(RepositoryServiceAdapter $adapter)
  {
    //Do stuff
  }
}

Und der RepositoryServiceAdapter passt das folgende Repository an:

//Namespace definitions etc etc

class SomeRepository extends \Doctrine\ORM\EntityRepository
{
   public function search($params)
   {
     //Search Logic
   }
}

Testen

Sie können also das Verhalten der in searchdefinierten Methode leicht verspotten / fest codieren / emulieren, SomeRepositoryindem Sie RepositoryServiceAdapterentweder den Ansatz der Nichtvererbung oder den Ansatz ContactEmailRepositoryServiceAdapterder Vererbung verspotten .

Der Fabrikansatz

Alternativ können Sie folgende Fabrik definieren:

namespace AppBundle\ServiceFactories;

use Doctrine\ORM\EntityManagerInterface;

class RepositoryFactory
{
  /**
  * @param EntityManagerInterface $entityManager The doctrine entity Manager
  * @param String $entityName The name of the entity
  * @return Class
  */
  public static function repositoryAsAService(EntityManagerInterface $entityManager,$entityName)
  {
    return $entityManager->getRepository($entityName);
  }
}

Wechseln Sie dann wie folgt zur Annotation des PHP-Dienstes:

./app/config/services.phpFügen Sie dies in eine Datei ein (für Symfony v3.4 .wird das Stammverzeichnis Ihres Projekts angenommen)

use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$definition = new Definition();

$definition->setAutowired(true)->setAutoconfigured(true)->setPublic(false);

// $this is a reference to the current loader
$this->registerClasses($definition, 'AppBundle\\', '../../src/AppBundle/*', '../../src/AppBundle/{Entity,Repository,Tests,Interfaces,Services/Adapters/RepositoryServiceAdapter.php}');


$definition->addTag('controller.service_arguments');
$this->registerClasses($definition, 'AppBundle\\Controller\\', '../../src/AppBundle/Controller/*');

Und cange die ./app/config/config.yml( .wird die Wurzel deines ptoject angenommen)

imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    #Replace services.yml to services.php
    - { resource: services.php }

#Other Configuration

Dann können Sie den Dienst wie folgt klassifizieren (verwendet aus meinem Beispiel, in dem ich eine Dummy-Entität mit dem Namen verwendet habe Item):

$container->register(ItemRepository::class,ItemRepository::class)
  ->setFactory([new Reference(RepositoryFactory::class),'repositoryAsAService'])
  ->setArguments(['$entityManager'=>new Reference('doctrine.orm.entity_manager'),'$entityName'=>Item::class]);

Als generischer Tipp phpkönnen Sie durch Umschalten auf Service-Annotation problemlos eine erweiterte Service-Konfiguration durchführen. Verwenden Sie für Codefragmente ein spezielles Repository, das ich mit der factoryMethode erstellt habe.

Dimitrios Desyllas
quelle
Können Sie erklären, warum Sie das vorschlagen? Im Vergleich zu einer Rohlösung verlieren Sie die Hilfe zur automatischen Vervollständigung Ihrer IDE - und was gewinnen Sie?
Nico Haase