JavaScript Module – Kommunikation mit dem Server

Nachdem ich letztens auf Serverseitig validert habe, baue ich heute ein allgemeines Modul um mit den Server zu kommunizieren und bei einen Standard Bootstrap Formular Fehler anzuzeigen.

Dazu erstelle ich mir ein JavaScript Modul:



var standardFormHandler = (function(window, document, $){
  
    var module = {};
    module.init = function(options){
        $.extend(settings, options);
    }
    return module;
}(window, document, jQuery));

Prinzipiell soll das Modul ein Entity erstellen und updaten können, löschen ist vorerst nicht angedacht.
Es werden durch die Settings die Felder, das Formular und die Ziel URL festgelegt. Weiters gibt es ein Array mit vorgefertigten Fehlermeldungen.
Die Methoden new und update greifen beide auf die _sendToServer Methode zu, die den eigentlich Ajax-Call darstellt. Es wird eine Default success und fail Funktion angeboten, die aufgerufen wird sollten success und fail nicht übergeben werden.
Weiters baut der Request auf die Methoden done() und fail() auf und nicht mehr auf die seit JQuery 1.8 als @deprecated markierten Felder success und error. Außerdem wird durch das Formular vorher von alten Fehlermeldungen bereinigt.
Der Standard-Success-Handler, liest alle Fehlermeldungen aus und schreibt sie in Felder mit den CSS-Klassen .help-inline oder .help-block. Die Fehlermeldungen liegen in einen gewissen Format vor das ich in meinen letzten Blog Eintrag vorgestellt habe.


/**
 * Standard Form Handler
 * Bietet funktionalitaeten fuer das Erstellen bzw Updaten von Entities in der Datenbank
 * in Kombination mit den Bootstrap Formularen
 * Der Handler muss zuerst initialisiert werden bevor er verwendet werden kann
 * @type {*}
 */
var standardFormHandler = (function(window, document, $){
    /**
     * Zeigt an ob die Initialisierung bereits stattgefunden hat
     * @type {Boolean}
     */
    var initialized = false;
    var errormessages = {
        NOT_INITIALIZED: "module must be initialized",
        REPSONSE_NOT_WELL_FORMED: "response was not well formed",
        SETTINGS_NOT_WELL_FORMED: "settings are not well formed"
    }
    var settings = {
        serverUrl: "",
        formfield: "",
        varstocheck: {}
    };

    var module = {};

    /**
     * Initialisiert den Handler
     * Es muessen alle Felder belegt werden
     * @param options
     */
    module.init = function(options){
        initialized = true;
        $.extend(settings, options);
        JSLogger.log(settings);
        if(settings.serverUrl === "" || typeof settings.serverUrl === 'undefined' || settings.serverUrl === null){
            JSLogger.log("serverUrl " + settings.serverUrl);
            throw Error(errormessages.SETTINGS_NOT_WELL_FORMED);
        }

        if(settings.formfield === "" || typeof settings.formfield === 'undefined' || settings.formfield === null){
            JSLogger.log("formfield " + settings.formfield);
            throw Error(errormessages.SETTINGS_NOT_WELL_FORMED);
        }

        if(settings.varstocheck === "" || typeof settings.varstocheck === 'undefined' || settings.varstocheck === null){
            JSLogger.log("varstocheck " + settings.varstocheck);
            throw Error(errormessages.SETTINGS_NOT_WELL_FORMED);
        }
    };

    module.new = function(data, success, fail){
        _sendToServer("PUT","JSON", data, success, fail);
    };

    module.update = function(data, success, fail){
        //daten absenden
        _sendToServer("POST", "JSON", data, success, fail);
    };

    /**
     * Sendet die Daten zum Server (Wrapper fuer den Standard AjaxRequest)
     * @param type
     * @param dataType
     * @param data
     * @param done
     * @param fail
     * @private
     */
    function _sendToServer(type, dataType, datatoSend, done, fail){
        if(initialized === false){
            throw Error(errormessages.NOT_INITIALIZED);
            return;
        }
        //parameter gegeben?
        if(typeof done === 'undefined' || done=== null){
            done = _standardSuccessHandler; //binde standardhandler auf das objekt
        }
        if(typeof fail === 'undefined' || fail=== null){
            fail = _standardSuccessHandler; //binde standardhandler auf das objekt
        }
        //alte fehlermeldungen entfernen
        _cleanUpForm();
        var ajaxRequest = $.ajax({
            url: settings.serverUrl,
            type: type,
            data: datatoSend,
            dataType: dataType
        });
        //done und fail anstatt success und error, da diese ab 1.8.3 deprecated sind
        ajaxRequest.done(done); //install success handler
        ajaxRequest.fail(fail); //install fail handler
    };

    /**
     * Standard Success Handler
     * @param data
     * @param textStatus
     * @param jqXHR
     * @private
     */
    function _standardSuccessHandler(data, textStatus, jqXHR){
        if(data.hasOwnProperty("status") && data.status === "error"){
            if(data.hasOwnProperty("messages")){
                var messages = data.messages;
                $.each(settings.varstocheck, function(index, value){
                    //durch felder iterieren und fehler suchen
                    if(messages.hasOwnProperty(index)){
                        $('#control-group-'+value).addClass("error"); // fehler klasse hinzufuegen
                        $.each(messages[index], function(count, message){ // durch fehler iterieren
                            $('#control-group-' + value).children(".help-inline, .help-block").html("Bitte etwas eintragen!");
                        });
                    };
                });
            } else {
                throw Error(errormessages.REPSONSE_NOT_WELL_FORMED);
            }
        }
    };

    /**
     * Standard Fail/Error Handler
     * Fehler werden in die Konsole geloggt
     * Es gibt nur einen alert als Fehler zurück
     * @param jqXHR
     * @param textStatus
     * @param errorThrown
     * @private
     */
    function _standardErrorHandler(jqXHR, textStatus, errorThrown){
        JSLogger.log("Error Hanlder invoked");
        JSLogger.log(jqXHR);
        JSLogger.log(textStatus);
        JSLogger.log(errorThrown);
        alert("Fehler beim Speichern!");
    };

    /**
     * Entfernt alle Fehlermeldung in den Formular
     * @private
     */
    function _cleanUpForm(){
        $('.control-group', settings.formfield).each(function(){
            $(this).removeClass("error");
            $(this).children(".help-inline, .help-block").html("");
        });
    }

    return module;
}(window, document, jQuery));

Nun muss der Standardhandler nur noch für die jeweilige View angepasst bzw. initialisiert werden, so wie z.B. hier:


var patStammView = (function(window, document, $){
    var module = {};
    var settings = {
        varstocheck: {  "nr":"nr", "vorname":"name", "nachname":"name", "anrede":"name", "ort":"plz", "plz":"plz", "strasse":"strasse",
                        "gebDatum":"gebDatum", "versicherungsnr":"versichnr", "versicherungsanstalt":"versichnr", "telnr":"tel","email":"email"
                      }
    };

    module.init = function(options){
        $.extend(settings, options);

        standardFormHandler.init({serverUrl: $('#baseUrl').val(), formfield: '#patient-form', varstocheck: settings.varstocheck});
    };

    module.save = function(data){
        standardFormHandler.new(_getJSONFromForm());
    };

    return module;

    /**
     * serialisiert das patienten formular zu einen json objekt
     * @private
     * @return {{nr: (*|jQuery), anrede: (*|jQuery), vorname: (*|jQuery), nachname: (*|jQuery), ort: (*|jQuery), plz: (*|jQuery), strasse: (*|jQuery), gebDatum: (*|jQuery), versicherungsnr: (*|jQuery), versicherungsanstalt: (*|jQuery), telnr: (*|jQuery), email: (*|jQuery)}}
     */
    function _getJSONFromForm(){
        var json = { patstamm : {
            nr: $('#patnr').val(),
            anrede: $('#patanrede').val(),
            vorname: $('#patvorname').val(),
            nachname: $('#patnachname').val(),
            ort: $('#patort').val(),
            plz: $('#patplz').val(),
            strasse: $('#patstrasse').val(),
            gebDatum: $('#patgebDatum').val(),
            versicherungsnr: $('#patversicherungsnr').val(),
            versicherungsanstalt: $('#patversicherungsanstalt').val(),
            telnr: $('#pattelnr').val(),
            email: $('#patemail').val()
        }
        };
        return json;
    }

}(window, document, jQuery));
</script>

siehe auch:

JSON Validatoren und JQuery

Heute habe ich versucht ein per JSON geliefertes Objekt in einen Controller abzufangen und zu validieren. Zuerst wollte ich dies mit der von Zend angebotenen Klasse AbstractValidator lösen. Jedoch habe ich nach genaueren Nachforschungen herausgefunden, das dieser wohl nur für Validatoren gedacht ist, die eine Variable betrachten bzw. diese validieren. Daher habe ich mich entschlossen nur das ValidatorInterface zu implementieren. dieses verlangt lediglich die Methoden isValid($value) und getMessages().
Wobei ich hier die Methode getMessages() auf ein 2-dimensionales Array erweitere, so das ein Array z.B. so aussieht


{"nr":["fieldempty","errors"],"vorname":["fieldempty","errors"],"nachname":["fieldempty","errors"]}

Hierzu wird zwar das ValidatorInterface etwas zweckentfremdet, dies hat aber den Vorteil das nun alle Felder gleichzeitig „validiert“ werden und nicht wie bei der Möglichkeit mit den AbstractValidator, Feld für Feld validiert wird und sobald ein Fehler gefunden wird dieser zurückgegeben wird.

Felder die das JSON Objekt enthalten soll stehen in $fields. Diese können über die setter- Methode gesetzt werden oder per Konstruktor übergeben werden.
Außerdem wird eine Möglichkeit geboten die Felder auf einen Leerstring zu überprüfen, dies kann mit isEmptyStringChecked(boolean) aktiviert werden.

Meine Implementierung sieht also wie folgt aus:
JSONValidator BasisKlasse


<?php
class JSONValidator implements ValidatorInterface{
    /** const messages */
    const FIELD_MISSING = "fieldmissing";
    const FIELD_EMPTY = "fieldempty";

    /**
     * Contains all error messages
     * @var array|null
     */
    private $messages = null;

    /**
     * Should contain all fields of the json object, that are expected
     * @var array|null
     */
    private $fields = null;

    /**
     * If set to true, fields must also be not empty strings
     * @var boolean
     */
    private $emptystring = false;

    public function __construct($fields = array()){
        $this->messages = array();
        $this->fields = $fields;
    }

    /**
     * Returns true if and only if $value meets the validation requirements
     *
     * If $value fails validation, then this method returns false, and
     * getMessages() will return an array of messages that explain why the
     * validation failed.
     *
     * @param  array $value
     * @return boolean
     * @throws Exception\RuntimeException If validation of $value is impossible
     */
    public function isValid($value){
        $isValid = true;
        foreach($this->fields as $field){
            if(!array_key_exists($field, $value)){
                $this->addMessage($field, self::FIELD_MISSING);
                $isValid = false;
            }else if($this->emptystring && $value[$field] == ""){
                $this->addMessage($field, self::FIELD_EMPTY);
                $isValid = false;
            }
            echo $value[$field];
        }

        return $isValid;
    }

    /**
     * checks if field already has an error
     * @param $field
     * @return bool
     */
    private function fieldHasError($field){
        return !array_key_exists($field, $this->messages);
    }

    /**
     * Returns an array which contains all the error messages
     * if isValid($value) returned true, the array will only be status => ok and no messages, otherwise the message part will contain all the error
     * messages. The key of the message array is the field which contained the error.
     * @return array
     */
    public function getMessages(){
        return $this->messages;
    }

    /**
     *
     * @param array $messages
     */
    public function setMessages(array $messages){
        $this->messages = $messages;
    }

    public function addMessage($field, $message){
        $this->messages[$field][] = $message;
    }

    /**
     * @param array $fields
     */
    public function setFields($fields){
        $this->fields = $fields;
    }

    /**
     * @return array
     */
    public function getFields(){
        return $this->fields;
    }

    /**
     * @param boolean $checkempty
     */
    public function checkEmptyString($checkempty = true){
        $this->emptystring = $checkempty;
    }

    /**
     * @return boolean
     */
    public function isEmptyStringChecked(){
        return $this->emptystring;
    }
}

Und hier z.B. eine von JSONValidator abgeleitete Klasse, es wird nur der Konstruktor überschrieben, um die Felder des JSON Objekts festzulegen, weiters könnte man hier auch noch die die isValid()-Methode erweitern, wenn manche Felder eine spezielle Validation benötigen.


class PatStammValidator extends JSONValidator{

    public function __construct(){
        $fields = array( "nr", "vorname", "nachname", "anrede", "ort", "plz", "strasse", "gebDatum", "versicherungsnr", "versicherungsanstalt", "telnr", "email" );
        parent::__construct($fields);
    }
}

Und hier noch der Aufruf in der Action im Controller


...
$validator = new PatStammValidator();
$validator->checkEmptyString();
if($validator->isValid($patstammarray)){
   $patstamm = $this->createPatStammFromRequest($patstammarray);
   return new JsonModel($patstamm->toArray());
}else{
   return new JsonModel(array("status"=>"error", "messages"=>$validator->getMessages()));
}
...

Somit können die meisten Formulare zumindest schnell auf Leerstrings geprüft werden und es kann geprüft werden, ob von der Clientseite überhaupt die richtigen Daten gesendet worden sind.

Die Ausgabe auf der Client Seite könnte in etwa mit JavaScript so aussehen:


var patStammView = (function(window, document, jQuery){
  var module = {};
  var settings = {
        varstocheck: {  "nr":"nr", "vorname":"name", "nachname":"name", "anrede":"name", 
                        "ort":"plz", "plz":"plz", "strasse":"strasse", "gebDatum":"gebDatum",
                        "versicherungsnr":"versichnr", "versicherungsanstalt":"versichnr", 
                        "telnr":"tel","email":"email"},
    };

  module.init = function(options){
      $.extend(settings, options);
  };

  module.save = function(){
      $('.control-group').each(function(){
          $(this).removeClass("error");
          $(this).children(".help-inline, .help-block").html("");
      });
      var jsonstring = $('#patient-form').serialize();
      JSLogger.log(jsonstring);
          $.ajax({
              url: $('#basePath').val() + "/api/patient/put",
              type: "PUT",
              data: _getJSONFromForm(),
              success: function(data){
                  if(data.hasOwnProperty("status") && data.status === "error"){
                      //$('#errormessage').html(data);
                      if(data.hasOwnProperty("messages")){
                          var messages = data.messages;
                          $.each(settings.varstocheck, function(index, value){
                              JSLogger.log(index);
                              if(messages.hasOwnProperty(index)){
                                  $('#control-group-'+value).addClass("error");
                                  $.each(messages[index], function(count, message){
                                   $('#control-group-' + value).
                                   children(".help-inline, .help-block").html("Bitte etwas eintragen!");
                                  });
                             }
                          });
                      }
                  }
              },
              error: function(data){
                  alert("Fehler beim Speichern!");
              },
              fail: function(){
                  alert("Fehler beim Speichern!");
              }
          });
      };
      return module;

      function _getJSONFromForm(){
          ...
          return json;
      }
}(window, document, jQuery));

Es wird in der Variable „varstocheck“ ein assoziatives Array angelegt, wobei der Key aus dem Feldnamen im zurückgelieferten JSON besteht, der Wert dazu der Name des Feldes ist, das „markiert“ werden soll.

Hier z.B. ein Teil der control-group id (Beispiel: „vorname“:name“ => „control-group-name“)


<div class="control-group" id="control-group-name">
    <label class="control-label">Name:</label>
    <input type="text" name="anrede" class="span2" id="patanrede" placeholder="Anrede" />
    <input type="text" name="nachname" id="patnachname" placeholder="Nachname">
    <input type="text" name="vorname" id="patvorname" placeholder="Vorname" />
    <span class="help-block"></span>
</div>

Weiters könnte man hier noch die entsprechenden Fehler Codes abarbeiten und dementsprechende Fehler ausgeben, dies habe ich mir hier mal gespart.

Siehe auch:

Zend 2 – RESTful Request

Ich versuche nun schon seit längerer Zeit mithilfe von \Zend\Http\Request bzw \Zend\Http\PhpEnvironment\Request eine REST fähige Applikation auf die Beine zustellen. Leider habe ich festgestellt, das diese Klasse keine Methode bietet um Daten die mit PUT oder DELETE mitgeliefert werden auszulesen.
Nach ein paar kurzen Recherchen bin ich auf mehrere Posts auf stackoverflow gestoßen, die ähnliche Probleme hatten. (Auch allgemein in PHP z.B. http://stackoverflow.com/questions/2081894/handling-put-delete-arguments-in-php). Dort wurde die Möglichkeit erwähnt durch

<?php parse_str(file_get_contents('php://input'), $data); >

die Parameter auszulesen. Ich habe mir daraufhin eine Klasse geschrieben die im Grunde von der \Zend\Http\PhpEnvironment\Request Klasse erbt (diese befüllt sich bereits im Konstruktor selbst), mit der Erweiterung Daten auch bei PUT und DELETE auszulesen.
<?php
use Zend\Http\PhpEnvironment\Request as ZendRequest;
use Zend\Stdlib\ParametersInterface;
use Zend\Stdlib\Parameters;

/**
 * User: Zelle
 * Date: 18.01.13
 * Time: 21:04
 *
 */
class Request extends ZendRequest{

    private $putParams;
    private $deleteParams;

    /**
     * Erstellt aus den Zend Request Objekt ein eigenes Request Objekt das die moeglichkeit bietet
     * auch bei PUT und DELETE anfragen gesendete Daten auszulesen
     * @use $_SERVER
     * @use $_GET
     * @use $_POST
     * @use php://input
     *
     */
    public function __construct(){
        parent::__construct();
        $data = array();
        parse_str(file_get_contents('php://input'), $data);
        switch($this->method){
            case self::METHOD_PUT;
                $this->putParams = new Parameters($data);
                break;
            case self::METHOD_DELETE:
                $this->deleteParams = new Parameters($data);
                break;
        }
    }

    /**
     * Liefert aus den Put Container die passenden Variablen zurück
     *
     * @param null $name
     * @param null $default
     * @return mixed|null|\Zend\Stdlib\Parameters|\Zend\Stdlib\ParametersInterface
     */
    public function getPut($name = null, $default = null){
        if($this->getMethod() !== self::METHOD_PUT){
            return $default;
        }
        if ($this->putParams === null) {
            $this->putParams = new Parameters();
        }

        if ($name === null) {
            return $this->putParams;
        }

        return $this->putParams->get($name, $default);
    }

    /**
     *
     * @see \Zend\Http\Request setPost
     *
     * @param ParametersInterface $put
     * @return Request
     */
    public function setPut(ParametersInterface $put)
    {
        $this->putParams = $put;
        return $this;
    }

    /**
     * Liefert aus den Put Container die passenden Variablen zurück
     *
     * @param null $name
     * @param null $default
     * @return mixed|null|\Zend\Stdlib\Parameters|\Zend\Stdlib\ParametersInterface
     */
    public function getDelete($name = null, $default = null){
        if($this->getMethod() !== self::METHOD_DELETE){
            return $default;
        }
        if ($this->deleteParams === null) {
            $this->deleteParams = new Parameters();
        }

        if ($name === null) {
            return $this->deleteParams;
        }

        return $this->deleteParams->get($name, $default);
    }

    /**
     *
     * @see \Zend\Http\Request setPost
     *
     * @param ParametersInterface $put
     * @return Request
     */
    public function setDelete(ParametersInterface $put)
    {
        $this->putParams = $put;
        return $this;
    }

}


Diese muss jetzt nur mehr befüllt werden. Dies funktioniert über den ServiceManager in der Modulklasse des jeweiligen Modules. Nun kann im Controller mit $this->getRequest darauf zugegriffen werden oder auch über $this->getServiceLocator()->get(‚Request‘);.
use Zend\ModuleManager\Feature\ConfigProviderInterface,
    Zend\ModuleManager\Feature\ServiceProviderInterface;

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(
                'Request' => function() {
                    return new \Medinfo\Request\Request();;

                },
            )
        );
    }

Weiters kann durch das Überschreiben des Request Objekts auch im Controller darauf zugegriffen werden

<?php 
use Zend\Mvc\Controller\AbstractActionController; 
use Zend\Http\Request; 
/**  
* User: Zelle  
* Date: 13.01.13  
* Time: 22:20  
*  
*/  class PatStammApiController extends AbstractActionController {     
public function putAction(){         
$request = $this->getRequest();
$sevlrequest = $this->getServiceLocator()->get('Request');

    }
}

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.

Posts navigation

1 2 3
Scroll to top