Doctrine Repositories als Kompositum in Symfony implementieren


Gepostet von Andreas Nölke am

Wie die Doctrine Repositories als Kompositum entkoppelt werden können hat Adam Qauile in seinem Blog Composition over inheritance in doctrine repositories bereits beschrieben. Ich möchte das hier nicht erneut erklären, das hat Adam bereits sehr ausführlich gemacht und auch die Hintergründe erläutert.

Stattdessen erkläre ich hier wie das in Symfony 3.4 und Symfony 4 mit Autowiring sehr einfach implementiert werden kann.

Wie Autowiring genau funktioniert, lest ihr am besten im Symfony Handbuch Defining Services Dependencies Automatically (Autowiring) nach.

Lege das Entity und das Repsoitory an

Lege ein Entity an und konfiguriere es entsprechend. Stelle dabei sicher das du nicht die Repository Klasse konfigurierst.

<?php declare(strict_types=1);

namespace Project\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Item
{
}

Lege das Repository an.

<?php declare(strict_types=1);

namespace Project\Repository;

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;

class ItemRepository
{
    /**
     * @var EntityManager
     */
    private $entityManager;

    /**
     * @var EntityRepository
     */
    private $entityRepository;

    public function __construct(EntityManager $entityManager, EntityRepository $doctrineItemRepository)
    {
        $this->entityManager = $entityManager;
        $this->entityRepository = $doctrineItemRepository;
    }
}

Autowiring konfigurieren

Soweit so gut. So nun geht es an das Autowiring. Hier ist etwas Konfiguration notwendig um das Doctrine Repsitory zu konfigurieren. Dieses wiederum soll dann automatisch in unser erstelltes ItemRepository injected werden. In dem folgenden Beispiel zeige ich wie so eine Yaml Konfiguration aussehen kann.

# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:

services:
  # default configuration for services in *this* file
  _defaults:
    autowire: true      # Automatically injects dependencies in your services.
    autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
    public: false       # Allows optimizing the container by removing unused services; this also means
    # fetching services directly from the container via $container->get() won't work.
    # The best practice is to be explicit about your dependencies anyway.
    bind:
      $entityManager: '@doctrine.orm.entity_manager'

      # Doctrine Repositories
      $doctrineItemRepository: '@repository.doctrine_item_repository'

  # makes classes in src/ available to be used as services
  # this creates a service per class whose id is the fully-qualified class name
  Project\:
    resource: '../src/*'
    exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

  repository.doctrine_item_repository:
    class: Doctrine\ORM\EntityRepository
    factory: ['@doctrine.orm.entity_manager', getRepository]
    arguments:
      - Project\Entity\Item

Durch das binden des erstellten Repositories an die variable $doctrineItemRepository spart man sich das erstellen eines extra services.

Aus meiner Sicht ist das eine sehr gute Implementierung um die Repositories besser zu entkoppeln und die Vererbung aufzulösen. Das schafft mehr Klarheit und vereinfacht unter anderem auch das erstellen von Unittests.

Eine weitere Vereinfachung ist möglich indem ganz auf das Doctrine EntityRepository verzichtet wird, welches über die Factory erstellt wird. So besteht lediglich die Abhängigkeit zum EntityManager. Das kann allerdings den Nachteil haben das bereits existierende Methoden nochmals implementiert werden müssen. Letzten Endes kommt es darauf an, was implementiert wird und wie viel vom EntityRepository benötigt wird.