Einleitung

Entwickler möchten sich die Arbeit an redundanten Sourcecode bei einer Modulüberladung sparen. Sourcecode soll für die jeweiligen Zwecke eine wartbare Codebasis besitzen und leicht zu erweitern sein. Wenn ein OXID-Modul dann für die jeweiligen Zwecke überladen werden muss und ggf. für angepasste Anforderungen überladene Methoden oder gar Klassen komplett angefasst werden müssen sind wir geschockt. Wir möchten die gleiche Codebasis nicht redundant implementieren, denn dies hat auch zur Folge, dass Sourcecode der redundant implementiert wird auch doppelt zu pflegen ist. Im Falle von OXID und Webservices die für einige Projekte anfallen, ist dieser Punkt immens wichtig. Haben wir eine Code-Basis die für jedes der Projekte an denen wir entwickeln einsetzbar ist und nur für kundenspezifische Anforderungen ein klein wenig angepasst werden muss, sind wir erleichtert darüber, wenn wir einfach die jeweiligen notwendigen Klassen nur überladen müssen & das „Hexenwerk“ bei der Deserialisierung von Webservice-Responses autom. von statten geht.

 

Modulüberladung & Einsatz eines ORMs

Um komplexe Webservices einfach zu halten, verwende ich das Doctrine ORM. Im Normalfall ist Doctrine ausgerichtet auf das handling von Datenbanken, ich habe es hierbei etwas zweck entfremdet und setze es ein um Auszeichnungsprachen zu mappen. Hierfür beziehe ich aus dem Composer-Repository die Library „JmsSerializer“. Diese erweitert Doctrine um das parsen von Auszeichnungsprachen wie XML, HTML, JSON und YML.

Wenn dieses über Composer ab Version 5.2.6 von OXID bezogen wird, lassen sich mittels des ORMs Webservices sehr simpel handhaben. Kommen neue Felder hinzu die es so in dem Standard-Modul nicht gibt, lassen sich diese durch kundenspezifische Erweiterungen ohne größeren Mehraufwand implementieren. Denn an dieser Stelle greift im Idealfall die Modulüberladung von OXID.

Probleme bei der Überladung des Moduls

Nun ist es so, wir haben die Library die wir brauchen über das Composer-Repository bezogen, wir haben auch bereits ein kundenspezifisches Modul entwickelt welches die zusätzlichen Felder in einem Webservice implementiert. Wir stellen fest, die Überladung der Requests erfolgt ohne jegliche Probleme. Die Überladung der Responses findet immer statt mit dem entsprechenden Parent-Entitätstypen. So ein Mist denken wir uns, denn eigentlich sollte hier durch die Modulüberladung von OXID die Klasse instantiiert werden die wir in unserem kundenspezifischen Modul haben. Dies erfolgt jedoch nicht, denn durch die Annotationen die gegeben sind kennt der Autoloader von OXID zwar die Parent-Klasse unseres Moduls, welches wir überladen wollen, jedoch nicht das Kindelement. Somit kriegen wir zwar vom Webservice einen validen Response zurück, können mit diesem jedoch nichts anfangen, da die Felder die wir brauchen nicht beim ORM bei der Deserializierung gemappt wurden. Sie fehlen schlecht hin einfach.

Lösungsansatz für die Modulüberladung

Ich habe mir hier ein Weilchen den Kopf zu zerbrochen, wie ich dem ORM mitteile, dass es überladene Klassen gibt. Mein erster Lösungsansatz war Doctrine mitzuteilen, dass es einen weiteren Autoloader gibt und zwar den Autoloader „oxAutoloader“. Dies brachte jedoch nicht den notwendigen Erfolg. Denn der Autoloader versteht zwar, dass es überladene Klassen gibt, aber er erwartet explizit die „_parent“ Klassen um sie zu initialisieren und zu includen. Diese bekommt er jedoch nicht, denn in den Annotationen ist die Basis-Klasse gegeben. Im Normalfall in OXID selbst ermittelt oxNew ob es zu einer Basis-Klasse entsprechende Überladungen gibt und wendet diese auf die Basis-Klasse an.

Im Falle von Doctrine, zieht sich Doctrine die Klassen die in den Annotations angegeben sind. Damit ist die Überladung der Klassen nicht gegeben, sie findet nicht statt.

Um hierfür ein Brücke zu bauen, muss Doctrine mitgeteilt werden, dass es überladene Klassen gibt und welche diese sind. Wichtig ist, dass dabei der OXID-Standard und der Doctrine-Standard nicht aufgebrochen werden dürfen.

Subscriber für die Modulüberladung

Um den beschriebenen Punkt mit der Updatefähigkeit nachzukommen habe ich eine recht vernünftige Lösung gefunden. Dem Doctrine ORM muss mitgeteilt werden, dass es überladene Klassen gibt. Doctrine selbst darf nichts darüber wissen, dass überladene Klassen existent sind, sondern muss die überladenen Klassen handhaben sofern es welche gibt. Dementsprechend muss eine Brücke zwischen OXID und Doctrine her, um Responses vernünftig zu handhaben.

Hierfür habe ich für meine Projekte einen weiteren Event-Subscriber implementiert, der sich in das Event „pre_deserialization“ einklinkt.

 

<?php

/**
 *
 * @author      Ilya Beliaev <info@php-dev.info>
 */

use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;

class ib_DeserializationSubscriber extends oxSuperCfg implements EventSubscriberInterface{

    /**
     * @inheritDoc
     */
    public static function getSubscribedEvents() {
        $aResult = [[
            'event'     => 'serializer.pre_deserialize',
            'method'    => 'onPreDeserialize',
        ]];
        return $aResult;
    }

    public function onPreDeserialize(PreDeserializeEvent $oEvent){
        $aType = $oEvent->getType();
        $sClassName = $aType["name"];
        $oObject = oxNew($sClassName);
        $sClassName = get_class($oObject);
        $oEvent->setType($sClassName);
    }
}

Dieser ermittelt die Klasse des zuletzt überladenen Moduls (siehe Klassenüberladung in Modulansicht)
und setzt die Klasse die zu initialisieren ist im Event.

Damit wird die Klasse instantiiert die zuletzt den Entitätstypen überladen hat, die entsprechenden Attribute werden richtig
initialisiert und damit haben wir alle Werte die relevant sind im Response.

Dieser Event-Subscriber muss anschließend Doctrine bekannt gemacht werden:


public function getSerializer(){
        if(!isset($this->_oSerializer)){
            $oConfig                = $this->getConfig();
            $blDebug                = $this->getDebug();
            $sModuleDir             = $oConfig->getModulesDir();

            AnnotationRegistry::registerAutoloadNamespace(
                'JMS\Serializer\Annotation',
                $sModuleDir . "/vendor/jms/serializer/src");

            $oSerializerBuilder     = SerializerBuilder::create();
            $oSerializerBuilder->configureListeners(function(EventDispatcher $oDispatcher){
                $oDeserializationSubscriber     = oxNew(ib_DeserializationSubscriber");
                $oDispatcher->addSubscriber($oDeserializationSubscriber);
            });


            $oSerializerBuilder->setDebug($blDebug);
            $oSerializer            = $oSerializerBuilder->build();
            $this->setSerializer($oSerializer);
        }

        return $this->_oSerializer;
    }

Anschließend sind die überladenen Objekte Doctrine bekannt und werden richtig geparst.