Zend Framework 2 – Doctrine – Service Layer

Im Rahmen meiner Bakk. Arbeit verwende ich das Zend Framework 2 + Doctrine 2 Serverseitig. Doctrine 2 bietet ein angenehmes ORM Mapping mit Annotationen (ähnlich dem von Hibernate in der Java Welt).

Jedoch habe ich in den vorhandenen Tutorials und Beispiel Applikationen nur Varianten gesehen, in denen die Queries an die Datenbank über das Repository von Doctrine 2, direkt im Controller stattgefunden haben. Das ist meiner Meinung nach keine schöne Lösung, da so die Business Logik mit Entities direkt im Controller stattfinden würde.
InstituionController mit Repository


class InstitutionController extends DoctrineReadyActionController
{
  /**
   * Institution list action
   *
   * Fetches and displays all institutions.
   *
   * @return array view variables
   */
  public function indexAction()
  {
      $repository = $this->getEntityManager()->getRepository('Vico\Entity\Institution');
      $institutions = $repository->findAll();
      return array(
        'institutions' => $institutions
      );
  }

}

DoctrineReadyActionController ein Controller der die Basisfunktionalität für den EntityManager zur Verfügung stellt

<?php
class DoctrineReadyActionController extends AbstractActionController {
    /**
     * @var EntityManager
     */
    protected $entityManager;

    /**
     * Sets the EntityManager
     *
     * @param EntityManager $em
     * @access protected
     * @return InstitutionController
     */
    protected function setEntityManager(EntityManager $em)
    {
        $this->entityManager = $em;
        return $this;
    }

    /**
     * Returns the EntityManager
     *
     * Fetches the EntityManager from ServiceLocator if it has not been initiated
     * and then returns it
     *
     * @access protected
     * @return EntityManager
     */
    protected function getEntityManager()
    {
        if (null === $this->entityManager) {
            $this->setEntityManager($this->getServiceLocator()->get('Doctrine\ORM\EntityManager'));
        }
        return $this->entityManager;
    }
}

Um dies zu entkoppeln wollte ich einen Service Layer zwischen Doctrine und den Controllern einziehen um. Um Singletons zu vermeiden (Probleme bei der Testbarkeit, etc.. http://stackoverflow.com/questions/1300655/whats-alternative-to-singleton), wollte ich dies mit Dependency Injection lösen, da die Dependency Injection in Zend 2 auch dafür sorgt, das wenn gewollt nur eine Instanz erzeugt wird. Weiters bietet sie z.B. den Vorteil der globalen Konfiguration usw.
Mein erster Ansatz war es eine generelle Service Klasse anzubieten, um so die Grundfunktionalität zu bieten und dann für jedes Entity zumindest ein Service zu erstellen das einen bequem Zugriff auf die Entities bietet. Anzumerken ist hier das durch Constructor Injection der Entity Manager erstellt bzw. hinzugefuegt wird. (Andere Beispiele findet man z.B. hier https://github.com/ralphschindler/Zend_DI-Examples/)


<?php
class DoctrineReadyService implements ServiceLocatorAwareInterface{

    public function __construct(EntityManager $em){
        $this->entityManager = $em;
    }
    /**
     * @var ServiceLocatorInterface
     */
    protected $serviceLocator;

    /**
     * @var EntityManager
     */
    protected $entityManager;

    /**
     * Retrieve serviceManager instance
     *
     * @return ServiceLocatorInterface
     */
    public function getServiceLocator()
    {
        return $this->serviceLocator;
    }

    /**
     * Set serviceManager instance
     *
     * @param  ServiceLocatorInterface $serviceLocator
     * @return void
     */
    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
    }

    /**
     * Sets the EntityManager
     *
     * @param EntityManager $em
     * @access protected
     * @return DoctrineReadyService
     */
    protected function setEntityManager(EntityManager $em)
    {
        $this->entityManager = $em;
        return $this;
    }

    /**
     * Returns the EntityManager
     *
     * Fetches the EntityManager from ServiceLocator if it has not been initiated
     * and then returns it
     *
     * @access protected
     * @return EntityManager
     */
    protected function getEntityManager()
    {
        return $this->entityManager;
    }

}

Da dies jedoch zu Problemen bei der Konfiguration führte und der Aufruf des Service sehr kompliziert wurde


$di = new Di;
      $di->instanceManager()->addTypePreference('Zend\ServiceManager\ServiceLocatorInterface', new \Zend\ServiceManager\ServiceManager());
      $di->instanceManager()->setParameters('Vico\Service\InstitutionService', array("sm"=>$this->getServiceLocator(), "em"=>$this->getServiceLocator()->get('Doctrine\ORM\EntityManager')));;
      

entschied ich mich den weg über den Service Manager zu gehen. Hierbei ist zu betrachten das in der Modul Klasse (Modul.php) folgenden Änderungen durchzuführen sind:

  • Das Interface Zend\ModuleManager\Feature\ServiceProviderInterface ist zu implementieren
  • eine getServiceConfig() Methode ist anzubieten

Die Klasse Module sieht nun wie folgt aus.


<?php
class Module implements ConfigProviderInterface, ServiceProviderInterface
{
    /**
     * Expected to return \Zend\ServiceManager\Config object or array to
     * seed such an object.
     *
     * @return array|\Zend\ServiceManager\Config
     */
    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                'Vico\Service\InstitutionService' => function ($sm) {
                    $entityManager = $sm->get('Doctrine\ORM\EntityManager');
                    return new Service\InstitutionService($entityManager);
                }
            )
        );
    }

    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }

    public function getAutoloaderConfig()
      {
        return array(
          'Zend\Loader\StandardAutoloader' => array(
            'namespaces' => array(
              __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
              __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
                    'Doctrine' => __DIR__ . '/../../vendor/Doctrine'
            ),
          ),
        );
      }
}

Controller:


<?php
class InstitutionController extends AbstractActionController
{
  /**
   * Institution list action
   *
   * Fetches and displays all institutions.
   *
   * @return array view variables
   */
  public function indexAction()
  {
      $service = $this->getServiceLocator()->get('Vico\Service\InstitutionService');
      $institutions = $service->getAll();
      return array(
          'institutions' => $institutions
      );

  }
}

Das Service von dem alle Services erben:

<?php
class DoctrineReadyService{

    /**
     *
     * @var Doctrine\ORM\EntityManager
     */
    protected $entityManager;

    public function __construct(EntityManager $em) {
        $this->entityManager = $em;
    }

    /**
     * Sets the EntityManager
     *
     * @param EntityManager $em
     * @access protected
     * @return DoctrineReadyService
     */
    protected function setEntityManager(EntityManager $em)
    {
        $this->entityManager = $em;
        return $this;
    }

    /**
     * Returns the EntityManager
     *
     * Fetches the EntityManager from ServiceLocator if it has not been initiated
     * and then returns it
     *
     * @access protected
     * @return EntityManager
     */
    protected function getEntityManager()
    {
        return $this->entityManager;
    }

}

Das eigentliche Service des Entities

class InstitutionService extends DoctrineReadyService {

    public function getAll(){
        $repository = $this->getEntityManager()->getRepository('Vico\Entity\Institution');
        return $repository->findAll();
    }

}

Wie man sieht ist dadurch das Aufrufen sehr der Services sehr schmal geworden und durch den ServiceManager wird immer noch die Singleton Funktionalität geliefert, jedoch bin ich mir momentan noch nicht sicher ob dies die sauberste Lösung ist.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Time limit is exhausted. Please reload CAPTCHA.

Anti-Spam durch WP-SpamShield

Scroll to top